Merge remote-tracking branch 'origin/develop' into jitsi-picture-in-picture
* origin/develop: (100 commits) Add comments to isRegionalIndicator Stop voice messages that are playing when starting a recording Properly set style attribute on shared usercontent iframe Fix in-call context menus when in PiP mode (#6552) Extract tooltipYOffset to a const Increase yOffset by 4px away i18n Post-merge conflict resolution and improve alignment of tooltips Fix image & blurhash info when skipping thumbnail due to thresholds Skip sending a thumbnail if it is not a sufficient saving over the original Null guard space inviter to prevent the app exploding Remove seams from pin icon Appease Jest Fix worklet reference for new webpack pipeline i18n Update copy Fix wrong cursor being used in PiP Fix voice feed cut-off Use flex-start as it has more universal support Wrap cases in {} ...
This commit is contained in:
commit
3b002c4c1f
66 changed files with 1762 additions and 537 deletions
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
* @matrix-org/element-web
|
1
.node-version
Normal file
1
.node-version
Normal file
|
@ -0,0 +1 @@
|
||||||
|
14
|
|
@ -87,6 +87,7 @@
|
||||||
"pako": "^2.0.3",
|
"pako": "^2.0.3",
|
||||||
"parse5": "^6.0.1",
|
"parse5": "^6.0.1",
|
||||||
"png-chunks-extract": "^1.0.0",
|
"png-chunks-extract": "^1.0.0",
|
||||||
|
"posthog-js": "1.12.2",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"qrcode": "^1.4.4",
|
"qrcode": "^1.4.4",
|
||||||
"re-resizable": "^6.9.0",
|
"re-resizable": "^6.9.0",
|
||||||
|
@ -123,6 +124,7 @@
|
||||||
"@babel/traverse": "^7.12.12",
|
"@babel/traverse": "^7.12.12",
|
||||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
||||||
"@peculiar/webcrypto": "^1.1.4",
|
"@peculiar/webcrypto": "^1.1.4",
|
||||||
|
"@sentry/types": "^6.10.0",
|
||||||
"@sinonjs/fake-timers": "^7.0.2",
|
"@sinonjs/fake-timers": "^7.0.2",
|
||||||
"@types/classnames": "^2.2.11",
|
"@types/classnames": "^2.2.11",
|
||||||
"@types/commonmark": "^0.27.4",
|
"@types/commonmark": "^0.27.4",
|
||||||
|
@ -147,13 +149,14 @@
|
||||||
"@typescript-eslint/eslint-plugin": "^4.17.0",
|
"@typescript-eslint/eslint-plugin": "^4.17.0",
|
||||||
"@typescript-eslint/parser": "^4.17.0",
|
"@typescript-eslint/parser": "^4.17.0",
|
||||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
|
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
|
||||||
|
"allchange": "github:matrix-org/allchange",
|
||||||
"babel-jest": "^26.6.3",
|
"babel-jest": "^26.6.3",
|
||||||
"chokidar": "^3.5.1",
|
"chokidar": "^3.5.1",
|
||||||
"concurrently": "^5.3.0",
|
"concurrently": "^5.3.0",
|
||||||
"enzyme": "^3.11.0",
|
"enzyme": "^3.11.0",
|
||||||
"eslint": "7.18.0",
|
"eslint": "7.18.0",
|
||||||
"eslint-config-google": "^0.14.0",
|
"eslint-config-google": "^0.14.0",
|
||||||
"eslint-plugin-matrix-org": "github:matrix-org/eslint-plugin-matrix-org#main",
|
"eslint-plugin-matrix-org": "github:matrix-org/eslint-plugin-matrix-org#2306b3d4da4eba908b256014b979f1d3d43d2945",
|
||||||
"eslint-plugin-react": "^7.22.0",
|
"eslint-plugin-react": "^7.22.0",
|
||||||
"eslint-plugin-react-hooks": "^4.2.0",
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
"glob": "^7.1.6",
|
"glob": "^7.1.6",
|
||||||
|
@ -166,6 +169,7 @@
|
||||||
"matrix-web-i18n": "github:matrix-org/matrix-web-i18n",
|
"matrix-web-i18n": "github:matrix-org/matrix-web-i18n",
|
||||||
"react-test-renderer": "^17.0.2",
|
"react-test-renderer": "^17.0.2",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
|
"rrweb-snapshot": "1.1.7",
|
||||||
"stylelint": "^13.9.0",
|
"stylelint": "^13.9.0",
|
||||||
"stylelint-config-standard": "^20.0.0",
|
"stylelint-config-standard": "^20.0.0",
|
||||||
"stylelint-scss": "^3.18.0",
|
"stylelint-scss": "^3.18.0",
|
||||||
|
@ -189,7 +193,8 @@
|
||||||
"decoderWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
|
"decoderWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
|
||||||
"decoderWorker\\.min\\.wasm": "<rootDir>/__mocks__/empty.js",
|
"decoderWorker\\.min\\.wasm": "<rootDir>/__mocks__/empty.js",
|
||||||
"waveWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
|
"waveWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
|
||||||
"workers/(.+)\\.worker\\.ts": "<rootDir>/__mocks__/workerMock.js"
|
"workers/(.+)\\.worker\\.ts": "<rootDir>/__mocks__/workerMock.js",
|
||||||
|
"RecorderWorklet": "<rootDir>/__mocks__/empty.js"
|
||||||
},
|
},
|
||||||
"transformIgnorePatterns": [
|
"transformIgnorePatterns": [
|
||||||
"/node_modules/(?!matrix-js-sdk).+$"
|
"/node_modules/(?!matrix-js-sdk).+$"
|
||||||
|
|
4
release_config.yaml
Normal file
4
release_config.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
subprojects:
|
||||||
|
matrix-js-sdk:
|
||||||
|
includeByDefault: false
|
||||||
|
|
|
@ -266,6 +266,7 @@
|
||||||
@import "./views/spaces/_SpacePublicShare.scss";
|
@import "./views/spaces/_SpacePublicShare.scss";
|
||||||
@import "./views/terms/_InlineTermsAgreement.scss";
|
@import "./views/terms/_InlineTermsAgreement.scss";
|
||||||
@import "./views/toasts/_AnalyticsToast.scss";
|
@import "./views/toasts/_AnalyticsToast.scss";
|
||||||
|
@import "./views/toasts/_IncomingCallToast.scss";
|
||||||
@import "./views/toasts/_NonUrgentEchoFailureToast.scss";
|
@import "./views/toasts/_NonUrgentEchoFailureToast.scss";
|
||||||
@import "./views/verification/_VerificationShowSas.scss";
|
@import "./views/verification/_VerificationShowSas.scss";
|
||||||
@import "./views/voip/_CallContainer.scss";
|
@import "./views/voip/_CallContainer.scss";
|
||||||
|
|
|
@ -28,7 +28,7 @@ limitations under the License.
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
grid-row: 2 / 4;
|
grid-row: 2 / 4;
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
background-color: $dark-panel-bg-color;
|
background-color: $toast-bg-color;
|
||||||
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5);
|
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ limitations under the License.
|
||||||
grid-row: 1 / 3;
|
grid-row: 1 / 3;
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
background-color: $dark-panel-bg-color;
|
background-color: $toast-bg-color;
|
||||||
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5);
|
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -85,7 +85,7 @@ limitations under the License.
|
||||||
.mx_InteractiveAuthEntryComponents_termsPolicy {
|
.mx_InteractiveAuthEntryComponents_termsPolicy {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,6 @@ limitations under the License.
|
||||||
.mx_desktopCapturerSourcePicker_source_thumbnail {
|
.mx_desktopCapturerSourcePicker_source_thumbnail {
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
width: 312px;
|
|
||||||
border-width: 2px;
|
border-width: 2px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
|
@ -53,6 +52,5 @@ limitations under the License.
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 312px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -271,7 +271,7 @@ limitations under the License.
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: start;
|
justify-content: flex-start;
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
|
|
||||||
.mx_EventTile_avatar {
|
.mx_EventTile_avatar {
|
||||||
|
|
|
@ -310,14 +310,12 @@ $hover-select-border: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomView_timeline_rr_enabled {
|
.mx_RoomView_timeline_rr_enabled {
|
||||||
|
.mx_EventTile[data-layout=group] {
|
||||||
.mx_EventTile:not([data-layout=bubble]) {
|
|
||||||
.mx_EventTile_line {
|
.mx_EventTile_line {
|
||||||
/* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */
|
/* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */
|
||||||
margin-right: 110px;
|
margin-right: 110px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter
|
// on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,21 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/element-icons/trashcan.svg');
|
mask-image: url('$(res)/img/element-icons/trashcan.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_VoiceRecordComposerTile_uploadingState {
|
||||||
|
margin-right: 10px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_VoiceRecordComposerTile_failedState {
|
||||||
|
margin-right: 21px;
|
||||||
|
|
||||||
|
.mx_VoiceRecordComposerTile_uploadState_badge {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 4px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_MessageComposer_row .mx_VoiceMessagePrimaryContainer {
|
.mx_MessageComposer_row .mx_VoiceMessagePrimaryContainer {
|
||||||
// Note: remaining class properties are in the PlayerContainer CSS.
|
// Note: remaining class properties are in the PlayerContainer CSS.
|
||||||
|
|
||||||
|
@ -68,7 +83,7 @@ limitations under the License.
|
||||||
height: 10px;
|
height: 10px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 12px; // 12px from the left edge for container padding
|
left: 12px; // 12px from the left edge for container padding
|
||||||
top: 18px; // vertically center (middle align with clock)
|
top: 16px; // vertically center (middle align with clock)
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
149
res/css/views/toasts/_IncomingCallToast.scss
Normal file
149
res/css/views/toasts/_IncomingCallToast.scss
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_IncomingCallToast {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
pointer-events: initial; // restore pointer events so the user can accept/decline
|
||||||
|
|
||||||
|
.mx_IncomingCallToast_content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: 8px;
|
||||||
|
|
||||||
|
.mx_CallEvent_caller {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-18px;
|
||||||
|
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CallEvent_type {
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $tertiary-fg-color;
|
||||||
|
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.mx_CallEvent_type_icon {
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
margin-right: 6px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: inherit;
|
||||||
|
width: inherit;
|
||||||
|
background-color: $tertiary-fg-color;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_IncomingCallToast_content_voice {
|
||||||
|
.mx_CallEvent_type .mx_CallEvent_type_icon::before,
|
||||||
|
.mx_IncomingCallToast_buttons .mx_IncomingCallToast_button_accept span::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/call/voice-call.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_IncomingCallToast_content_video {
|
||||||
|
.mx_CallEvent_type .mx_CallEvent_type_icon::before,
|
||||||
|
.mx_IncomingCallToast_buttons .mx_IncomingCallToast_button_accept span::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/call/video-call.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_IncomingCallToast_buttons {
|
||||||
|
margin-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.mx_IncomingCallToast_button {
|
||||||
|
height: 24px;
|
||||||
|
padding: 0px 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-right: 0;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
padding: 8px 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
background-color: $button-fg-color;
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_IncomingCallToast_button_accept span::before {
|
||||||
|
mask-size: 13px;
|
||||||
|
width: 13px;
|
||||||
|
height: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_IncomingCallToast_button_decline span::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/call/hangup.svg');
|
||||||
|
mask-size: 16px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_IncomingCallToast_iconButton {
|
||||||
|
display: flex;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
|
||||||
|
height: inherit;
|
||||||
|
width: inherit;
|
||||||
|
background-color: $tertiary-fg-color;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-position: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_IncomingCallToast_silence::before {
|
||||||
|
mask-image: url('$(res)/img/voip/silence.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_IncomingCallToast_unSilence::before {
|
||||||
|
mask-image: url('$(res)/img/voip/un-silence.svg');
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,6 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_CallPreview {
|
.mx_CallPreview {
|
||||||
pointer-events: initial; // restore pointer events so the user can leave/interact
|
pointer-events: initial; // restore pointer events so the user can leave/interact
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.mx_VideoFeed_remote.mx_VideoFeed_voice {
|
.mx_VideoFeed_remote.mx_VideoFeed_voice {
|
||||||
min-height: 150px;
|
min-height: 150px;
|
||||||
|
@ -43,84 +42,4 @@ limitations under the License.
|
||||||
.mx_AppTile_persistedWrapper div {
|
.mx_AppTile_persistedWrapper div {
|
||||||
min-width: 350px;
|
min-width: 350px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_IncomingCallBox {
|
|
||||||
min-width: 250px;
|
|
||||||
background-color: $voipcall-plinth-color;
|
|
||||||
padding: 8px;
|
|
||||||
box-shadow: 0px 14px 24px rgba(0, 0, 0, 0.08);
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
pointer-events: initial; // restore pointer events so the user can accept/decline
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.mx_IncomingCallBox_CallerInfo {
|
|
||||||
display: flex;
|
|
||||||
direction: row;
|
|
||||||
|
|
||||||
img, .mx_BaseAvatar_initial {
|
|
||||||
margin: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, p {
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
font-size: $font-14px;
|
|
||||||
line-height: $font-16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_IncomingCallBox_buttons {
|
|
||||||
padding: 8px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
> .mx_IncomingCallBox_spacer {
|
|
||||||
width: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> * {
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
margin-right: 0;
|
|
||||||
font-size: $font-15px;
|
|
||||||
line-height: $font-24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_IncomingCallBox_iconButton {
|
|
||||||
position: absolute;
|
|
||||||
right: 8px;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
background-color: $icon-button-color;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-size: contain;
|
|
||||||
mask-position: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_IncomingCallBox_silence::before {
|
|
||||||
mask-image: url('$(res)/img/voip/silence.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_IncomingCallBox_unSilence::before {
|
|
||||||
mask-image: url('$(res)/img/voip/un-silence.svg');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ limitations under the License.
|
||||||
.mx_CallView_pip {
|
.mx_CallView_pip {
|
||||||
width: 320px;
|
width: 320px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
background-color: $voipcall-plinth-color;
|
background-color: $toast-bg-color;
|
||||||
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.20);
|
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.20);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
||||||
|
@ -75,8 +75,6 @@ limitations under the License.
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
&.mx_VideoFeed_voice {
|
&.mx_VideoFeed_voice {
|
||||||
// We don't want to collide with the call controls that have 52px of height
|
|
||||||
margin-bottom: 52px;
|
|
||||||
background-color: $inverted-bg-color;
|
background-color: $inverted-bg-color;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
|
@ -40,8 +40,6 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
aspect-ratio: 16 / 9;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_VideoFeed_video {
|
.mx_VideoFeed_video {
|
||||||
|
|
|
@ -20,6 +20,7 @@ limitations under the License.
|
||||||
|
|
||||||
&.mx_VideoFeed_voice {
|
&.mx_VideoFeed_voice {
|
||||||
background-color: $inverted-bg-color;
|
background-color: $inverted-bg-color;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_VideoFeed_video {
|
.mx_VideoFeed_video {
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M18.5151 20.0831L15.6941 17.2621L17.2621 15.6941L20.0831 18.5151C21.5741 20.0061 22.1529 21.7793 21.9661 21.9661C21.7793 22.1529 20.0061 21.5741 18.5151 20.0831Z" fill="#737D8C"/>
|
<path d="m11.068 2c-0.32021 4.772e-4 -0.66852 0.17244-0.96484 0.46875-2.5464 2.5435-5.0905 5.0892-7.6348 7.6348-0.79016 0.7902-0.69302 1.9462 1.1641 1.9707 1.855 0.02447 3.4407-0.56671 3.8281-0.69141l2.4355 3.1445c-0.83503 1.9462-0.86902 4.062-0.058594 5.7949 0.47213 1.0095 1.79 1.0049 2.5781 0.2168l3.2773-3.2773 2.8223 2.8223c1.491 1.491 3.2644 2.0696 3.4512 1.8828s-0.39181-1.9602-1.8828-3.4512l-2.8223-2.8223 3.2773-3.2773c0.788-0.788 0.79075-2.106-0.21875-2.5781-1.733-0.81044-3.8468-0.77643-5.793 0.058594l-3.1445-2.4355c0.1247-0.38742 0.71588-1.9731 0.69141-3.8281-0.015311-1.1607-0.47217-1.6336-1.0059-1.6328z" fill="#737d8c"/>
|
||||||
<path d="M7.46196 11.3821C7.07677 11.5059 5.49073 12.0989 3.63366 12.0744C1.77658 12.0499 1.67795 10.8941 2.46811 10.1039L6.28598 6.28602L9.42196 9.42203L7.46196 11.3821Z" fill="#737D8C"/>
|
|
||||||
<path d="M11.3821 7.46202C11.5059 7.07682 12.0989 5.49077 12.0744 3.63368C12.0499 1.77658 10.8941 1.67795 10.1039 2.46812L6.28598 6.28602L9.42196 9.42203L11.3821 7.46202Z" fill="#737D8C"/>
|
|
||||||
<path d="M7.40596 11.438L11.4379 7.40602L14.9099 10.206L10.2059 14.9101L7.40596 11.438Z" fill="#737D8C"/>
|
|
||||||
<path d="M11.774 11.774C9.31114 14.2369 8.61779 17.7115 9.83827 20.3213C10.3104 21.3308 11.6288 21.3273 12.4169 20.5392L20.5391 12.4169C21.3271 11.6289 21.3307 10.3104 20.3212 9.83829C17.7114 8.61779 14.2369 9.31115 11.774 11.774Z" fill="#737D8C"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 744 B |
|
@ -1,3 +1,6 @@
|
||||||
|
// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0
|
||||||
|
$system-dark: #21262C;
|
||||||
|
|
||||||
// unified palette
|
// unified palette
|
||||||
// try to use these colors when possible
|
// try to use these colors when possible
|
||||||
$bg-color: #15191E;
|
$bg-color: #15191E;
|
||||||
|
@ -47,7 +50,7 @@ $inverted-bg-color: $base-color;
|
||||||
$selected-color: $room-highlight-color;
|
$selected-color: $room-highlight-color;
|
||||||
|
|
||||||
// selected for hoverover & selected event tiles
|
// selected for hoverover & selected event tiles
|
||||||
$event-selected-color: #21262c;
|
$event-selected-color: $system-dark;
|
||||||
|
|
||||||
// used for the hairline dividers in RoomView
|
// used for the hairline dividers in RoomView
|
||||||
$primary-hairline-color: transparent;
|
$primary-hairline-color: transparent;
|
||||||
|
@ -91,7 +94,7 @@ $lightbox-background-bg-color: #000;
|
||||||
$lightbox-background-bg-opacity: 0.85;
|
$lightbox-background-bg-opacity: 0.85;
|
||||||
|
|
||||||
$settings-grey-fg-color: #a2a2a2;
|
$settings-grey-fg-color: #a2a2a2;
|
||||||
$settings-profile-placeholder-bg-color: #21262c;
|
$settings-profile-placeholder-bg-color: $system-dark;
|
||||||
$settings-profile-overlay-placeholder-fg-color: #454545;
|
$settings-profile-overlay-placeholder-fg-color: #454545;
|
||||||
$settings-profile-button-bg-color: #e7e7e7;
|
$settings-profile-button-bg-color: #e7e7e7;
|
||||||
$settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color;
|
$settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color;
|
||||||
|
@ -112,8 +115,8 @@ $eventtile-meta-color: $roomtopic-color;
|
||||||
$header-divider-color: $header-panel-text-primary-color;
|
$header-divider-color: $header-panel-text-primary-color;
|
||||||
$composer-e2e-icon-color: $header-panel-text-primary-color;
|
$composer-e2e-icon-color: $header-panel-text-primary-color;
|
||||||
|
|
||||||
// this probably shouldn't have it's own colour
|
$quinary-content-color: #394049;
|
||||||
$voipcall-plinth-color: #394049;
|
$toast-bg-color: $quinary-content-color;
|
||||||
|
|
||||||
// ********************
|
// ********************
|
||||||
|
|
||||||
|
@ -175,7 +178,7 @@ $button-link-bg-color: transparent;
|
||||||
$togglesw-off-color: $room-highlight-color;
|
$togglesw-off-color: $room-highlight-color;
|
||||||
|
|
||||||
$progressbar-fg-color: $accent-color;
|
$progressbar-fg-color: $accent-color;
|
||||||
$progressbar-bg-color: #21262c;
|
$progressbar-bg-color: $system-dark;
|
||||||
|
|
||||||
$visual-bell-bg-color: #800;
|
$visual-bell-bg-color: #800;
|
||||||
|
|
||||||
|
@ -210,7 +213,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color;
|
||||||
$message-body-panel-fg-color: $secondary-fg-color;
|
$message-body-panel-fg-color: $secondary-fg-color;
|
||||||
$message-body-panel-bg-color: #394049; // "Dark Tile"
|
$message-body-panel-bg-color: #394049; // "Dark Tile"
|
||||||
$message-body-panel-icon-fg-color: $secondary-fg-color;
|
$message-body-panel-icon-fg-color: $secondary-fg-color;
|
||||||
$message-body-panel-icon-bg-color: #21262C; // "System Dark"
|
$message-body-panel-icon-bg-color: $system-dark; // "System Dark"
|
||||||
|
|
||||||
$voice-record-stop-border-color: $quaternary-fg-color;
|
$voice-record-stop-border-color: $quaternary-fg-color;
|
||||||
$voice-record-waveform-incomplete-fg-color: $quaternary-fg-color;
|
$voice-record-waveform-incomplete-fg-color: $quaternary-fg-color;
|
||||||
|
@ -228,9 +231,9 @@ $groupFilterPanel-background-blur-amount: 30px;
|
||||||
$composer-shadow-color: rgba(0, 0, 0, 0.28);
|
$composer-shadow-color: rgba(0, 0, 0, 0.28);
|
||||||
|
|
||||||
// Bubble tiles
|
// Bubble tiles
|
||||||
$eventbubble-self-bg: #143A34;
|
$eventbubble-self-bg: #14322E;
|
||||||
$eventbubble-others-bg: #394049;
|
$eventbubble-others-bg: $event-selected-color;
|
||||||
$eventbubble-bg-hover: #433C23;
|
$eventbubble-bg-hover: #1C2026;
|
||||||
$eventbubble-avatar-outline: $bg-color;
|
$eventbubble-avatar-outline: $bg-color;
|
||||||
$eventbubble-reply-color: #C1C6CD;
|
$eventbubble-reply-color: #C1C6CD;
|
||||||
|
|
||||||
|
|
|
@ -111,8 +111,8 @@ $eventtile-meta-color: $roomtopic-color;
|
||||||
$header-divider-color: $header-panel-text-primary-color;
|
$header-divider-color: $header-panel-text-primary-color;
|
||||||
$composer-e2e-icon-color: $header-panel-text-primary-color;
|
$composer-e2e-icon-color: $header-panel-text-primary-color;
|
||||||
|
|
||||||
// this probably shouldn't have it's own colour
|
$quinary-content-color: #394049;
|
||||||
$voipcall-plinth-color: #394049;
|
$toast-bg-color: $quinary-content-color;
|
||||||
|
|
||||||
// ********************
|
// ********************
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,12 @@
|
||||||
/* Noto Color Emoji contains digits, in fixed-width, therefore causing
|
/* Noto Color Emoji contains digits, in fixed-width, therefore causing
|
||||||
digits in flowed text to stand out.
|
digits in flowed text to stand out.
|
||||||
TODO: Consider putting all emoji fonts to the end rather than the front. */
|
TODO: Consider putting all emoji fonts to the end rather than the front. */
|
||||||
$font-family: Nunito, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Arial, Helvetica, Sans-Serif, 'Noto Color Emoji';
|
$font-family: 'Nunito', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', sans-serif, 'Noto Color Emoji';
|
||||||
|
|
||||||
$monospace-font-family: Inconsolata, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Courier, monospace, 'Noto Color Emoji';
|
$monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', monospace, 'Noto Color Emoji';
|
||||||
|
|
||||||
|
// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0
|
||||||
|
$system-light: #F4F6FA;
|
||||||
|
|
||||||
// unified palette
|
// unified palette
|
||||||
// try to use these colors when possible
|
// try to use these colors when possible
|
||||||
|
@ -178,8 +181,8 @@ $eventtile-meta-color: $roomtopic-color;
|
||||||
$composer-e2e-icon-color: #91a1c0;
|
$composer-e2e-icon-color: #91a1c0;
|
||||||
$header-divider-color: #91a1c0;
|
$header-divider-color: #91a1c0;
|
||||||
|
|
||||||
// this probably shouldn't have it's own colour
|
$toast-bg-color: $system-light;
|
||||||
$voipcall-plinth-color: #F4F6FA;
|
$voipcall-plinth-color: $system-light;
|
||||||
|
|
||||||
// ********************
|
// ********************
|
||||||
|
|
||||||
|
@ -331,7 +334,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color;
|
||||||
$message-body-panel-fg-color: $secondary-fg-color;
|
$message-body-panel-fg-color: $secondary-fg-color;
|
||||||
$message-body-panel-bg-color: #E3E8F0;
|
$message-body-panel-bg-color: #E3E8F0;
|
||||||
$message-body-panel-icon-fg-color: $secondary-fg-color;
|
$message-body-panel-icon-fg-color: $secondary-fg-color;
|
||||||
$message-body-panel-icon-bg-color: #F4F6FA;
|
$message-body-panel-icon-bg-color: $system-light;
|
||||||
|
|
||||||
// See non-legacy _light for variable information
|
// See non-legacy _light for variable information
|
||||||
$voice-record-stop-symbol-color: #ff4b55;
|
$voice-record-stop-symbol-color: #ff4b55;
|
||||||
|
@ -348,9 +351,9 @@ $appearance-tab-border-color: $input-darker-bg-color;
|
||||||
$composer-shadow-color: tranparent;
|
$composer-shadow-color: tranparent;
|
||||||
|
|
||||||
// Bubble tiles
|
// Bubble tiles
|
||||||
$eventbubble-self-bg: #F8FDFC;
|
$eventbubble-self-bg: #F0FBF8;
|
||||||
$eventbubble-others-bg: #F7F8F9;
|
$eventbubble-others-bg: $system-light;
|
||||||
$eventbubble-bg-hover: rgb(242, 242, 242);
|
$eventbubble-bg-hover: #FAFBFD;
|
||||||
$eventbubble-avatar-outline: #fff;
|
$eventbubble-avatar-outline: #fff;
|
||||||
$eventbubble-reply-color: #C1C6CD;
|
$eventbubble-reply-color: #C1C6CD;
|
||||||
|
|
||||||
|
@ -390,7 +393,7 @@ $eventbubble-reply-color: #C1C6CD;
|
||||||
@define-mixin mx_DialogButton_secondary {
|
@define-mixin mx_DialogButton_secondary {
|
||||||
// flip colours for the secondary ones
|
// flip colours for the secondary ones
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border: 1px solid $accent-color ! important;
|
border: 1px solid $accent-color !important;
|
||||||
color: $accent-color;
|
color: $accent-color;
|
||||||
background-color: $button-secondary-bg-color;
|
background-color: $button-secondary-bg-color;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,12 @@
|
||||||
/* Noto Color Emoji contains digits, in fixed-width, therefore causing
|
/* Noto Color Emoji contains digits, in fixed-width, therefore causing
|
||||||
digits in flowed text to stand out.
|
digits in flowed text to stand out.
|
||||||
TODO: Consider putting all emoji fonts to the end rather than the front. */
|
TODO: Consider putting all emoji fonts to the end rather than the front. */
|
||||||
$font-family: Inter, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Arial, Helvetica, Sans-Serif, 'Noto Color Emoji';
|
$font-family: 'Inter', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial', 'Helvetica', sans-serif, 'Noto Color Emoji';
|
||||||
|
|
||||||
$monospace-font-family: Inconsolata, Twemoji, 'Apple Color Emoji', 'Segoe UI Emoji', Courier, monospace, 'Noto Color Emoji';
|
$monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', monospace, 'Noto Color Emoji';
|
||||||
|
|
||||||
|
// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0
|
||||||
|
$system-light: #F4F6FA;
|
||||||
|
|
||||||
// unified palette
|
// unified palette
|
||||||
// try to use these colors when possible
|
// try to use these colors when possible
|
||||||
|
@ -138,7 +141,7 @@ $blockquote-bar-color: #ddd;
|
||||||
$blockquote-fg-color: #777;
|
$blockquote-fg-color: #777;
|
||||||
|
|
||||||
$settings-grey-fg-color: #a2a2a2;
|
$settings-grey-fg-color: #a2a2a2;
|
||||||
$settings-profile-placeholder-bg-color: #f4f6fa;
|
$settings-profile-placeholder-bg-color: $system-light;
|
||||||
$settings-profile-overlay-placeholder-fg-color: #2e2f32;
|
$settings-profile-overlay-placeholder-fg-color: #2e2f32;
|
||||||
$settings-profile-button-bg-color: #e7e7e7;
|
$settings-profile-button-bg-color: #e7e7e7;
|
||||||
$settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color;
|
$settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color;
|
||||||
|
@ -167,8 +170,8 @@ $eventtile-meta-color: $roomtopic-color;
|
||||||
$composer-e2e-icon-color: #91A1C0;
|
$composer-e2e-icon-color: #91A1C0;
|
||||||
$header-divider-color: #91A1C0;
|
$header-divider-color: #91A1C0;
|
||||||
|
|
||||||
// this probably shouldn't have it's own colour
|
$toast-bg-color: $system-light;
|
||||||
$voipcall-plinth-color: #F4F6FA;
|
$voipcall-plinth-color: $system-light;
|
||||||
|
|
||||||
// ********************
|
// ********************
|
||||||
|
|
||||||
|
@ -327,7 +330,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color;
|
||||||
$message-body-panel-fg-color: $secondary-fg-color;
|
$message-body-panel-fg-color: $secondary-fg-color;
|
||||||
$message-body-panel-bg-color: #E3E8F0; // "Separator"
|
$message-body-panel-bg-color: #E3E8F0; // "Separator"
|
||||||
$message-body-panel-icon-fg-color: $secondary-fg-color;
|
$message-body-panel-icon-fg-color: $secondary-fg-color;
|
||||||
$message-body-panel-icon-bg-color: #F4F6FA;
|
$message-body-panel-icon-bg-color: $system-light;
|
||||||
|
|
||||||
// These two don't change between themes. They are the $warning-color, but we don't
|
// These two don't change between themes. They are the $warning-color, but we don't
|
||||||
// want custom themes to affect them by accident.
|
// want custom themes to affect them by accident.
|
||||||
|
@ -350,9 +353,9 @@ $groupFilterPanel-background-blur-amount: 20px;
|
||||||
$composer-shadow-color: rgba(0, 0, 0, 0.04);
|
$composer-shadow-color: rgba(0, 0, 0, 0.04);
|
||||||
|
|
||||||
// Bubble tiles
|
// Bubble tiles
|
||||||
$eventbubble-self-bg: #F8FDFC;
|
$eventbubble-self-bg: #F0FBF8;
|
||||||
$eventbubble-others-bg: #F7F8F9;
|
$eventbubble-others-bg: $system-light;
|
||||||
$eventbubble-bg-hover: #FEFCF5;
|
$eventbubble-bg-hover: #FAFBFD;
|
||||||
$eventbubble-avatar-outline: $primary-bg-color;
|
$eventbubble-avatar-outline: $primary-bg-color;
|
||||||
$eventbubble-reply-color: #C1C6CD;
|
$eventbubble-reply-color: #C1C6CD;
|
||||||
|
|
||||||
|
@ -392,7 +395,7 @@ $eventbubble-reply-color: #C1C6CD;
|
||||||
@define-mixin mx_DialogButton_secondary {
|
@define-mixin mx_DialogButton_secondary {
|
||||||
// flip colours for the secondary ones
|
// flip colours for the secondary ones
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border: 1px solid $accent-color ! important;
|
border: 1px solid $accent-color !important;
|
||||||
color: $accent-color;
|
color: $accent-color;
|
||||||
background-color: $button-secondary-bg-color;
|
background-color: $button-secondary-bg-color;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
Copyright 2017, 2018 New Vector Ltd
|
Copyright 2017, 2018 New Vector Ltd
|
||||||
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||||
|
|
||||||
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.
|
||||||
|
@ -60,7 +61,6 @@ import Modal from './Modal';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import dis from './dispatcher/dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import WidgetUtils from './utils/WidgetUtils';
|
import WidgetUtils from './utils/WidgetUtils';
|
||||||
import WidgetEchoStore from './stores/WidgetEchoStore';
|
|
||||||
import SettingsStore from './settings/SettingsStore';
|
import SettingsStore from './settings/SettingsStore';
|
||||||
import { Jitsi } from "./widgets/Jitsi";
|
import { Jitsi } from "./widgets/Jitsi";
|
||||||
import { WidgetType } from "./widgets/WidgetType";
|
import { WidgetType } from "./widgets/WidgetType";
|
||||||
|
@ -86,6 +86,12 @@ import { randomUppercaseString, randomLowercaseString } from "matrix-js-sdk/src/
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import SdkConfig from './SdkConfig';
|
import SdkConfig from './SdkConfig';
|
||||||
import { ensureDMExists, findDMForUser } from './createRoom';
|
import { ensureDMExists, findDMForUser } from './createRoom';
|
||||||
|
import { IPushRule, RuleId, TweakName, Tweaks } from "matrix-js-sdk/src/@types/PushRules";
|
||||||
|
import { PushProcessor } from 'matrix-js-sdk/src/pushprocessor';
|
||||||
|
import { WidgetLayoutStore, Container } from './stores/widgets/WidgetLayoutStore';
|
||||||
|
import { getIncomingCallToastKey } from './toasts/IncomingCallToast';
|
||||||
|
import ToastStore from './stores/ToastStore';
|
||||||
|
import IncomingCallToast from "./toasts/IncomingCallToast";
|
||||||
|
|
||||||
export const PROTOCOL_PSTN = 'm.protocol.pstn';
|
export const PROTOCOL_PSTN = 'm.protocol.pstn';
|
||||||
export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn';
|
export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn';
|
||||||
|
@ -476,14 +482,28 @@ export default class CallHandler extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (newState) {
|
switch (newState) {
|
||||||
case CallState.Ringing:
|
case CallState.Ringing: {
|
||||||
this.play(AudioID.Ring);
|
const incomingCallPushRule = (
|
||||||
|
new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) as IPushRule
|
||||||
|
);
|
||||||
|
const pushRuleEnabled = incomingCallPushRule?.enabled;
|
||||||
|
const tweakSetToRing = incomingCallPushRule?.actions.some((action: Tweaks) => (
|
||||||
|
action.set_tweak === TweakName.Sound &&
|
||||||
|
action.value === "ring"
|
||||||
|
));
|
||||||
|
|
||||||
|
if (pushRuleEnabled && tweakSetToRing) {
|
||||||
|
this.play(AudioID.Ring);
|
||||||
|
} else {
|
||||||
|
this.silenceCall(call.callId);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case CallState.InviteSent:
|
}
|
||||||
|
case CallState.InviteSent: {
|
||||||
this.play(AudioID.Ringback);
|
this.play(AudioID.Ringback);
|
||||||
break;
|
break;
|
||||||
case CallState.Ended:
|
}
|
||||||
{
|
case CallState.Ended: {
|
||||||
const hangupReason = call.hangupReason;
|
const hangupReason = call.hangupReason;
|
||||||
Analytics.trackEvent('voip', 'callEnded', 'hangupReason', hangupReason);
|
Analytics.trackEvent('voip', 'callEnded', 'hangupReason', hangupReason);
|
||||||
this.removeCallForRoom(mappedRoomId);
|
this.removeCallForRoom(mappedRoomId);
|
||||||
|
@ -624,6 +644,19 @@ export default class CallHandler extends EventEmitter {
|
||||||
`Call state in ${mappedRoomId} changed to ${status}`,
|
`Call state in ${mappedRoomId} changed to ${status}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const toastKey = getIncomingCallToastKey(call.callId);
|
||||||
|
if (status === CallState.Ringing) {
|
||||||
|
ToastStore.sharedInstance().addOrReplaceToast({
|
||||||
|
key: toastKey,
|
||||||
|
priority: 100,
|
||||||
|
component: IncomingCallToast,
|
||||||
|
bodyClassName: "mx_IncomingCallToast",
|
||||||
|
props: { call },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ToastStore.sharedInstance().dismissToast(toastKey);
|
||||||
|
}
|
||||||
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'call_state',
|
action: 'call_state',
|
||||||
room_id: mappedRoomId,
|
room_id: mappedRoomId,
|
||||||
|
@ -914,6 +947,8 @@ export default class CallHandler extends EventEmitter {
|
||||||
action: 'view_room',
|
action: 'view_room',
|
||||||
room_id: roomId,
|
room_id: roomId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.placeCall(roomId, PlaceCallType.Voice, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async startTransferToPhoneNumber(call: MatrixCall, destination: string, consultFirst: boolean) {
|
private async startTransferToPhoneNumber(call: MatrixCall, destination: string, consultFirst: boolean) {
|
||||||
|
@ -993,14 +1028,10 @@ export default class CallHandler extends EventEmitter {
|
||||||
|
|
||||||
// prevent double clicking the call button
|
// prevent double clicking the call button
|
||||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
const currentJitsiWidgets = WidgetUtils.getRoomWidgetsOfType(room, WidgetType.JITSI);
|
const jitsiWidget = WidgetStore.instance.getApps(roomId).find((app) => WidgetType.JITSI.matches(app.type));
|
||||||
const hasJitsi = currentJitsiWidgets.length > 0
|
if (jitsiWidget) {
|
||||||
|| WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentJitsiWidgets, WidgetType.JITSI);
|
// If there already is a Jitsi widget pin it
|
||||||
if (hasJitsi) {
|
WidgetLayoutStore.instance.moveToContainer(room, jitsiWidget, Container.Top);
|
||||||
Modal.createTrackedDialog('Call already in progress', '', ErrorDialog, {
|
|
||||||
title: _t('Call in Progress'),
|
|
||||||
description: _t('A call is currently being placed!'),
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -209,6 +209,14 @@ async function loadImageElement(imageFile: File) {
|
||||||
return { width, height, img };
|
return { width, height, img };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Minimum size for image files before we generate a thumbnail for them.
|
||||||
|
const IMAGE_SIZE_THRESHOLD_THUMBNAIL = 1 << 15; // 32KB
|
||||||
|
// Minimum size improvement for image thumbnails, if both are not met then don't bother uploading thumbnail.
|
||||||
|
const IMAGE_THUMBNAIL_MIN_REDUCTION_SIZE = 1 << 16; // 1MB
|
||||||
|
const IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT = 0.1; // 10%
|
||||||
|
// We don't apply these thresholds to video thumbnails as a poster image is always useful
|
||||||
|
// and videos tend to be much larger.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the metadata for an image file and create and upload a thumbnail of the image.
|
* Read the metadata for an image file and create and upload a thumbnail of the image.
|
||||||
*
|
*
|
||||||
|
@ -217,23 +225,33 @@ async function loadImageElement(imageFile: File) {
|
||||||
* @param {File} imageFile The image to read and thumbnail.
|
* @param {File} imageFile The image to read and thumbnail.
|
||||||
* @return {Promise} A promise that resolves with the attachment info.
|
* @return {Promise} A promise that resolves with the attachment info.
|
||||||
*/
|
*/
|
||||||
function infoForImageFile(matrixClient, roomId, imageFile) {
|
async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imageFile: File) {
|
||||||
let thumbnailType = "image/png";
|
let thumbnailType = "image/png";
|
||||||
if (imageFile.type === "image/jpeg") {
|
if (imageFile.type === "image/jpeg") {
|
||||||
thumbnailType = "image/jpeg";
|
thumbnailType = "image/jpeg";
|
||||||
}
|
}
|
||||||
|
|
||||||
let imageInfo;
|
const imageElement = await loadImageElement(imageFile);
|
||||||
return loadImageElement(imageFile).then((r) => {
|
|
||||||
return createThumbnail(r.img, r.width, r.height, thumbnailType);
|
const result = await createThumbnail(imageElement.img, imageElement.width, imageElement.height, thumbnailType);
|
||||||
}).then((result) => {
|
const imageInfo = result.info;
|
||||||
imageInfo = result.info;
|
|
||||||
return uploadFile(matrixClient, roomId, result.thumbnail);
|
// we do all sizing checks here because we still rely on thumbnail generation for making a blurhash from.
|
||||||
}).then((result) => {
|
const sizeDifference = imageFile.size - imageInfo.thumbnail_info.size;
|
||||||
imageInfo.thumbnail_url = result.url;
|
if (
|
||||||
imageInfo.thumbnail_file = result.file;
|
imageFile.size <= IMAGE_SIZE_THRESHOLD_THUMBNAIL || // image is small enough already
|
||||||
|
(sizeDifference <= IMAGE_THUMBNAIL_MIN_REDUCTION_SIZE && // thumbnail is not sufficiently smaller than original
|
||||||
|
sizeDifference <= (imageFile.size * IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT))
|
||||||
|
) {
|
||||||
|
delete imageInfo["thumbnail_info"];
|
||||||
return imageInfo;
|
return imageInfo;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
const uploadResult = await uploadFile(matrixClient, roomId, result.thumbnail);
|
||||||
|
|
||||||
|
imageInfo["thumbnail_url"] = uploadResult.url;
|
||||||
|
imageInfo["thumbnail_file"] = uploadResult.file;
|
||||||
|
return imageInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -57,7 +57,33 @@ const BIGEMOJI_REGEX = new RegExp(`^(${EMOJIBASE_REGEX.source})+$`, 'i');
|
||||||
|
|
||||||
const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
|
const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
|
||||||
|
|
||||||
export const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet', 'matrix'];
|
export const PERMITTED_URL_SCHEMES = [
|
||||||
|
"bitcoin",
|
||||||
|
"ftp",
|
||||||
|
"geo",
|
||||||
|
"http",
|
||||||
|
"https",
|
||||||
|
"im",
|
||||||
|
"irc",
|
||||||
|
"ircs",
|
||||||
|
"magnet",
|
||||||
|
"mailto",
|
||||||
|
"matrix",
|
||||||
|
"mms",
|
||||||
|
"news",
|
||||||
|
"nntp",
|
||||||
|
"openpgp4fpr",
|
||||||
|
"sip",
|
||||||
|
"sftp",
|
||||||
|
"sms",
|
||||||
|
"smsto",
|
||||||
|
"ssh",
|
||||||
|
"tel",
|
||||||
|
"urn",
|
||||||
|
"webcal",
|
||||||
|
"wtai",
|
||||||
|
"xmpp",
|
||||||
|
];
|
||||||
|
|
||||||
const MEDIA_API_MXC_REGEX = /\/_matrix\/media\/r0\/(?:download|thumbnail)\/(.+?)\/(.+?)(?:[?/]|$)/;
|
const MEDIA_API_MXC_REGEX = /\/_matrix\/media\/r0\/(?:download|thumbnail)\/(.+?)\/(.+?)(?:[?/]|$)/;
|
||||||
|
|
||||||
|
|
|
@ -146,23 +146,23 @@ export default class IdentityAuthClient {
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
const { finished } = Modal.createTrackedDialog('Default identity server terms warning', '',
|
const { finished } = Modal.createTrackedDialog('Default identity server terms warning', '',
|
||||||
QuestionDialog, {
|
QuestionDialog, {
|
||||||
title: _t("Identity server has no terms of service"),
|
title: _t("Identity server has no terms of service"),
|
||||||
description: (
|
description: (
|
||||||
<div>
|
<div>
|
||||||
<p>{ _t(
|
<p>{ _t(
|
||||||
"This action requires accessing the default identity server " +
|
"This action requires accessing the default identity server " +
|
||||||
"<server /> to validate an email address or phone number, " +
|
"<server /> to validate an email address or phone number, " +
|
||||||
"but the server does not have any terms of service.", {},
|
"but the server does not have any terms of service.", {},
|
||||||
{
|
{
|
||||||
server: () => <b>{ abbreviateUrl(identityServerUrl) }</b>,
|
server: () => <b>{ abbreviateUrl(identityServerUrl) }</b>,
|
||||||
},
|
},
|
||||||
) }</p>
|
) }</p>
|
||||||
<p>{ _t(
|
<p>{ _t(
|
||||||
"Only continue if you trust the owner of the server.",
|
"Only continue if you trust the owner of the server.",
|
||||||
) }</p>
|
) }</p>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
button: _t("Trust"),
|
button: _t("Trust"),
|
||||||
});
|
});
|
||||||
const [confirmed] = await finished;
|
const [confirmed] = await finished;
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
|
|
|
@ -48,6 +48,7 @@ import { Jitsi } from "./widgets/Jitsi";
|
||||||
import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY, SSO_IDP_ID_KEY } from "./BasePlatform";
|
import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY, SSO_IDP_ID_KEY } from "./BasePlatform";
|
||||||
import ThreepidInviteStore from "./stores/ThreepidInviteStore";
|
import ThreepidInviteStore from "./stores/ThreepidInviteStore";
|
||||||
import CountlyAnalytics from "./CountlyAnalytics";
|
import CountlyAnalytics from "./CountlyAnalytics";
|
||||||
|
import { PosthogAnalytics } from "./PosthogAnalytics";
|
||||||
import CallHandler from './CallHandler';
|
import CallHandler from './CallHandler';
|
||||||
import LifecycleCustomisations from "./customisations/Lifecycle";
|
import LifecycleCustomisations from "./customisations/Lifecycle";
|
||||||
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
||||||
|
@ -573,6 +574,8 @@ async function doSetLoggedIn(
|
||||||
await abortLogin();
|
await abortLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PosthogAnalytics.instance.updateAnonymityFromSettings(credentials.userId);
|
||||||
|
|
||||||
Analytics.setLoggedIn(credentials.guest, credentials.homeserverUrl);
|
Analytics.setLoggedIn(credentials.guest, credentials.homeserverUrl);
|
||||||
|
|
||||||
MatrixClientPeg.replaceUsingCreds(credentials);
|
MatrixClientPeg.replaceUsingCreds(credentials);
|
||||||
|
@ -700,6 +703,8 @@ export function logout(): void {
|
||||||
CountlyAnalytics.instance.enable(/* anonymous = */ true);
|
CountlyAnalytics.instance.enable(/* anonymous = */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PosthogAnalytics.instance.logout();
|
||||||
|
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
// logout doesn't work for guest sessions
|
// logout doesn't work for guest sessions
|
||||||
// Also we sometimes want to re-log in a guest session if we abort the login.
|
// Also we sometimes want to re-log in a guest session if we abort the login.
|
||||||
|
|
355
src/PosthogAnalytics.ts
Normal file
355
src/PosthogAnalytics.ts
Normal file
|
@ -0,0 +1,355 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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 posthog, { PostHog } from 'posthog-js';
|
||||||
|
import PlatformPeg from './PlatformPeg';
|
||||||
|
import SdkConfig from './SdkConfig';
|
||||||
|
import SettingsStore from './settings/SettingsStore';
|
||||||
|
|
||||||
|
/* Posthog analytics tracking.
|
||||||
|
*
|
||||||
|
* Anonymity behaviour is as follows:
|
||||||
|
*
|
||||||
|
* - If Posthog isn't configured in `config.json`, events are not sent.
|
||||||
|
* - If [Do Not Track](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/doNotTrack) is
|
||||||
|
* enabled, events are not sent (this detection is built into posthog and turned on via the
|
||||||
|
* `respect_dnt` flag being passed to `posthog.init`).
|
||||||
|
* - If the `feature_pseudonymous_analytics_opt_in` labs flag is `true`, track pseudonomously, i.e.
|
||||||
|
* hash all matrix identifiers in tracking events (user IDs, room IDs etc) using SHA-256.
|
||||||
|
* - Otherwise, if the existing `analyticsOptIn` flag is `true`, track anonymously, i.e.
|
||||||
|
* redact all matrix identifiers in tracking events.
|
||||||
|
* - If both flags are false or not set, events are not sent.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface IEvent {
|
||||||
|
// The event name that will be used by PostHog. Event names should use snake_case.
|
||||||
|
eventName: string;
|
||||||
|
|
||||||
|
// The properties of the event that will be stored in PostHog. This is just a placeholder,
|
||||||
|
// extending interfaces must override this with a concrete definition to do type validation.
|
||||||
|
properties: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Anonymity {
|
||||||
|
Disabled,
|
||||||
|
Anonymous,
|
||||||
|
Pseudonymous
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an event extends IPseudonymousEvent, the event contains pseudonymous data
|
||||||
|
// that won't be sent unless the user has explicitly consented to pseudonymous tracking.
|
||||||
|
// For example, it might contain hashed user IDs or room IDs.
|
||||||
|
// Such events will be automatically dropped if PosthogAnalytics.anonymity isn't set to Pseudonymous.
|
||||||
|
export interface IPseudonymousEvent extends IEvent {}
|
||||||
|
|
||||||
|
// If an event extends IAnonymousEvent, the event strictly contains *only* anonymous data;
|
||||||
|
// i.e. no identifiers that can be associated with the user.
|
||||||
|
export interface IAnonymousEvent extends IEvent {}
|
||||||
|
|
||||||
|
export interface IRoomEvent extends IPseudonymousEvent {
|
||||||
|
hashedRoomId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPageView extends IAnonymousEvent {
|
||||||
|
eventName: "$pageview";
|
||||||
|
properties: {
|
||||||
|
durationMs?: number;
|
||||||
|
screen?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashHex = async (input: string): Promise<string> => {
|
||||||
|
const buf = new TextEncoder().encode(input);
|
||||||
|
const digestBuf = await window.crypto.subtle.digest("sha-256", buf);
|
||||||
|
return [...new Uint8Array(digestBuf)].map((b: number) => b.toString(16).padStart(2, "0")).join("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const whitelistedScreens = new Set([
|
||||||
|
"register", "login", "forgot_password", "soft_logout", "new", "settings", "welcome", "home", "start", "directory",
|
||||||
|
"start_sso", "start_cas", "groups", "complete_security", "post_registration", "room", "user", "group",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export async function getRedactedCurrentLocation(
|
||||||
|
origin: string,
|
||||||
|
hash: string,
|
||||||
|
pathname: string,
|
||||||
|
anonymity: Anonymity,
|
||||||
|
): Promise<string> {
|
||||||
|
// Redact PII from the current location.
|
||||||
|
// If anonymous is true, redact entirely, if false, substitute it with a hash.
|
||||||
|
// For known screens, assumes a URL structure of /<screen name>/might/be/pii
|
||||||
|
if (origin.startsWith('file://')) {
|
||||||
|
pathname = "/<redacted_file_scheme_url>/";
|
||||||
|
}
|
||||||
|
|
||||||
|
let hashStr;
|
||||||
|
if (hash == "") {
|
||||||
|
hashStr = "";
|
||||||
|
} else {
|
||||||
|
let [beforeFirstSlash, screen, ...parts] = hash.split("/");
|
||||||
|
|
||||||
|
if (!whitelistedScreens.has(screen)) {
|
||||||
|
screen = "<redacted_screen_name>";
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < parts.length; i++) {
|
||||||
|
parts[i] = anonymity === Anonymity.Anonymous ? `<redacted>` : await hashHex(parts[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
hashStr = `${beforeFirstSlash}/${screen}/${parts.join("/")}`;
|
||||||
|
}
|
||||||
|
return origin + pathname + hashStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PlatformProperties {
|
||||||
|
appVersion: string;
|
||||||
|
appPlatform: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PosthogAnalytics {
|
||||||
|
/* Wrapper for Posthog analytics.
|
||||||
|
* 3 modes of anonymity are supported, governed by this.anonymity
|
||||||
|
* - Anonymity.Disabled means *no data* is passed to posthog
|
||||||
|
* - Anonymity.Anonymous means all identifers will be redacted before being passed to posthog
|
||||||
|
* - Anonymity.Pseudonymous means all identifiers will be hashed via SHA-256 before being passed
|
||||||
|
* to Posthog
|
||||||
|
*
|
||||||
|
* To update anonymity, call updateAnonymityFromSettings() or you can set it directly via setAnonymity().
|
||||||
|
*
|
||||||
|
* To pass an event to Posthog:
|
||||||
|
*
|
||||||
|
* 1. Declare a type for the event, extending IAnonymousEvent, IPseudonymousEvent or IRoomEvent.
|
||||||
|
* 2. Call the appropriate track*() method. Pseudonymous events will be dropped when anonymity is
|
||||||
|
* Anonymous or Disabled; Anonymous events will be dropped when anonymity is Disabled.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private anonymity = Anonymity.Disabled;
|
||||||
|
// set true during the constructor if posthog config is present, otherwise false
|
||||||
|
private enabled = false;
|
||||||
|
private static _instance = null;
|
||||||
|
private platformSuperProperties = {};
|
||||||
|
|
||||||
|
public static get instance(): PosthogAnalytics {
|
||||||
|
if (!this._instance) {
|
||||||
|
this._instance = new PosthogAnalytics(posthog);
|
||||||
|
}
|
||||||
|
return this._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private readonly posthog: PostHog) {
|
||||||
|
const posthogConfig = SdkConfig.get()["posthog"];
|
||||||
|
if (posthogConfig) {
|
||||||
|
this.posthog.init(posthogConfig.projectApiKey, {
|
||||||
|
api_host: posthogConfig.apiHost,
|
||||||
|
autocapture: false,
|
||||||
|
mask_all_text: true,
|
||||||
|
mask_all_element_attributes: true,
|
||||||
|
// This only triggers on page load, which for our SPA isn't particularly useful.
|
||||||
|
// Plus, the .capture call originating from somewhere in posthog makes it hard
|
||||||
|
// to redact URLs, which requires async code.
|
||||||
|
//
|
||||||
|
// To raise this manually, just call .capture("$pageview") or posthog.capture_pageview.
|
||||||
|
capture_pageview: false,
|
||||||
|
sanitize_properties: this.sanitizeProperties,
|
||||||
|
respect_dnt: true,
|
||||||
|
});
|
||||||
|
this.enabled = true;
|
||||||
|
} else {
|
||||||
|
this.enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sanitizeProperties = (properties: posthog.Properties): posthog.Properties => {
|
||||||
|
// Callback from posthog to sanitize properties before sending them to the server.
|
||||||
|
//
|
||||||
|
// Here we sanitize posthog's built in properties which leak PII e.g. url reporting.
|
||||||
|
// See utils.js _.info.properties in posthog-js.
|
||||||
|
|
||||||
|
// Replace the $current_url with a redacted version.
|
||||||
|
// $redacted_current_url is injected by this class earlier in capture(), as its generation
|
||||||
|
// is async and can't be done in this non-async callback.
|
||||||
|
if (!properties['$redacted_current_url']) {
|
||||||
|
console.log("$redacted_current_url not set in sanitizeProperties, will drop $current_url entirely");
|
||||||
|
}
|
||||||
|
properties['$current_url'] = properties['$redacted_current_url'];
|
||||||
|
delete properties['$redacted_current_url'];
|
||||||
|
|
||||||
|
if (this.anonymity == Anonymity.Anonymous) {
|
||||||
|
// drop referrer information for anonymous users
|
||||||
|
properties['$referrer'] = null;
|
||||||
|
properties['$referring_domain'] = null;
|
||||||
|
properties['$initial_referrer'] = null;
|
||||||
|
properties['$initial_referring_domain'] = null;
|
||||||
|
|
||||||
|
// drop device ID, which is a UUID persisted in local storage
|
||||||
|
properties['$device_id'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
};
|
||||||
|
|
||||||
|
private static getAnonymityFromSettings(): Anonymity {
|
||||||
|
// determine the current anonymity level based on current user settings
|
||||||
|
|
||||||
|
// "Send anonymous usage data which helps us improve Element. This will use a cookie."
|
||||||
|
const analyticsOptIn = SettingsStore.getValue("analyticsOptIn", null, true);
|
||||||
|
|
||||||
|
// (proposed wording) "Send pseudonymous usage data which helps us improve Element. This will use a cookie."
|
||||||
|
//
|
||||||
|
// TODO: Currently, this is only a labs flag, for testing purposes.
|
||||||
|
const pseudonumousOptIn = SettingsStore.getValue("feature_pseudonymous_analytics_opt_in", null, true);
|
||||||
|
|
||||||
|
let anonymity;
|
||||||
|
if (pseudonumousOptIn) {
|
||||||
|
anonymity = Anonymity.Pseudonymous;
|
||||||
|
} else if (analyticsOptIn) {
|
||||||
|
anonymity = Anonymity.Anonymous;
|
||||||
|
} else {
|
||||||
|
anonymity = Anonymity.Disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
return anonymity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerSuperProperties(properties: posthog.Properties) {
|
||||||
|
if (this.enabled) {
|
||||||
|
this.posthog.register(properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async getPlatformProperties(): Promise<PlatformProperties> {
|
||||||
|
const platform = PlatformPeg.get();
|
||||||
|
let appVersion;
|
||||||
|
try {
|
||||||
|
appVersion = await platform.getAppVersion();
|
||||||
|
} catch (e) {
|
||||||
|
// this happens if no version is set i.e. in dev
|
||||||
|
appVersion = "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
appVersion,
|
||||||
|
appPlatform: platform.getHumanReadableName(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async capture(eventName: string, properties: posthog.Properties) {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { origin, hash, pathname } = window.location;
|
||||||
|
properties['$redacted_current_url'] = await getRedactedCurrentLocation(
|
||||||
|
origin, hash, pathname, this.anonymity);
|
||||||
|
this.posthog.capture(eventName, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public isEnabled(): boolean {
|
||||||
|
return this.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setAnonymity(anonymity: Anonymity): void {
|
||||||
|
// Update this.anonymity.
|
||||||
|
// This is public for testing purposes, typically you want to call updateAnonymityFromSettings
|
||||||
|
// to ensure this value is in step with the user's settings.
|
||||||
|
if (this.enabled && (anonymity == Anonymity.Disabled || anonymity == Anonymity.Anonymous)) {
|
||||||
|
// when transitioning to Disabled or Anonymous ensure we clear out any prior state
|
||||||
|
// set in posthog e.g. distinct ID
|
||||||
|
this.posthog.reset();
|
||||||
|
// Restore any previously set platform super properties
|
||||||
|
this.registerSuperProperties(this.platformSuperProperties);
|
||||||
|
}
|
||||||
|
this.anonymity = anonymity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async identifyUser(userId: string): Promise<void> {
|
||||||
|
if (this.anonymity == Anonymity.Pseudonymous) {
|
||||||
|
this.posthog.identify(await hashHex(userId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAnonymity(): Anonymity {
|
||||||
|
return this.anonymity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public logout(): void {
|
||||||
|
if (this.enabled) {
|
||||||
|
this.posthog.reset();
|
||||||
|
}
|
||||||
|
this.setAnonymity(Anonymity.Anonymous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async trackPseudonymousEvent<E extends IPseudonymousEvent>(
|
||||||
|
eventName: E["eventName"],
|
||||||
|
properties: E["properties"] = {},
|
||||||
|
) {
|
||||||
|
if (this.anonymity == Anonymity.Anonymous || this.anonymity == Anonymity.Disabled) return;
|
||||||
|
await this.capture(eventName, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async trackAnonymousEvent<E extends IAnonymousEvent>(
|
||||||
|
eventName: E["eventName"],
|
||||||
|
properties: E["properties"] = {},
|
||||||
|
): Promise<void> {
|
||||||
|
if (this.anonymity == Anonymity.Disabled) return;
|
||||||
|
await this.capture(eventName, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async trackRoomEvent<E extends IRoomEvent>(
|
||||||
|
eventName: E["eventName"],
|
||||||
|
roomId: string,
|
||||||
|
properties: Omit<E["properties"], "roomId">,
|
||||||
|
): Promise<void> {
|
||||||
|
const updatedProperties = {
|
||||||
|
...properties,
|
||||||
|
hashedRoomId: roomId ? await hashHex(roomId) : null,
|
||||||
|
};
|
||||||
|
await this.trackPseudonymousEvent(eventName, updatedProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async trackPageView(durationMs: number): Promise<void> {
|
||||||
|
const hash = window.location.hash;
|
||||||
|
|
||||||
|
let screen = null;
|
||||||
|
const split = hash.split("/");
|
||||||
|
if (split.length >= 2) {
|
||||||
|
screen = split[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.trackAnonymousEvent<IPageView>("$pageview", {
|
||||||
|
durationMs,
|
||||||
|
screen,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updatePlatformSuperProperties(): Promise<void> {
|
||||||
|
// Update super properties in posthog with our platform (app version, platform).
|
||||||
|
// These properties will be subsequently passed in every event.
|
||||||
|
//
|
||||||
|
// This only needs to be done once per page lifetime. Note that getPlatformProperties
|
||||||
|
// is async and can involve a network request if we are running in a browser.
|
||||||
|
this.platformSuperProperties = await PosthogAnalytics.getPlatformProperties();
|
||||||
|
this.registerSuperProperties(this.platformSuperProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateAnonymityFromSettings(userId?: string): Promise<void> {
|
||||||
|
// Update this.anonymity based on the user's analytics opt-in settings
|
||||||
|
// Identify the user (via hashed user ID) to posthog if anonymity is pseudonmyous
|
||||||
|
this.setAnonymity(PosthogAnalytics.getAnonymityFromSettings());
|
||||||
|
if (userId && this.getAnonymity() == Anonymity.Pseudonymous) {
|
||||||
|
await this.identifyUser(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -163,7 +163,7 @@ const shortcuts: Record<Categories, IShortcut[]> = {
|
||||||
modifiers: [Modifiers.SHIFT],
|
modifiers: [Modifiers.SHIFT],
|
||||||
key: Key.PAGE_UP,
|
key: Key.PAGE_UP,
|
||||||
}],
|
}],
|
||||||
description: _td("Jump to oldest unread message"),
|
description: _td("Jump to oldest unread message"),
|
||||||
}, {
|
}, {
|
||||||
keybinds: [{
|
keybinds: [{
|
||||||
modifiers: [CMD_OR_CTRL, Modifiers.SHIFT],
|
modifiers: [CMD_OR_CTRL, Modifiers.SHIFT],
|
||||||
|
|
|
@ -38,17 +38,9 @@ function makePlaybackWaveform(input: number[]): number[] {
|
||||||
// First, convert negative amplitudes to positive so we don't detect zero as "noisy".
|
// First, convert negative amplitudes to positive so we don't detect zero as "noisy".
|
||||||
const noiseWaveform = input.map(v => Math.abs(v));
|
const noiseWaveform = input.map(v => Math.abs(v));
|
||||||
|
|
||||||
// Next, we'll resample the waveform using a smoothing approach so we can keep the same rough shape.
|
// Then, we'll resample the waveform using a smoothing approach so we can keep the same rough shape.
|
||||||
// We also rescale the waveform to be 0-1 for the remaining function logic.
|
// We also rescale the waveform to be 0-1 so we end up with a clamped waveform to rely upon.
|
||||||
const resampled = arrayRescale(arraySmoothingResample(noiseWaveform, PLAYBACK_WAVEFORM_SAMPLES), 0, 1);
|
return arrayRescale(arraySmoothingResample(noiseWaveform, PLAYBACK_WAVEFORM_SAMPLES), 0, 1);
|
||||||
|
|
||||||
// Then, we'll do a high and low pass filter to isolate actual speaking volumes within the rescaled
|
|
||||||
// waveform. Most speech happens below the 0.5 mark.
|
|
||||||
const filtered = resampled.map(v => clamp(v, 0.1, 0.5));
|
|
||||||
|
|
||||||
// Finally, we'll rescale the filtered waveform (0.1-0.5 becomes 0-1 again) so the user sees something
|
|
||||||
// sensible. This is what we return to keep our contract of "values between zero and one".
|
|
||||||
return arrayRescale(filtered, 0, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Playback extends EventEmitter implements IDestroyable {
|
export class Playback extends EventEmitter implements IDestroyable {
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { IEncryptedFile } from "matrix-js-sdk/src/@types/event";
|
||||||
import { uploadFile } from "../ContentMessages";
|
import { uploadFile } from "../ContentMessages";
|
||||||
import { FixedRollingArray } from "../utils/FixedRollingArray";
|
import { FixedRollingArray } from "../utils/FixedRollingArray";
|
||||||
import { clamp } from "../utils/numbers";
|
import { clamp } from "../utils/numbers";
|
||||||
|
import mxRecorderWorkletPath from "./RecorderWorklet";
|
||||||
|
|
||||||
const CHANNELS = 1; // stereo isn't important
|
const CHANNELS = 1; // stereo isn't important
|
||||||
export const SAMPLE_RATE = 48000; // 48khz is what WebRTC uses. 12khz is where we lose quality.
|
export const SAMPLE_RATE = 48000; // 48khz is what WebRTC uses. 12khz is where we lose quality.
|
||||||
|
@ -113,16 +114,10 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
|
||||||
});
|
});
|
||||||
this.recorderSource = this.recorderContext.createMediaStreamSource(this.recorderStream);
|
this.recorderSource = this.recorderContext.createMediaStreamSource(this.recorderStream);
|
||||||
|
|
||||||
// Set up our worklet. We use this for timing information and waveform analysis: the
|
|
||||||
// web audio API prefers this be done async to avoid holding the main thread with math.
|
|
||||||
const mxRecorderWorkletPath = document.body.dataset.vectorRecorderWorkletScript;
|
|
||||||
if (!mxRecorderWorkletPath) {
|
|
||||||
// noinspection ExceptionCaughtLocallyJS
|
|
||||||
throw new Error("Unable to create recorder: no worklet script registered");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect our inputs and outputs
|
// Connect our inputs and outputs
|
||||||
if (this.recorderContext.audioWorklet) {
|
if (this.recorderContext.audioWorklet) {
|
||||||
|
// Set up our worklet. We use this for timing information and waveform analysis: the
|
||||||
|
// web audio API prefers this be done async to avoid holding the main thread with math.
|
||||||
await this.recorderContext.audioWorklet.addModule(mxRecorderWorkletPath);
|
await this.recorderContext.audioWorklet.addModule(mxRecorderWorkletPath);
|
||||||
this.recorderWorklet = new AudioWorkletNode(this.recorderContext, WORKLET_NAME);
|
this.recorderWorklet = new AudioWorkletNode(this.recorderContext, WORKLET_NAME);
|
||||||
this.recorderSource.connect(this.recorderWorklet);
|
this.recorderSource.connect(this.recorderWorklet);
|
||||||
|
|
|
@ -107,6 +107,7 @@ import UIStore, { UI_EVENTS } from "../../stores/UIStore";
|
||||||
import SoftLogout from './auth/SoftLogout';
|
import SoftLogout from './auth/SoftLogout';
|
||||||
import { makeRoomPermalink } from "../../utils/permalinks/Permalinks";
|
import { makeRoomPermalink } from "../../utils/permalinks/Permalinks";
|
||||||
import { copyPlaintext } from "../../utils/strings";
|
import { copyPlaintext } from "../../utils/strings";
|
||||||
|
import { PosthogAnalytics } from '../../PosthogAnalytics';
|
||||||
|
|
||||||
/** constants for MatrixChat.state.view */
|
/** constants for MatrixChat.state.view */
|
||||||
export enum Views {
|
export enum Views {
|
||||||
|
@ -387,6 +388,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
if (SettingsStore.getValue("analyticsOptIn")) {
|
if (SettingsStore.getValue("analyticsOptIn")) {
|
||||||
Analytics.enable();
|
Analytics.enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PosthogAnalytics.instance.updateAnonymityFromSettings();
|
||||||
|
PosthogAnalytics.instance.updatePlatformSuperProperties();
|
||||||
|
|
||||||
CountlyAnalytics.instance.enable(/* anonymous = */ true);
|
CountlyAnalytics.instance.enable(/* anonymous = */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,6 +448,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
const durationMs = this.stopPageChangeTimer();
|
const durationMs = this.stopPageChangeTimer();
|
||||||
Analytics.trackPageChange(durationMs);
|
Analytics.trackPageChange(durationMs);
|
||||||
CountlyAnalytics.instance.trackPageChange(durationMs);
|
CountlyAnalytics.instance.trackPageChange(durationMs);
|
||||||
|
PosthogAnalytics.instance.trackPageView(durationMs);
|
||||||
}
|
}
|
||||||
if (this.focusComposer) {
|
if (this.focusComposer) {
|
||||||
dis.fire(Action.FocusSendMessageComposer);
|
dis.fire(Action.FocusSendMessageComposer);
|
||||||
|
|
|
@ -192,11 +192,11 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
||||||
|
|
||||||
if (inviteSender) {
|
if (inviteSender) {
|
||||||
inviterSection = <div className="mx_SpaceRoomView_preview_inviter">
|
inviterSection = <div className="mx_SpaceRoomView_preview_inviter">
|
||||||
<MemberAvatar member={inviter} width={32} height={32} />
|
<MemberAvatar member={inviter} fallbackUserId={inviteSender} width={32} height={32} />
|
||||||
<div>
|
<div>
|
||||||
<div className="mx_SpaceRoomView_preview_inviter_name">
|
<div className="mx_SpaceRoomView_preview_inviter_name">
|
||||||
{ _t("<inviter/> invites you", {}, {
|
{ _t("<inviter/> invites you", {}, {
|
||||||
inviter: () => <b>{ inviter.name || inviteSender }</b>,
|
inviter: () => <b>{ inviter?.name || inviteSender }</b>,
|
||||||
}) }
|
}) }
|
||||||
</div>
|
</div>
|
||||||
{ inviter ? <div className="mx_SpaceRoomView_preview_inviter_mxid">
|
{ inviter ? <div className="mx_SpaceRoomView_preview_inviter_mxid">
|
||||||
|
|
|
@ -58,28 +58,39 @@ export default class ToastContainer extends React.Component<{}, IState> {
|
||||||
let containerClasses;
|
let containerClasses;
|
||||||
if (totalCount !== 0) {
|
if (totalCount !== 0) {
|
||||||
const topToast = this.state.toasts[0];
|
const topToast = this.state.toasts[0];
|
||||||
const { title, icon, key, component, className, props } = topToast;
|
const { title, icon, key, component, className, bodyClassName, props } = topToast;
|
||||||
const toastClasses = classNames("mx_Toast_toast", {
|
const bodyClasses = classNames("mx_Toast_body", bodyClassName);
|
||||||
|
const toastClasses = classNames("mx_Toast_toast", className, {
|
||||||
"mx_Toast_hasIcon": icon,
|
"mx_Toast_hasIcon": icon,
|
||||||
[`mx_Toast_icon_${icon}`]: icon,
|
[`mx_Toast_icon_${icon}`]: icon,
|
||||||
}, className);
|
});
|
||||||
|
|
||||||
let countIndicator;
|
|
||||||
if (isStacked || this.state.countSeen > 0) {
|
|
||||||
countIndicator = ` (${this.state.countSeen + 1}/${this.state.countSeen + totalCount})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const toastProps = Object.assign({}, props, {
|
const toastProps = Object.assign({}, props, {
|
||||||
key,
|
key,
|
||||||
toastKey: key,
|
toastKey: key,
|
||||||
});
|
});
|
||||||
toast = (<div className={toastClasses}>
|
const content = React.createElement(component, toastProps);
|
||||||
<div className="mx_Toast_title">
|
|
||||||
<h2>{ title }</h2>
|
let countIndicator;
|
||||||
<span>{ countIndicator }</span>
|
if (title && isStacked || this.state.countSeen > 0) {
|
||||||
|
countIndicator = ` (${this.state.countSeen + 1}/${this.state.countSeen + totalCount})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let titleElement;
|
||||||
|
if (title) {
|
||||||
|
titleElement = (
|
||||||
|
<div className="mx_Toast_title">
|
||||||
|
<h2>{ title }</h2>
|
||||||
|
<span>{ countIndicator }</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toast = (
|
||||||
|
<div className={toastClasses}>
|
||||||
|
{ titleElement }
|
||||||
|
<div className={bodyClasses}>{ content }</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Toast_body">{ React.createElement(component, toastProps) }</div>
|
);
|
||||||
</div>);
|
|
||||||
|
|
||||||
containerClasses = classNames("mx_ToastContainer", {
|
containerClasses = classNames("mx_ToastContainer", {
|
||||||
"mx_ToastContainer_stacked": isStacked,
|
"mx_ToastContainer_stacked": isStacked,
|
||||||
|
|
|
@ -17,8 +17,7 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording } from "../../../audio/VoiceRecording";
|
import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording } from "../../../audio/VoiceRecording";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { arrayFastResample } from "../../../utils/arrays";
|
import { arrayFastResample, arraySeed } from "../../../utils/arrays";
|
||||||
import { percentageOf } from "../../../utils/numbers";
|
|
||||||
import Waveform from "./Waveform";
|
import Waveform from "./Waveform";
|
||||||
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
||||||
|
|
||||||
|
@ -48,18 +47,14 @@ export default class LiveRecordingWaveform extends React.PureComponent<IProps, I
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
waveform: [],
|
waveform: arraySeed(0, RECORDING_PLAYBACK_SAMPLES),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.recorder.liveData.onUpdate((update: IRecordingUpdate) => {
|
this.props.recorder.liveData.onUpdate((update: IRecordingUpdate) => {
|
||||||
const bars = arrayFastResample(Array.from(update.waveform), RECORDING_PLAYBACK_SAMPLES);
|
// The incoming data is between zero and one, so we don't need to clamp/rescale it.
|
||||||
// The incoming data is between zero and one, but typically even screaming into a
|
this.waveform = arrayFastResample(Array.from(update.waveform), RECORDING_PLAYBACK_SAMPLES);
|
||||||
// microphone won't send you over 0.6, so we artificially adjust the gain for the
|
|
||||||
// waveform. This results in a slightly more cinematic/animated waveform for the
|
|
||||||
// user.
|
|
||||||
this.waveform = bars.map(b => percentageOf(b, 0, 0.50));
|
|
||||||
this.scheduledUpdate.mark();
|
this.scheduledUpdate.mark();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -490,9 +490,9 @@ class RoomStateExplorer extends React.PureComponent<IExplorerProps, IRoomStateEx
|
||||||
forceStateEvent={true}
|
forceStateEvent={true}
|
||||||
onBack={this.onBack}
|
onBack={this.onBack}
|
||||||
inputs={{
|
inputs={{
|
||||||
eventType: this.state.event.getType(),
|
eventType: this.state.event.getType(),
|
||||||
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
||||||
stateKey: this.state.event.getStateKey(),
|
stateKey: this.state.event.getStateKey(),
|
||||||
}}
|
}}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,10 @@ import { _t } from '../../../languageHandler';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
|
|
||||||
export default function KeySignatureUploadFailedDialog({
|
export default function KeySignatureUploadFailedDialog({
|
||||||
failures,
|
failures,
|
||||||
source,
|
source,
|
||||||
continuation,
|
continuation,
|
||||||
onFinished,
|
onFinished,
|
||||||
}) {
|
}) {
|
||||||
const RETRIES = 2;
|
const RETRIES = 2;
|
||||||
const BaseDialog = sdk.getComponent('dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('dialogs.BaseDialog');
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
|
@ -24,19 +25,25 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
import RestoreKeyBackupDialog from './security/RestoreKeyBackupDialog';
|
import RestoreKeyBackupDialog from './security/RestoreKeyBackupDialog';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
onFinished: (success: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
shouldLoadBackupStatus: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
backupInfo: IKeyBackupInfo;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.dialogs.LogoutDialog")
|
@replaceableComponent("views.dialogs.LogoutDialog")
|
||||||
export default class LogoutDialog extends React.Component {
|
export default class LogoutDialog extends React.Component<IProps, IState> {
|
||||||
defaultProps = {
|
static defaultProps = {
|
||||||
onFinished: function() {},
|
onFinished: function() {},
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor(props) {
|
||||||
super();
|
super(props);
|
||||||
this._onSettingsLinkClick = this._onSettingsLinkClick.bind(this);
|
|
||||||
this._onExportE2eKeysClicked = this._onExportE2eKeysClicked.bind(this);
|
|
||||||
this._onFinished = this._onFinished.bind(this);
|
|
||||||
this._onSetRecoveryMethodClick = this._onSetRecoveryMethodClick.bind(this);
|
|
||||||
this._onLogoutConfirm = this._onLogoutConfirm.bind(this);
|
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const shouldLoadBackupStatus = cli.isCryptoEnabled() && !cli.getKeyBackupEnabled();
|
const shouldLoadBackupStatus = cli.isCryptoEnabled() && !cli.getKeyBackupEnabled();
|
||||||
|
@ -49,11 +56,11 @@ export default class LogoutDialog extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (shouldLoadBackupStatus) {
|
if (shouldLoadBackupStatus) {
|
||||||
this._loadBackupStatus();
|
this.loadBackupStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _loadBackupStatus() {
|
private async loadBackupStatus() {
|
||||||
try {
|
try {
|
||||||
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -69,29 +76,29 @@ export default class LogoutDialog extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSettingsLinkClick() {
|
private onSettingsLinkClick = (): void => {
|
||||||
// close dialog
|
// close dialog
|
||||||
this.props.onFinished();
|
this.props.onFinished(true);
|
||||||
}
|
};
|
||||||
|
|
||||||
_onExportE2eKeysClicked() {
|
private onExportE2eKeysClicked = (): void => {
|
||||||
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
||||||
import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'),
|
import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'),
|
||||||
{
|
{
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
_onFinished(confirmed) {
|
private onFinished = (confirmed: boolean): void => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
dis.dispatch({ action: 'logout' });
|
dis.dispatch({ action: 'logout' });
|
||||||
}
|
}
|
||||||
// close dialog
|
// close dialog
|
||||||
this.props.onFinished();
|
this.props.onFinished(confirmed);
|
||||||
}
|
};
|
||||||
|
|
||||||
_onSetRecoveryMethodClick() {
|
private onSetRecoveryMethodClick = (): void => {
|
||||||
if (this.state.backupInfo) {
|
if (this.state.backupInfo) {
|
||||||
// A key backup exists for this account, but the creating device is not
|
// A key backup exists for this account, but the creating device is not
|
||||||
// verified, so restore the backup which will give us the keys from it and
|
// verified, so restore the backup which will give us the keys from it and
|
||||||
|
@ -108,15 +115,15 @@ export default class LogoutDialog extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// close dialog
|
// close dialog
|
||||||
this.props.onFinished();
|
this.props.onFinished(true);
|
||||||
}
|
};
|
||||||
|
|
||||||
_onLogoutConfirm() {
|
private onLogoutConfirm = (): void => {
|
||||||
dis.dispatch({ action: 'logout' });
|
dis.dispatch({ action: 'logout' });
|
||||||
|
|
||||||
// close dialog
|
// close dialog
|
||||||
this.props.onFinished();
|
this.props.onFinished(true);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.shouldLoadBackupStatus) {
|
if (this.state.shouldLoadBackupStatus) {
|
||||||
|
@ -152,16 +159,16 @@ export default class LogoutDialog extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<DialogButtons primaryButton={setupButtonCaption}
|
<DialogButtons primaryButton={setupButtonCaption}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
onPrimaryButtonClick={this._onSetRecoveryMethodClick}
|
onPrimaryButtonClick={this.onSetRecoveryMethodClick}
|
||||||
focus={true}
|
focus={true}
|
||||||
>
|
>
|
||||||
<button onClick={this._onLogoutConfirm}>
|
<button onClick={this.onLogoutConfirm}>
|
||||||
{ _t("I don't want my encrypted messages") }
|
{ _t("I don't want my encrypted messages") }
|
||||||
</button>
|
</button>
|
||||||
</DialogButtons>
|
</DialogButtons>
|
||||||
<details>
|
<details>
|
||||||
<summary>{ _t("Advanced") }</summary>
|
<summary>{ _t("Advanced") }</summary>
|
||||||
<p><button onClick={this._onExportE2eKeysClicked}>
|
<p><button onClick={this.onExportE2eKeysClicked}>
|
||||||
{ _t("Manually export keys") }
|
{ _t("Manually export keys") }
|
||||||
</button></p>
|
</button></p>
|
||||||
</details>
|
</details>
|
||||||
|
@ -174,7 +181,7 @@ export default class LogoutDialog extends React.Component {
|
||||||
title={_t("You'll lose access to your encrypted messages")}
|
title={_t("You'll lose access to your encrypted messages")}
|
||||||
contentId='mx_Dialog_content'
|
contentId='mx_Dialog_content'
|
||||||
hasCancel={true}
|
hasCancel={true}
|
||||||
onFinished={this._onFinished}
|
onFinished={this.onFinished}
|
||||||
>
|
>
|
||||||
{ dialogContent }
|
{ dialogContent }
|
||||||
</BaseDialog>);
|
</BaseDialog>);
|
||||||
|
@ -187,7 +194,7 @@ export default class LogoutDialog extends React.Component {
|
||||||
"Are you sure you want to sign out?",
|
"Are you sure you want to sign out?",
|
||||||
)}
|
)}
|
||||||
button={_t("Sign out")}
|
button={_t("Sign out")}
|
||||||
onFinished={this._onFinished}
|
onFinished={this.onFinished}
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -25,7 +25,6 @@ import { CallErrorCode, CallState } from 'matrix-js-sdk/src/webrtc/call';
|
||||||
import InfoTooltip, { InfoTooltipKind } from '../elements/InfoTooltip';
|
import InfoTooltip, { InfoTooltipKind } from '../elements/InfoTooltip';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
|
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
|
||||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
mxEvent: MatrixEvent;
|
mxEvent: MatrixEvent;
|
||||||
|
@ -117,14 +116,12 @@ export default class CallEvent extends React.Component<IProps, IState> {
|
||||||
if (state === CallState.Ended) {
|
if (state === CallState.Ended) {
|
||||||
const hangupReason = this.props.callEventGrouper.hangupReason;
|
const hangupReason = this.props.callEventGrouper.hangupReason;
|
||||||
const gotRejected = this.props.callEventGrouper.gotRejected;
|
const gotRejected = this.props.callEventGrouper.gotRejected;
|
||||||
const rejectParty = this.props.callEventGrouper.rejectParty;
|
|
||||||
|
|
||||||
if (gotRejected) {
|
if (gotRejected) {
|
||||||
const weDeclinedCall = MatrixClientPeg.get().getUserId() === rejectParty;
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_CallEvent_content">
|
<div className="mx_CallEvent_content">
|
||||||
{ weDeclinedCall ? _t("You declined this call") : _t("They declined this call") }
|
{ _t("Call declined") }
|
||||||
{ this.renderCallBackButton(weDeclinedCall ? _t("Call back") : _t("Call again")) }
|
{ this.renderCallBackButton(_t("Call back")) }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (([CallErrorCode.UserHangup, "user hangup"].includes(hangupReason) || !hangupReason)) {
|
} else if (([CallErrorCode.UserHangup, "user hangup"].includes(hangupReason) || !hangupReason)) {
|
||||||
|
@ -136,14 +133,14 @@ export default class CallEvent extends React.Component<IProps, IState> {
|
||||||
// Also, if we don't have a reason
|
// Also, if we don't have a reason
|
||||||
return (
|
return (
|
||||||
<div className="mx_CallEvent_content">
|
<div className="mx_CallEvent_content">
|
||||||
{ _t("This call has ended") }
|
{ _t("Call ended") }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (hangupReason === CallErrorCode.InviteTimeout) {
|
} else if (hangupReason === CallErrorCode.InviteTimeout) {
|
||||||
return (
|
return (
|
||||||
<div className="mx_CallEvent_content">
|
<div className="mx_CallEvent_content">
|
||||||
{ _t("They didn't pick up") }
|
{ _t("Missed call") }
|
||||||
{ this.renderCallBackButton(_t("Call again")) }
|
{ this.renderCallBackButton(_t("Call back")) }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -176,7 +173,8 @@ export default class CallEvent extends React.Component<IProps, IState> {
|
||||||
className="mx_CallEvent_content_tooltip"
|
className="mx_CallEvent_content_tooltip"
|
||||||
kind={InfoTooltipKind.Warning}
|
kind={InfoTooltipKind.Warning}
|
||||||
/>
|
/>
|
||||||
{ _t("This call has failed") }
|
{ _t("Connection failed") }
|
||||||
|
{ this.renderCallBackButton(_t("Retry")) }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -190,7 +188,7 @@ export default class CallEvent extends React.Component<IProps, IState> {
|
||||||
if (state === CustomCallState.Missed) {
|
if (state === CustomCallState.Missed) {
|
||||||
return (
|
return (
|
||||||
<div className="mx_CallEvent_content">
|
<div className="mx_CallEvent_content">
|
||||||
{ _t("You missed this call") }
|
{ _t("Missed call") }
|
||||||
{ this.renderCallBackButton(_t("Call back")) }
|
{ this.renderCallBackButton(_t("Call back")) }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -152,7 +152,7 @@ const PinnedMessagesCard = ({ room, onClose }: IProps) => {
|
||||||
<h2>{ _t("Nothing pinned, yet") }</h2>
|
<h2>{ _t("Nothing pinned, yet") }</h2>
|
||||||
{ _t("If you have permissions, open the menu on any message and select " +
|
{ _t("If you have permissions, open the menu on any message and select " +
|
||||||
"<b>Pin</b> to stick them here.", {}, {
|
"<b>Pin</b> to stick them here.", {}, {
|
||||||
b: sub => <b>{ sub }</b>,
|
b: sub => <b>{ sub }</b>,
|
||||||
}) }
|
}) }
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -1381,8 +1381,8 @@ const BasicUserInfo: React.FC<{
|
||||||
className="mx_UserInfo_field"
|
className="mx_UserInfo_field"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: Action.ViewUserSettings,
|
action: Action.ViewUserSettings,
|
||||||
initialTabId: UserTab.Security,
|
initialTabId: UserTab.Security,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -55,6 +55,14 @@ const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.sourc
|
||||||
|
|
||||||
const IS_MAC = navigator.platform.indexOf("Mac") !== -1;
|
const IS_MAC = navigator.platform.indexOf("Mac") !== -1;
|
||||||
|
|
||||||
|
const SURROUND_WITH_CHARACTERS = ["\"", "_", "`", "'", "*", "~", "$"];
|
||||||
|
const SURROUND_WITH_DOUBLE_CHARACTERS = new Map([
|
||||||
|
["(", ")"],
|
||||||
|
["[", "]"],
|
||||||
|
["{", "}"],
|
||||||
|
["<", ">"],
|
||||||
|
]);
|
||||||
|
|
||||||
function ctrlShortcutLabel(key: string): string {
|
function ctrlShortcutLabel(key: string): string {
|
||||||
return (IS_MAC ? "⌘" : "Ctrl") + "+" + key;
|
return (IS_MAC ? "⌘" : "Ctrl") + "+" + key;
|
||||||
}
|
}
|
||||||
|
@ -99,6 +107,7 @@ interface IState {
|
||||||
showVisualBell?: boolean;
|
showVisualBell?: boolean;
|
||||||
autoComplete?: AutocompleteWrapperModel;
|
autoComplete?: AutocompleteWrapperModel;
|
||||||
completionIndex?: number;
|
completionIndex?: number;
|
||||||
|
surroundWith: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.rooms.BasicMessageEditor")
|
@replaceableComponent("views.rooms.BasicMessageEditor")
|
||||||
|
@ -117,12 +126,14 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
|
||||||
|
|
||||||
private readonly emoticonSettingHandle: string;
|
private readonly emoticonSettingHandle: string;
|
||||||
private readonly shouldShowPillAvatarSettingHandle: string;
|
private readonly shouldShowPillAvatarSettingHandle: string;
|
||||||
|
private readonly surroundWithHandle: string;
|
||||||
private readonly historyManager = new HistoryManager();
|
private readonly historyManager = new HistoryManager();
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
showPillAvatar: SettingsStore.getValue("Pill.shouldShowPillAvatar"),
|
showPillAvatar: SettingsStore.getValue("Pill.shouldShowPillAvatar"),
|
||||||
|
surroundWith: SettingsStore.getValue("MessageComposerInput.surroundWith"),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.emoticonSettingHandle = SettingsStore.watchSetting('MessageComposerInput.autoReplaceEmoji', null,
|
this.emoticonSettingHandle = SettingsStore.watchSetting('MessageComposerInput.autoReplaceEmoji', null,
|
||||||
|
@ -130,6 +141,8 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
|
||||||
this.configureEmoticonAutoReplace();
|
this.configureEmoticonAutoReplace();
|
||||||
this.shouldShowPillAvatarSettingHandle = SettingsStore.watchSetting("Pill.shouldShowPillAvatar", null,
|
this.shouldShowPillAvatarSettingHandle = SettingsStore.watchSetting("Pill.shouldShowPillAvatar", null,
|
||||||
this.configureShouldShowPillAvatar);
|
this.configureShouldShowPillAvatar);
|
||||||
|
this.surroundWithHandle = SettingsStore.watchSetting("MessageComposerInput.surroundWith", null,
|
||||||
|
this.surroundWithSettingChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(prevProps: IProps) {
|
public componentDidUpdate(prevProps: IProps) {
|
||||||
|
@ -422,6 +435,28 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
|
||||||
private onKeyDown = (event: React.KeyboardEvent): void => {
|
private onKeyDown = (event: React.KeyboardEvent): void => {
|
||||||
const model = this.props.model;
|
const model = this.props.model;
|
||||||
let handled = false;
|
let handled = false;
|
||||||
|
|
||||||
|
if (this.state.surroundWith && document.getSelection().type != "Caret") {
|
||||||
|
// This surrounds the selected text with a character. This is
|
||||||
|
// intentionally left out of the keybinding manager as the keybinds
|
||||||
|
// here shouldn't be changeable
|
||||||
|
|
||||||
|
const selectionRange = getRangeForSelection(
|
||||||
|
this.editorRef.current,
|
||||||
|
this.props.model,
|
||||||
|
document.getSelection(),
|
||||||
|
);
|
||||||
|
// trim the range as we want it to exclude leading/trailing spaces
|
||||||
|
selectionRange.trim();
|
||||||
|
|
||||||
|
if ([...SURROUND_WITH_DOUBLE_CHARACTERS.keys(), ...SURROUND_WITH_CHARACTERS].includes(event.key)) {
|
||||||
|
this.historyManager.ensureLastChangesPushed(this.props.model);
|
||||||
|
this.modifiedFlag = true;
|
||||||
|
toggleInlineFormat(selectionRange, event.key, SURROUND_WITH_DOUBLE_CHARACTERS.get(event.key));
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const action = getKeyBindingsManager().getMessageComposerAction(event);
|
const action = getKeyBindingsManager().getMessageComposerAction(event);
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case MessageComposerAction.FormatBold:
|
case MessageComposerAction.FormatBold:
|
||||||
|
@ -574,6 +609,11 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
|
||||||
this.setState({ showPillAvatar });
|
this.setState({ showPillAvatar });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private surroundWithSettingChanged = () => {
|
||||||
|
const surroundWith = SettingsStore.getValue("MessageComposerInput.surroundWith");
|
||||||
|
this.setState({ surroundWith });
|
||||||
|
};
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
document.removeEventListener("selectionchange", this.onSelectionChange);
|
document.removeEventListener("selectionchange", this.onSelectionChange);
|
||||||
this.editorRef.current.removeEventListener("input", this.onInput, true);
|
this.editorRef.current.removeEventListener("input", this.onInput, true);
|
||||||
|
@ -581,6 +621,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
|
||||||
this.editorRef.current.removeEventListener("compositionend", this.onCompositionEnd, true);
|
this.editorRef.current.removeEventListener("compositionend", this.onCompositionEnd, true);
|
||||||
SettingsStore.unwatchSetting(this.emoticonSettingHandle);
|
SettingsStore.unwatchSetting(this.emoticonSettingHandle);
|
||||||
SettingsStore.unwatchSetting(this.shouldShowPillAvatarSettingHandle);
|
SettingsStore.unwatchSetting(this.shouldShowPillAvatarSettingHandle);
|
||||||
|
SettingsStore.unwatchSetting(this.surroundWithHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
|
|
@ -64,9 +64,9 @@ const NewRoomIntro = () => {
|
||||||
height={AVATAR_SIZE}
|
height={AVATAR_SIZE}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
defaultDispatcher.dispatch<ViewUserPayload>({
|
defaultDispatcher.dispatch<ViewUserPayload>({
|
||||||
action: Action.ViewUser,
|
action: Action.ViewUser,
|
||||||
// XXX: We should be using a real member object and not assuming what the receiver wants.
|
// XXX: We should be using a real member object and not assuming what the receiver wants.
|
||||||
member: member || { userId: dmPartner } as User,
|
member: member || { userId: dmPartner } as User,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -145,7 +145,7 @@ export default class ReadReceiptMarker extends React.PureComponent {
|
||||||
if (oldInfo && oldInfo.left) {
|
if (oldInfo && oldInfo.left) {
|
||||||
// start at the old height and in the old h pos
|
// start at the old height and in the old h pos
|
||||||
startStyles.push({ top: startTopOffset+"px",
|
startStyles.push({ top: startTopOffset+"px",
|
||||||
left: toPx(oldInfo.left) });
|
left: toPx(oldInfo.left) });
|
||||||
}
|
}
|
||||||
|
|
||||||
startStyles.push({ top: startTopOffset+'px', left: '0' });
|
startStyles.push({ top: startTopOffset+'px', left: '0' });
|
||||||
|
@ -174,14 +174,14 @@ export default class ReadReceiptMarker extends React.PureComponent {
|
||||||
title = _t(
|
title = _t(
|
||||||
"Seen by %(userName)s at %(dateTime)s",
|
"Seen by %(userName)s at %(dateTime)s",
|
||||||
{ userName: this.props.fallbackUserId,
|
{ userName: this.props.fallbackUserId,
|
||||||
dateTime: dateString },
|
dateTime: dateString },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
title = _t(
|
title = _t(
|
||||||
"Seen by %(displayName)s (%(userName)s) at %(dateTime)s",
|
"Seen by %(displayName)s (%(userName)s) at %(dateTime)s",
|
||||||
{ displayName: this.props.member.rawDisplayName,
|
{ displayName: this.props.member.rawDisplayName,
|
||||||
userName: this.props.fallbackUserId,
|
userName: this.props.fallbackUserId,
|
||||||
dateTime: dateString },
|
dateTime: dateString },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,7 @@ limitations under the License.
|
||||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from "react";
|
||||||
import {
|
import { IUpload, RecordingState, VoiceRecording } from "../../../audio/VoiceRecording";
|
||||||
RecordingState,
|
|
||||||
VoiceRecording,
|
|
||||||
} from "../../../audio/VoiceRecording";
|
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
@ -34,6 +31,11 @@ import { MsgType } from "matrix-js-sdk/src/@types/event";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||||
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../MediaDeviceHandler";
|
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../MediaDeviceHandler";
|
||||||
|
import NotificationBadge from "./NotificationBadge";
|
||||||
|
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
|
||||||
|
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
|
||||||
|
import InlineSpinner from "../elements/InlineSpinner";
|
||||||
|
import { PlaybackManager } from "../../../audio/PlaybackManager";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -42,6 +44,7 @@ interface IProps {
|
||||||
interface IState {
|
interface IState {
|
||||||
recorder?: VoiceRecording;
|
recorder?: VoiceRecording;
|
||||||
recordingPhase?: RecordingState;
|
recordingPhase?: RecordingState;
|
||||||
|
didUploadFail?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,9 +72,19 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
|
||||||
|
|
||||||
await this.state.recorder.stop();
|
await this.state.recorder.stop();
|
||||||
|
|
||||||
|
let upload: IUpload;
|
||||||
try {
|
try {
|
||||||
const upload = await this.state.recorder.upload(this.props.room.roomId);
|
upload = await this.state.recorder.upload(this.props.room.roomId);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error uploading voice message:", e);
|
||||||
|
|
||||||
|
// Flag error and move on. The recording phase will be reset by the upload function.
|
||||||
|
this.setState({ didUploadFail: true });
|
||||||
|
|
||||||
|
return; // don't dispose the recording: the user has a chance to re-upload
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
// noinspection ES6MissingAwait - we don't care if it fails, it'll get queued.
|
// noinspection ES6MissingAwait - we don't care if it fails, it'll get queued.
|
||||||
MatrixClientPeg.get().sendMessage(this.props.room.roomId, {
|
MatrixClientPeg.get().sendMessage(this.props.room.roomId, {
|
||||||
"body": "Voice message",
|
"body": "Voice message",
|
||||||
|
@ -104,12 +117,11 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
|
||||||
"org.matrix.msc3245.voice": {}, // No content, this is a rendering hint
|
"org.matrix.msc3245.voice": {}, // No content, this is a rendering hint
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error sending/uploading voice message:", e);
|
console.error("Error sending voice message:", e);
|
||||||
Modal.createTrackedDialog('Upload failed', '', ErrorDialog, {
|
|
||||||
title: _t('Upload Failed'),
|
// Voice message should be in the timeline at this point, so let other things take care
|
||||||
description: _t("The voice message failed to upload."),
|
// of error handling. We also shouldn't need the recording anymore, so fall through to
|
||||||
});
|
// disposal.
|
||||||
return; // don't dispose the recording so the user can retry, maybe
|
|
||||||
}
|
}
|
||||||
await this.disposeRecording();
|
await this.disposeRecording();
|
||||||
}
|
}
|
||||||
|
@ -118,7 +130,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
|
||||||
await VoiceRecordingStore.instance.disposeRecording();
|
await VoiceRecordingStore.instance.disposeRecording();
|
||||||
|
|
||||||
// Reset back to no recording, which means no phase (ie: restart component entirely)
|
// Reset back to no recording, which means no phase (ie: restart component entirely)
|
||||||
this.setState({ recorder: null, recordingPhase: null });
|
this.setState({ recorder: null, recordingPhase: null, didUploadFail: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
private onCancel = async () => {
|
private onCancel = async () => {
|
||||||
|
@ -166,6 +178,9 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// stop any noises which might be happening
|
||||||
|
await PlaybackManager.instance.playOnly(null);
|
||||||
|
|
||||||
const recorder = VoiceRecordingStore.instance.startRecording();
|
const recorder = VoiceRecordingStore.instance.startRecording();
|
||||||
await recorder.start();
|
await recorder.start();
|
||||||
|
|
||||||
|
@ -209,9 +224,9 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
|
||||||
'mx_VoiceRecordComposerTile_stop': this.state.recorder?.isRecording,
|
'mx_VoiceRecordComposerTile_stop': this.state.recorder?.isRecording,
|
||||||
});
|
});
|
||||||
|
|
||||||
let tooltip = _t("Record a voice message");
|
let tooltip = _t("Send voice message");
|
||||||
if (!!this.state.recorder) {
|
if (!!this.state.recorder) {
|
||||||
tooltip = _t("Stop the recording");
|
tooltip = _t("Stop recording");
|
||||||
}
|
}
|
||||||
|
|
||||||
let stopOrRecordBtn = <AccessibleTooltipButton
|
let stopOrRecordBtn = <AccessibleTooltipButton
|
||||||
|
@ -229,12 +244,30 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
|
||||||
if (this.state.recorder && this.state.recordingPhase !== RecordingState.Uploading) {
|
if (this.state.recorder && this.state.recordingPhase !== RecordingState.Uploading) {
|
||||||
deleteButton = <AccessibleTooltipButton
|
deleteButton = <AccessibleTooltipButton
|
||||||
className='mx_VoiceRecordComposerTile_delete'
|
className='mx_VoiceRecordComposerTile_delete'
|
||||||
title={_t("Delete recording")}
|
title={_t("Delete")}
|
||||||
onClick={this.onCancel}
|
onClick={this.onCancel}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let uploadIndicator;
|
||||||
|
if (this.state.recordingPhase === RecordingState.Uploading) {
|
||||||
|
uploadIndicator = <span className='mx_VoiceRecordComposerTile_uploadingState'>
|
||||||
|
<InlineSpinner w={16} h={16} />
|
||||||
|
</span>;
|
||||||
|
} else if (this.state.didUploadFail && this.state.recordingPhase === RecordingState.Ended) {
|
||||||
|
uploadIndicator = <span className='mx_VoiceRecordComposerTile_failedState'>
|
||||||
|
<span className='mx_VoiceRecordComposerTile_uploadState_badge'>
|
||||||
|
{ /* Need to stick the badge in a span to ensure it doesn't create a block component */ }
|
||||||
|
<NotificationBadge
|
||||||
|
notification={StaticNotificationState.forSymbol("!", NotificationColor.Red)}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span className='text-warning'>{ _t("Failed to send") }</span>
|
||||||
|
</span>;
|
||||||
|
}
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
|
{ uploadIndicator }
|
||||||
{ deleteButton }
|
{ deleteButton }
|
||||||
{ this.renderWaveformArea() }
|
{ this.renderWaveformArea() }
|
||||||
{ recordingInfo }
|
{ recordingInfo }
|
||||||
|
|
|
@ -61,6 +61,7 @@ export default class PreferencesUserSettingsTab extends React.Component<{}, ISta
|
||||||
'MessageComposerInput.suggestEmoji',
|
'MessageComposerInput.suggestEmoji',
|
||||||
'sendTypingNotifications',
|
'sendTypingNotifications',
|
||||||
'MessageComposerInput.ctrlEnterToSend',
|
'MessageComposerInput.ctrlEnterToSend',
|
||||||
|
'MessageComposerInput.surroundWith',
|
||||||
'MessageComposerInput.showStickersButton',
|
'MessageComposerInput.showStickersButton',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ import { UIFeature } from "../../../../../settings/UIFeature";
|
||||||
import { isE2eAdvancedPanelPossible } from "../../E2eAdvancedPanel";
|
import { isE2eAdvancedPanelPossible } from "../../E2eAdvancedPanel";
|
||||||
import CountlyAnalytics from "../../../../../CountlyAnalytics";
|
import CountlyAnalytics from "../../../../../CountlyAnalytics";
|
||||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||||
|
import { PosthogAnalytics } from "../../../../../PosthogAnalytics";
|
||||||
|
|
||||||
export class IgnoredUser extends React.Component {
|
export class IgnoredUser extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -106,6 +107,7 @@ export default class SecurityUserSettingsTab extends React.Component {
|
||||||
_updateAnalytics = (checked) => {
|
_updateAnalytics = (checked) => {
|
||||||
checked ? Analytics.enable() : Analytics.disable();
|
checked ? Analytics.enable() : Analytics.disable();
|
||||||
CountlyAnalytics.instance.enable(/* anonymous = */ !checked);
|
CountlyAnalytics.instance.enable(/* anonymous = */ !checked);
|
||||||
|
PosthogAnalytics.instance.updateAnonymityFromSettings(MatrixClientPeg.get().getUserId());
|
||||||
};
|
};
|
||||||
|
|
||||||
_onExportE2eKeysClicked = () => {
|
_onExportE2eKeysClicked = () => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||||
|
|
||||||
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,7 +16,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import IncomingCallBox from './IncomingCallBox';
|
|
||||||
import CallPreview from './CallPreview';
|
import CallPreview from './CallPreview';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
@ -31,7 +31,6 @@ interface IState {
|
||||||
export default class CallContainer extends React.PureComponent<IProps, IState> {
|
export default class CallContainer extends React.PureComponent<IProps, IState> {
|
||||||
public render() {
|
public render() {
|
||||||
return <div className="mx_CallContainer">
|
return <div className="mx_CallContainer">
|
||||||
<IncomingCallBox />
|
|
||||||
<CallPreview />
|
<CallPreview />
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,16 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
import { _t, _td } from '../../../languageHandler';
|
import { _t, _td } from '../../../languageHandler';
|
||||||
import VideoFeed from './VideoFeed';
|
import VideoFeed from './VideoFeed';
|
||||||
import RoomAvatar from "../avatars/RoomAvatar";
|
import RoomAvatar from "../avatars/RoomAvatar";
|
||||||
import { CallState, CallType, MatrixCall, CallEvent } from 'matrix-js-sdk/src/webrtc/call';
|
import { CallEvent, CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import { isOnlyCtrlOrCmdKeyEvent, Key } from '../../../Keyboard';
|
import { isOnlyCtrlOrCmdKeyEvent, Key } from '../../../Keyboard';
|
||||||
import { alwaysAboveLeftOf, alwaysAboveRightOf, ChevronFace, ContextMenuButton } from '../../structures/ContextMenu';
|
import {
|
||||||
|
alwaysAboveLeftOf,
|
||||||
|
alwaysAboveRightOf,
|
||||||
|
ChevronFace,
|
||||||
|
ContextMenuTooltipButton,
|
||||||
|
} from '../../structures/ContextMenu';
|
||||||
import CallContextMenu from '../context_menus/CallContextMenu';
|
import CallContextMenu from '../context_menus/CallContextMenu';
|
||||||
import { avatarUrlForMember } from '../../../Avatar';
|
import { avatarUrlForMember } from '../../../Avatar';
|
||||||
import DialpadContextMenu from '../context_menus/DialpadContextMenu';
|
import DialpadContextMenu from '../context_menus/DialpadContextMenu';
|
||||||
|
@ -38,6 +43,8 @@ import Modal from '../../../Modal';
|
||||||
import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes';
|
import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes';
|
||||||
import CallViewSidebar from './CallViewSidebar';
|
import CallViewSidebar from './CallViewSidebar';
|
||||||
import { CallViewHeader } from './CallView/CallViewHeader';
|
import { CallViewHeader } from './CallView/CallViewHeader';
|
||||||
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
|
import { Alignment } from "../elements/Tooltip";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
// The call for us to display
|
// The call for us to display
|
||||||
|
@ -76,6 +83,8 @@ interface IState {
|
||||||
sidebarShown: boolean;
|
sidebarShown: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tooltipYOffset = -24;
|
||||||
|
|
||||||
function getFullScreenElement() {
|
function getFullScreenElement() {
|
||||||
return (
|
return (
|
||||||
document.fullscreenElement ||
|
document.fullscreenElement ||
|
||||||
|
@ -447,9 +456,12 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||||
let vidMuteButton;
|
let vidMuteButton;
|
||||||
if (this.props.call.type === CallType.Video) {
|
if (this.props.call.type === CallType.Video) {
|
||||||
vidMuteButton = (
|
vidMuteButton = (
|
||||||
<AccessibleButton
|
<AccessibleTooltipButton
|
||||||
className={vidClasses}
|
className={vidClasses}
|
||||||
onClick={this.onVidMuteClick}
|
onClick={this.onVidMuteClick}
|
||||||
|
title={this.state.vidMuted ? _t("Start the camera") : _t("Stop the camera")}
|
||||||
|
alignment={Alignment.Top}
|
||||||
|
yOffset={tooltipYOffset}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -464,9 +476,15 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||||
this.props.call.state === CallState.Connected
|
this.props.call.state === CallState.Connected
|
||||||
) {
|
) {
|
||||||
screensharingButton = (
|
screensharingButton = (
|
||||||
<AccessibleButton
|
<AccessibleTooltipButton
|
||||||
className={screensharingClasses}
|
className={screensharingClasses}
|
||||||
onClick={this.onScreenshareClick}
|
onClick={this.onScreenshareClick}
|
||||||
|
title={this.state.screensharing
|
||||||
|
? _t("Stop sharing your screen")
|
||||||
|
: _t("Start sharing your screen")
|
||||||
|
}
|
||||||
|
alignment={Alignment.Top}
|
||||||
|
yOffset={tooltipYOffset}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -486,6 +504,7 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className={sidebarButtonClasses}
|
className={sidebarButtonClasses}
|
||||||
onClick={this.onToggleSidebar}
|
onClick={this.onToggleSidebar}
|
||||||
|
aria-label={this.state.sidebarShown ? _t("Hide sidebar") : _t("Show sidebar")}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -494,22 +513,28 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||||
let contextMenuButton;
|
let contextMenuButton;
|
||||||
if (this.state.callState === CallState.Connected) {
|
if (this.state.callState === CallState.Connected) {
|
||||||
contextMenuButton = (
|
contextMenuButton = (
|
||||||
<ContextMenuButton
|
<ContextMenuTooltipButton
|
||||||
className="mx_CallView_callControls_button mx_CallView_callControls_button_more"
|
className="mx_CallView_callControls_button mx_CallView_callControls_button_more"
|
||||||
onClick={this.onMoreClick}
|
onClick={this.onMoreClick}
|
||||||
inputRef={this.contextMenuButton}
|
inputRef={this.contextMenuButton}
|
||||||
isExpanded={this.state.showMoreMenu}
|
isExpanded={this.state.showMoreMenu}
|
||||||
|
title={_t("More")}
|
||||||
|
alignment={Alignment.Top}
|
||||||
|
yOffset={tooltipYOffset}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let dialpadButton;
|
let dialpadButton;
|
||||||
if (this.state.callState === CallState.Connected && this.props.call.opponentSupportsDTMF()) {
|
if (this.state.callState === CallState.Connected && this.props.call.opponentSupportsDTMF()) {
|
||||||
dialpadButton = (
|
dialpadButton = (
|
||||||
<ContextMenuButton
|
<ContextMenuTooltipButton
|
||||||
className="mx_CallView_callControls_button mx_CallView_callControls_dialpad"
|
className="mx_CallView_callControls_button mx_CallView_callControls_dialpad"
|
||||||
inputRef={this.dialpadButton}
|
inputRef={this.dialpadButton}
|
||||||
onClick={this.onDialpadClick}
|
onClick={this.onDialpadClick}
|
||||||
isExpanded={this.state.showDialpad}
|
isExpanded={this.state.showDialpad}
|
||||||
|
title={_t("Dialpad")}
|
||||||
|
alignment={Alignment.Top}
|
||||||
|
yOffset={tooltipYOffset}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -522,7 +547,11 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||||
ChevronFace.None,
|
ChevronFace.None,
|
||||||
CONTEXT_MENU_VPADDING,
|
CONTEXT_MENU_VPADDING,
|
||||||
)}
|
)}
|
||||||
mountAsChild={true}
|
// We mount the context menus as a as a child typically in order to include the
|
||||||
|
// context menus when fullscreening the call content.
|
||||||
|
// However, this does not work as well when the call is embedded in a
|
||||||
|
// picture-in-picture frame. Thus, only mount as child when we are *not* in PiP.
|
||||||
|
mountAsChild={!this.props.pipMode}
|
||||||
onFinished={this.closeDialpad}
|
onFinished={this.closeDialpad}
|
||||||
call={this.props.call}
|
call={this.props.call}
|
||||||
/>;
|
/>;
|
||||||
|
@ -536,7 +565,7 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||||
ChevronFace.None,
|
ChevronFace.None,
|
||||||
CONTEXT_MENU_VPADDING,
|
CONTEXT_MENU_VPADDING,
|
||||||
)}
|
)}
|
||||||
mountAsChild={true}
|
mountAsChild={!this.props.pipMode}
|
||||||
onFinished={this.closeContextMenu}
|
onFinished={this.closeContextMenu}
|
||||||
call={this.props.call}
|
call={this.props.call}
|
||||||
/>;
|
/>;
|
||||||
|
@ -551,9 +580,12 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||||
{ dialPad }
|
{ dialPad }
|
||||||
{ contextMenu }
|
{ contextMenu }
|
||||||
{ dialpadButton }
|
{ dialpadButton }
|
||||||
<AccessibleButton
|
<AccessibleTooltipButton
|
||||||
className={micClasses}
|
className={micClasses}
|
||||||
onClick={this.onMicMuteClick}
|
onClick={this.onMicMuteClick}
|
||||||
|
title={this.state.micMuted ? _t("Unmute the microphone") : _t("Mute the microphone")}
|
||||||
|
alignment={Alignment.Top}
|
||||||
|
yOffset={tooltipYOffset}
|
||||||
/>
|
/>
|
||||||
{ vidMuteButton }
|
{ vidMuteButton }
|
||||||
<div className={micCacheClasses} />
|
<div className={micCacheClasses} />
|
||||||
|
@ -561,9 +593,12 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||||
{ screensharingButton }
|
{ screensharingButton }
|
||||||
{ sidebarButton }
|
{ sidebarButton }
|
||||||
{ contextMenuButton }
|
{ contextMenuButton }
|
||||||
<AccessibleButton
|
<AccessibleTooltipButton
|
||||||
className="mx_CallView_callControls_button mx_CallView_callControls_button_hangup"
|
className="mx_CallView_callControls_button mx_CallView_callControls_button_hangup"
|
||||||
onClick={this.onHangupClick}
|
onClick={this.onHangupClick}
|
||||||
|
title={_t("Hangup")}
|
||||||
|
alignment={Alignment.Top}
|
||||||
|
yOffset={tooltipYOffset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,176 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015, 2016 OpenMarket 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");
|
|
||||||
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 { MatrixClientPeg } from '../../../MatrixClientPeg';
|
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
import { ActionPayload } from '../../../dispatcher/payloads';
|
|
||||||
import CallHandler, { CallHandlerEvent } from '../../../CallHandler';
|
|
||||||
import RoomAvatar from '../avatars/RoomAvatar';
|
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
|
||||||
import { CallState } from 'matrix-js-sdk/src/webrtc/call';
|
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
|
||||||
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
incomingCall: any;
|
|
||||||
silenced: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@replaceableComponent("views.voip.IncomingCallBox")
|
|
||||||
export default class IncomingCallBox extends React.Component<IProps, IState> {
|
|
||||||
private dispatcherRef: string;
|
|
||||||
|
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
|
||||||
this.state = {
|
|
||||||
incomingCall: null,
|
|
||||||
silenced: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount = () => {
|
|
||||||
CallHandler.sharedInstance().addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged);
|
|
||||||
};
|
|
||||||
|
|
||||||
public componentWillUnmount() {
|
|
||||||
dis.unregister(this.dispatcherRef);
|
|
||||||
CallHandler.sharedInstance().removeListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onAction = (payload: ActionPayload) => {
|
|
||||||
switch (payload.action) {
|
|
||||||
case 'call_state': {
|
|
||||||
const call = CallHandler.sharedInstance().getCallForRoom(payload.room_id);
|
|
||||||
if (call && call.state === CallState.Ringing) {
|
|
||||||
this.setState({
|
|
||||||
incomingCall: call,
|
|
||||||
silenced: false, // Reset silenced state for new call
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
incomingCall: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private onSilencedCallsChanged = () => {
|
|
||||||
const callId = this.state.incomingCall?.callId;
|
|
||||||
if (!callId) return;
|
|
||||||
this.setState({ silenced: CallHandler.sharedInstance().isCallSilenced(callId) });
|
|
||||||
};
|
|
||||||
|
|
||||||
private onAnswerClick: React.MouseEventHandler = (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'answer',
|
|
||||||
room_id: CallHandler.sharedInstance().roomIdForCall(this.state.incomingCall),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
private onRejectClick: React.MouseEventHandler = (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'reject',
|
|
||||||
room_id: CallHandler.sharedInstance().roomIdForCall(this.state.incomingCall),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
private onSilenceClick: React.MouseEventHandler = (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
const callId = this.state.incomingCall.callId;
|
|
||||||
this.state.silenced ?
|
|
||||||
CallHandler.sharedInstance().unSilenceCall(callId):
|
|
||||||
CallHandler.sharedInstance().silenceCall(callId);
|
|
||||||
};
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
if (!this.state.incomingCall) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let room = null;
|
|
||||||
if (this.state.incomingCall) {
|
|
||||||
room = MatrixClientPeg.get().getRoom(CallHandler.sharedInstance().roomIdForCall(this.state.incomingCall));
|
|
||||||
}
|
|
||||||
|
|
||||||
const caller = room ? room.name : _t("Unknown caller");
|
|
||||||
|
|
||||||
let incomingCallText = null;
|
|
||||||
if (this.state.incomingCall) {
|
|
||||||
if (this.state.incomingCall.type === "voice") {
|
|
||||||
incomingCallText = _t("Incoming voice call");
|
|
||||||
} else if (this.state.incomingCall.type === "video") {
|
|
||||||
incomingCallText = _t("Incoming video call");
|
|
||||||
} else {
|
|
||||||
incomingCallText = _t("Incoming call");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const silenceClass = classNames({
|
|
||||||
"mx_IncomingCallBox_iconButton": true,
|
|
||||||
"mx_IncomingCallBox_unSilence": this.state.silenced,
|
|
||||||
"mx_IncomingCallBox_silence": !this.state.silenced,
|
|
||||||
});
|
|
||||||
|
|
||||||
return <div className="mx_IncomingCallBox">
|
|
||||||
<div className="mx_IncomingCallBox_CallerInfo">
|
|
||||||
<RoomAvatar
|
|
||||||
room={room}
|
|
||||||
height={32}
|
|
||||||
width={32}
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<h1>{ caller }</h1>
|
|
||||||
<p>{ incomingCallText }</p>
|
|
||||||
</div>
|
|
||||||
<AccessibleTooltipButton
|
|
||||||
className={silenceClass}
|
|
||||||
onClick={this.onSilenceClick}
|
|
||||||
title={this.state.silenced ? _t("Sound on"): _t("Silence call")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mx_IncomingCallBox_buttons">
|
|
||||||
<AccessibleButton
|
|
||||||
className="mx_IncomingCallBox_decline"
|
|
||||||
onClick={this.onRejectClick}
|
|
||||||
kind="danger"
|
|
||||||
>
|
|
||||||
{ _t("Decline") }
|
|
||||||
</AccessibleButton>
|
|
||||||
<div className="mx_IncomingCallBox_spacer" />
|
|
||||||
<AccessibleButton
|
|
||||||
className="mx_IncomingCallBox_accept"
|
|
||||||
onClick={this.onAnswerClick}
|
|
||||||
kind="primary"
|
|
||||||
>
|
|
||||||
{ _t("Accept") }
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -207,6 +207,7 @@ export default class VideoFeed extends React.PureComponent<IProps, IState> {
|
||||||
const videoClasses = classnames("mx_VideoFeed_video", {
|
const videoClasses = classnames("mx_VideoFeed_video", {
|
||||||
mx_VideoFeed_video_mirror: (
|
mx_VideoFeed_video_mirror: (
|
||||||
this.props.feed.isLocal() &&
|
this.props.feed.isLocal() &&
|
||||||
|
this.props.feed.purpose === SDPStreamMetadataPurpose.Usermedia &&
|
||||||
SettingsStore.getValue('VideoView.flipVideoHorizontally')
|
SettingsStore.getValue('VideoView.flipVideoHorizontally')
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
16
src/emoji.ts
16
src/emoji.ts
|
@ -35,6 +35,16 @@ export const EMOTICON_TO_EMOJI = new Map<string, IEmoji>();
|
||||||
|
|
||||||
export const getEmojiFromUnicode = unicode => UNICODE_TO_EMOJI.get(stripVariation(unicode));
|
export const getEmojiFromUnicode = unicode => UNICODE_TO_EMOJI.get(stripVariation(unicode));
|
||||||
|
|
||||||
|
const isRegionalIndicator = (x: string): boolean => {
|
||||||
|
// First verify that the string is a single character. We use Array.from
|
||||||
|
// to make sure we count by characters, not UTF-8 code units.
|
||||||
|
return Array.from(x).length === 1 &&
|
||||||
|
// Next verify that the character is within the code point range for
|
||||||
|
// regional indicators.
|
||||||
|
// http://unicode.org/charts/PDF/Unicode-6.0/U60-1F100.pdf
|
||||||
|
x >= '\u{1f1e6}' && x <= '\u{1f1ff}';
|
||||||
|
};
|
||||||
|
|
||||||
const EMOJIBASE_GROUP_ID_TO_CATEGORY = [
|
const EMOJIBASE_GROUP_ID_TO_CATEGORY = [
|
||||||
"people", // smileys
|
"people", // smileys
|
||||||
"people", // actually people
|
"people", // actually people
|
||||||
|
@ -72,7 +82,11 @@ export const EMOJI: IEmoji[] = EMOJIBASE.map((emojiData: Omit<IEmoji, "shortcode
|
||||||
shortcodes: typeof shortcodeData === "string" ? [shortcodeData] : shortcodeData,
|
shortcodes: typeof shortcodeData === "string" ? [shortcodeData] : shortcodeData,
|
||||||
};
|
};
|
||||||
|
|
||||||
const categoryId = EMOJIBASE_GROUP_ID_TO_CATEGORY[emoji.group];
|
// We manually include regional indicators in the symbols group, since
|
||||||
|
// Emojibase intentionally leaves them uncategorized
|
||||||
|
const categoryId = EMOJIBASE_GROUP_ID_TO_CATEGORY[emoji.group] ??
|
||||||
|
(isRegionalIndicator(emoji.unicode) ? "symbols" : null);
|
||||||
|
|
||||||
if (DATA_BY_CATEGORY.hasOwnProperty(categoryId)) {
|
if (DATA_BY_CATEGORY.hasOwnProperty(categoryId)) {
|
||||||
DATA_BY_CATEGORY[categoryId].push(emoji);
|
DATA_BY_CATEGORY[categoryId].push(emoji);
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,8 +64,6 @@
|
||||||
"Unable to transfer call": "Unable to transfer call",
|
"Unable to transfer call": "Unable to transfer call",
|
||||||
"Transfer Failed": "Transfer Failed",
|
"Transfer Failed": "Transfer Failed",
|
||||||
"Failed to transfer call": "Failed to transfer call",
|
"Failed to transfer call": "Failed to transfer call",
|
||||||
"Call in Progress": "Call in Progress",
|
|
||||||
"A call is currently being placed!": "A call is currently being placed!",
|
|
||||||
"Permission Required": "Permission Required",
|
"Permission Required": "Permission Required",
|
||||||
"You do not have permission to start a conference call in this room": "You do not have permission to start a conference call in this room",
|
"You do not have permission to start a conference call in this room": "You do not have permission to start a conference call in this room",
|
||||||
"End conference": "End conference",
|
"End conference": "End conference",
|
||||||
|
@ -734,6 +732,13 @@
|
||||||
"Notifications": "Notifications",
|
"Notifications": "Notifications",
|
||||||
"Enable desktop notifications": "Enable desktop notifications",
|
"Enable desktop notifications": "Enable desktop notifications",
|
||||||
"Enable": "Enable",
|
"Enable": "Enable",
|
||||||
|
"Unknown caller": "Unknown caller",
|
||||||
|
"Voice call": "Voice call",
|
||||||
|
"Video call": "Video call",
|
||||||
|
"Decline": "Decline",
|
||||||
|
"Accept": "Accept",
|
||||||
|
"Sound on": "Sound on",
|
||||||
|
"Silence call": "Silence call",
|
||||||
"Use app for a better experience": "Use app for a better experience",
|
"Use app for a better experience": "Use app for a better experience",
|
||||||
"Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.": "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.",
|
"Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.": "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.",
|
||||||
"Use app": "Use app",
|
"Use app": "Use app",
|
||||||
|
@ -813,6 +818,7 @@
|
||||||
"Show message previews for reactions in DMs": "Show message previews for reactions in DMs",
|
"Show message previews for reactions in DMs": "Show message previews for reactions in DMs",
|
||||||
"Show message previews for reactions in all rooms": "Show message previews for reactions in all rooms",
|
"Show message previews for reactions in all rooms": "Show message previews for reactions in all rooms",
|
||||||
"Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices",
|
"Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices",
|
||||||
|
"Send pseudonymous analytics data": "Send pseudonymous analytics data",
|
||||||
"Enable advanced debugging for the room list": "Enable advanced debugging for the room list",
|
"Enable advanced debugging for the room list": "Enable advanced debugging for the room list",
|
||||||
"Show info about bridges in room settings": "Show info about bridges in room settings",
|
"Show info about bridges in room settings": "Show info about bridges in room settings",
|
||||||
"New layout switcher (with message bubbles)": "New layout switcher (with message bubbles)",
|
"New layout switcher (with message bubbles)": "New layout switcher (with message bubbles)",
|
||||||
|
@ -841,6 +847,7 @@
|
||||||
"Use Ctrl + F to search timeline": "Use Ctrl + F to search timeline",
|
"Use Ctrl + F to search timeline": "Use Ctrl + F to search timeline",
|
||||||
"Use Command + Enter to send a message": "Use Command + Enter to send a message",
|
"Use Command + Enter to send a message": "Use Command + Enter to send a message",
|
||||||
"Use Ctrl + Enter to send a message": "Use Ctrl + Enter to send a message",
|
"Use Ctrl + Enter to send a message": "Use Ctrl + Enter to send a message",
|
||||||
|
"Surround selected text when typing special characters": "Surround selected text when typing special characters",
|
||||||
"Automatically replace plain text Emoji": "Automatically replace plain text Emoji",
|
"Automatically replace plain text Emoji": "Automatically replace plain text Emoji",
|
||||||
"Mirror local video feed": "Mirror local video feed",
|
"Mirror local video feed": "Mirror local video feed",
|
||||||
"Enable Community Filter Panel": "Enable Community Filter Panel",
|
"Enable Community Filter Panel": "Enable Community Filter Panel",
|
||||||
|
@ -897,6 +904,17 @@
|
||||||
"sends snowfall": "sends snowfall",
|
"sends snowfall": "sends snowfall",
|
||||||
"Sends the given message with a space themed effect": "Sends the given message with a space themed effect",
|
"Sends the given message with a space themed effect": "Sends the given message with a space themed effect",
|
||||||
"sends space invaders": "sends space invaders",
|
"sends space invaders": "sends space invaders",
|
||||||
|
"Start the camera": "Start the camera",
|
||||||
|
"Stop the camera": "Stop the camera",
|
||||||
|
"Stop sharing your screen": "Stop sharing your screen",
|
||||||
|
"Start sharing your screen": "Start sharing your screen",
|
||||||
|
"Hide sidebar": "Hide sidebar",
|
||||||
|
"Show sidebar": "Show sidebar",
|
||||||
|
"More": "More",
|
||||||
|
"Dialpad": "Dialpad",
|
||||||
|
"Unmute the microphone": "Unmute the microphone",
|
||||||
|
"Mute the microphone": "Mute the microphone",
|
||||||
|
"Hangup": "Hangup",
|
||||||
"unknown person": "unknown person",
|
"unknown person": "unknown person",
|
||||||
"Consulting with %(transferTarget)s. <a>Transfer to %(transferee)s</a>": "Consulting with %(transferTarget)s. <a>Transfer to %(transferee)s</a>",
|
"Consulting with %(transferTarget)s. <a>Transfer to %(transferee)s</a>": "Consulting with %(transferTarget)s. <a>Transfer to %(transferee)s</a>",
|
||||||
"You held the call <a>Switch</a>": "You held the call <a>Switch</a>",
|
"You held the call <a>Switch</a>": "You held the call <a>Switch</a>",
|
||||||
|
@ -907,14 +925,9 @@
|
||||||
"%(sharerName)s is presenting": "%(sharerName)s is presenting",
|
"%(sharerName)s is presenting": "%(sharerName)s is presenting",
|
||||||
"Your camera is turned off": "Your camera is turned off",
|
"Your camera is turned off": "Your camera is turned off",
|
||||||
"Your camera is still enabled": "Your camera is still enabled",
|
"Your camera is still enabled": "Your camera is still enabled",
|
||||||
"Unknown caller": "Unknown caller",
|
|
||||||
"Incoming voice call": "Incoming voice call",
|
"Incoming voice call": "Incoming voice call",
|
||||||
"Incoming video call": "Incoming video call",
|
"Incoming video call": "Incoming video call",
|
||||||
"Incoming call": "Incoming call",
|
"Incoming call": "Incoming call",
|
||||||
"Sound on": "Sound on",
|
|
||||||
"Silence call": "Silence call",
|
|
||||||
"Decline": "Decline",
|
|
||||||
"Accept": "Accept",
|
|
||||||
"Video Call": "Video Call",
|
"Video Call": "Video Call",
|
||||||
"Voice Call": "Voice Call",
|
"Voice Call": "Voice Call",
|
||||||
"Fill Screen": "Fill Screen",
|
"Fill Screen": "Fill Screen",
|
||||||
|
@ -1582,8 +1595,6 @@
|
||||||
"Hide Widgets": "Hide Widgets",
|
"Hide Widgets": "Hide Widgets",
|
||||||
"Show Widgets": "Show Widgets",
|
"Show Widgets": "Show Widgets",
|
||||||
"Search": "Search",
|
"Search": "Search",
|
||||||
"Voice call": "Voice call",
|
|
||||||
"Video call": "Video call",
|
|
||||||
"Invites": "Invites",
|
"Invites": "Invites",
|
||||||
"Favourites": "Favourites",
|
"Favourites": "Favourites",
|
||||||
"People": "People",
|
"People": "People",
|
||||||
|
@ -1700,14 +1711,12 @@
|
||||||
"Invited by %(sender)s": "Invited by %(sender)s",
|
"Invited by %(sender)s": "Invited by %(sender)s",
|
||||||
"Jump to first unread message.": "Jump to first unread message.",
|
"Jump to first unread message.": "Jump to first unread message.",
|
||||||
"Mark all as read": "Mark all as read",
|
"Mark all as read": "Mark all as read",
|
||||||
"The voice message failed to upload.": "The voice message failed to upload.",
|
|
||||||
"Unable to access your microphone": "Unable to access your microphone",
|
"Unable to access your microphone": "Unable to access your microphone",
|
||||||
"We were unable to access your microphone. Please check your browser settings and try again.": "We were unable to access your microphone. Please check your browser settings and try again.",
|
"We were unable to access your microphone. Please check your browser settings and try again.": "We were unable to access your microphone. Please check your browser settings and try again.",
|
||||||
"No microphone found": "No microphone found",
|
"No microphone found": "No microphone found",
|
||||||
"We didn't find a microphone on your device. Please check your settings and try again.": "We didn't find a microphone on your device. Please check your settings and try again.",
|
"We didn't find a microphone on your device. Please check your settings and try again.": "We didn't find a microphone on your device. Please check your settings and try again.",
|
||||||
"Record a voice message": "Record a voice message",
|
"Send voice message": "Send voice message",
|
||||||
"Stop the recording": "Stop the recording",
|
"Stop recording": "Stop recording",
|
||||||
"Delete recording": "Delete recording",
|
|
||||||
"Error updating main address": "Error updating main address",
|
"Error updating main address": "Error updating main address",
|
||||||
"There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.",
|
"There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.",
|
||||||
"There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.",
|
"There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.",
|
||||||
|
@ -1868,19 +1877,15 @@
|
||||||
"Verification cancelled": "Verification cancelled",
|
"Verification cancelled": "Verification cancelled",
|
||||||
"Compare emoji": "Compare emoji",
|
"Compare emoji": "Compare emoji",
|
||||||
"Connected": "Connected",
|
"Connected": "Connected",
|
||||||
"You declined this call": "You declined this call",
|
"Call declined": "Call declined",
|
||||||
"They declined this call": "They declined this call",
|
|
||||||
"Call back": "Call back",
|
"Call back": "Call back",
|
||||||
"Call again": "Call again",
|
"Missed call": "Missed call",
|
||||||
"This call has ended": "This call has ended",
|
|
||||||
"They didn't pick up": "They didn't pick up",
|
|
||||||
"Could not connect media": "Could not connect media",
|
"Could not connect media": "Could not connect media",
|
||||||
"Connection failed": "Connection failed",
|
"Connection failed": "Connection failed",
|
||||||
"Their device couldn't start the camera or microphone": "Their device couldn't start the camera or microphone",
|
"Their device couldn't start the camera or microphone": "Their device couldn't start the camera or microphone",
|
||||||
"An unknown error occurred": "An unknown error occurred",
|
"An unknown error occurred": "An unknown error occurred",
|
||||||
"Unknown failure: %(reason)s)": "Unknown failure: %(reason)s)",
|
"Unknown failure: %(reason)s)": "Unknown failure: %(reason)s)",
|
||||||
"This call has failed": "This call has failed",
|
"Retry": "Retry",
|
||||||
"You missed this call": "You missed this call",
|
|
||||||
"The call is in an unknown state!": "The call is in an unknown state!",
|
"The call is in an unknown state!": "The call is in an unknown state!",
|
||||||
"Sunday": "Sunday",
|
"Sunday": "Sunday",
|
||||||
"Monday": "Monday",
|
"Monday": "Monday",
|
||||||
|
@ -1903,7 +1908,6 @@
|
||||||
"Error processing audio message": "Error processing audio message",
|
"Error processing audio message": "Error processing audio message",
|
||||||
"React": "React",
|
"React": "React",
|
||||||
"Edit": "Edit",
|
"Edit": "Edit",
|
||||||
"Retry": "Retry",
|
|
||||||
"Reply": "Reply",
|
"Reply": "Reply",
|
||||||
"Message Actions": "Message Actions",
|
"Message Actions": "Message Actions",
|
||||||
"Download %(text)s": "Download %(text)s",
|
"Download %(text)s": "Download %(text)s",
|
||||||
|
|
|
@ -41,6 +41,7 @@ import { Layout } from "./Layout";
|
||||||
import ReducedMotionController from './controllers/ReducedMotionController';
|
import ReducedMotionController from './controllers/ReducedMotionController';
|
||||||
import IncompatibleController from "./controllers/IncompatibleController";
|
import IncompatibleController from "./controllers/IncompatibleController";
|
||||||
import SdkConfig from "../SdkConfig";
|
import SdkConfig from "../SdkConfig";
|
||||||
|
import PseudonymousAnalyticsController from './controllers/PseudonymousAnalyticsController';
|
||||||
import NewLayoutSwitcherController from './controllers/NewLayoutSwitcherController';
|
import NewLayoutSwitcherController from './controllers/NewLayoutSwitcherController';
|
||||||
|
|
||||||
// These are just a bunch of helper arrays to avoid copy/pasting a bunch of times
|
// These are just a bunch of helper arrays to avoid copy/pasting a bunch of times
|
||||||
|
@ -268,6 +269,13 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
||||||
supportedLevels: LEVELS_FEATURE,
|
supportedLevels: LEVELS_FEATURE,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
"feature_pseudonymous_analytics_opt_in": {
|
||||||
|
isFeature: true,
|
||||||
|
supportedLevels: LEVELS_FEATURE,
|
||||||
|
displayName: _td('Send pseudonymous analytics data'),
|
||||||
|
default: false,
|
||||||
|
controller: new PseudonymousAnalyticsController(),
|
||||||
|
},
|
||||||
"advancedRoomListLogging": {
|
"advancedRoomListLogging": {
|
||||||
// TODO: Remove flag before launch: https://github.com/vector-im/element-web/issues/14231
|
// TODO: Remove flag before launch: https://github.com/vector-im/element-web/issues/14231
|
||||||
displayName: _td("Enable advanced debugging for the room list"),
|
displayName: _td("Enable advanced debugging for the room list"),
|
||||||
|
@ -441,6 +449,11 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
||||||
displayName: isMac ? _td("Use Command + Enter to send a message") : _td("Use Ctrl + Enter to send a message"),
|
displayName: isMac ? _td("Use Command + Enter to send a message") : _td("Use Ctrl + Enter to send a message"),
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
"MessageComposerInput.surroundWith": {
|
||||||
|
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||||
|
displayName: _td("Surround selected text when typing special characters"),
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
"MessageComposerInput.autoReplaceEmoji": {
|
"MessageComposerInput.autoReplaceEmoji": {
|
||||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||||
displayName: _td('Automatically replace plain text Emoji'),
|
displayName: _td('Automatically replace plain text Emoji'),
|
||||||
|
|
26
src/settings/controllers/PseudonymousAnalyticsController.ts
Normal file
26
src/settings/controllers/PseudonymousAnalyticsController.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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 SettingController from "./SettingController";
|
||||||
|
import { SettingLevel } from "../SettingLevel";
|
||||||
|
import { PosthogAnalytics } from "../../PosthogAnalytics";
|
||||||
|
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
|
|
||||||
|
export default class PseudonymousAnalyticsController extends SettingController {
|
||||||
|
public onChange(level: SettingLevel, roomId: string, newValue: any) {
|
||||||
|
PosthogAnalytics.instance.updateAnonymityFromSettings(MatrixClientPeg.get().getUserId());
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,10 +22,11 @@ export interface IToast<C extends ComponentClass> {
|
||||||
key: string;
|
key: string;
|
||||||
// higher priority number will be shown on top of lower priority
|
// higher priority number will be shown on top of lower priority
|
||||||
priority: number;
|
priority: number;
|
||||||
title: string;
|
title?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
component: C;
|
component: C;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
bodyClassName?: string;
|
||||||
props?: Omit<React.ComponentProps<C>, "toastKey">; // toastKey is injected by ToastContainer
|
props?: Omit<React.ComponentProps<C>, "toastKey">; // toastKey is injected by ToastContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
13
src/theme.js
13
src/theme.js
|
@ -171,15 +171,10 @@ export async function setTheme(theme) {
|
||||||
// look for the stylesheet elements.
|
// look for the stylesheet elements.
|
||||||
// styleElements is a map from style name to HTMLLinkElement.
|
// styleElements is a map from style name to HTMLLinkElement.
|
||||||
const styleElements = Object.create(null);
|
const styleElements = Object.create(null);
|
||||||
let a;
|
const themes = Array.from(document.querySelectorAll('[data-mx-theme]'));
|
||||||
for (let i = 0; (a = document.getElementsByTagName("link")[i]); i++) {
|
themes.forEach(theme => {
|
||||||
const href = a.getAttribute("href");
|
styleElements[theme.attributes['data-mx-theme'].value.toLowerCase()] = theme;
|
||||||
// shouldn't we be using the 'title' tag rather than the href?
|
});
|
||||||
const match = href && href.match(/^bundles\/.*\/theme-(.*)\.css$/);
|
|
||||||
if (match) {
|
|
||||||
styleElements[match[1]] = a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(stylesheetName in styleElements)) {
|
if (!(stylesheetName in styleElements)) {
|
||||||
throw new Error("Unknown theme " + stylesheetName);
|
throw new Error("Unknown theme " + stylesheetName);
|
||||||
|
|
140
src/toasts/IncomingCallToast.tsx
Normal file
140
src/toasts/IncomingCallToast.tsx
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { replaceableComponent } from '../utils/replaceableComponent';
|
||||||
|
import CallHandler, { CallHandlerEvent } from '../CallHandler';
|
||||||
|
import dis from '../dispatcher/dispatcher';
|
||||||
|
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||||
|
import { _t } from '../languageHandler';
|
||||||
|
import RoomAvatar from '../components/views/avatars/RoomAvatar';
|
||||||
|
import AccessibleTooltipButton from '../components/views/elements/AccessibleTooltipButton';
|
||||||
|
import AccessibleButton from '../components/views/elements/AccessibleButton';
|
||||||
|
|
||||||
|
export const getIncomingCallToastKey = (callId: string) => `call_${callId}`;
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
call: MatrixCall;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
silenced: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("views.voip.IncomingCallToast")
|
||||||
|
export default class IncomingCallToast extends React.Component<IProps, IState> {
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
silenced: CallHandler.sharedInstance().isCallSilenced(this.props.call.callId),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidMount = (): void => {
|
||||||
|
CallHandler.sharedInstance().addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged);
|
||||||
|
};
|
||||||
|
|
||||||
|
public componentWillUnmount(): void {
|
||||||
|
CallHandler.sharedInstance().removeListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onSilencedCallsChanged = (): void => {
|
||||||
|
this.setState({ silenced: CallHandler.sharedInstance().isCallSilenced(this.props.call.callId) });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onAnswerClick = (e: React.MouseEvent): void => {
|
||||||
|
e.stopPropagation();
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'answer',
|
||||||
|
room_id: CallHandler.sharedInstance().roomIdForCall(this.props.call),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onRejectClick= (e: React.MouseEvent): void => {
|
||||||
|
e.stopPropagation();
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'reject',
|
||||||
|
room_id: CallHandler.sharedInstance().roomIdForCall(this.props.call),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onSilenceClick = (e: React.MouseEvent): void => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const callId = this.props.call.callId;
|
||||||
|
this.state.silenced ?
|
||||||
|
CallHandler.sharedInstance().unSilenceCall(callId) :
|
||||||
|
CallHandler.sharedInstance().silenceCall(callId);
|
||||||
|
};
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const call = this.props.call;
|
||||||
|
const room = MatrixClientPeg.get().getRoom(CallHandler.sharedInstance().roomIdForCall(call));
|
||||||
|
const isVoice = call.type === CallType.Voice;
|
||||||
|
|
||||||
|
const contentClass = classNames("mx_IncomingCallToast_content", {
|
||||||
|
"mx_IncomingCallToast_content_voice": isVoice,
|
||||||
|
"mx_IncomingCallToast_content_video": !isVoice,
|
||||||
|
});
|
||||||
|
const silenceClass = classNames("mx_IncomingCallToast_iconButton", {
|
||||||
|
"mx_IncomingCallToast_unSilence": this.state.silenced,
|
||||||
|
"mx_IncomingCallToast_silence": !this.state.silenced,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <React.Fragment>
|
||||||
|
<RoomAvatar
|
||||||
|
room={room}
|
||||||
|
height={32}
|
||||||
|
width={32}
|
||||||
|
/>
|
||||||
|
<div className={contentClass}>
|
||||||
|
<span className="mx_CallEvent_caller">
|
||||||
|
{ room ? room.name : _t("Unknown caller") }
|
||||||
|
</span>
|
||||||
|
<div className="mx_CallEvent_type">
|
||||||
|
<div className="mx_CallEvent_type_icon" />
|
||||||
|
{ isVoice ? _t("Voice call") : _t("Video call") }
|
||||||
|
</div>
|
||||||
|
<div className="mx_IncomingCallToast_buttons">
|
||||||
|
<AccessibleButton
|
||||||
|
className="mx_IncomingCallToast_button mx_IncomingCallToast_button_decline"
|
||||||
|
onClick={this.onRejectClick}
|
||||||
|
kind="danger"
|
||||||
|
>
|
||||||
|
<span> { _t("Decline") } </span>
|
||||||
|
</AccessibleButton>
|
||||||
|
<AccessibleButton
|
||||||
|
className="mx_IncomingCallToast_button mx_IncomingCallToast_button_accept"
|
||||||
|
onClick={this.onAnswerClick}
|
||||||
|
kind="primary"
|
||||||
|
>
|
||||||
|
<span> { _t("Accept") } </span>
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<AccessibleTooltipButton
|
||||||
|
className={silenceClass}
|
||||||
|
onClick={this.onSilenceClick}
|
||||||
|
title={this.state.silenced ? _t("Sound on") : _t("Silence call")}
|
||||||
|
/>
|
||||||
|
</React.Fragment>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,9 +43,8 @@ function getManagedIframe(): { iframe: HTMLIFrameElement, onLoadPromise: Promise
|
||||||
|
|
||||||
// Dev note: the reassignment warnings are entirely incorrect here.
|
// Dev note: the reassignment warnings are entirely incorrect here.
|
||||||
|
|
||||||
// @ts-ignore
|
managedIframe.style.display = "none";
|
||||||
// noinspection JSConstantReassignment
|
|
||||||
managedIframe.style = { display: "none" };
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
// noinspection JSConstantReassignment
|
// noinspection JSConstantReassignment
|
||||||
managedIframe.sandbox = "allow-scripts allow-downloads allow-downloads-without-user-activation";
|
managedIframe.sandbox = "allow-scripts allow-downloads allow-downloads-without-user-activation";
|
||||||
|
|
|
@ -156,13 +156,14 @@ describe('CallHandler', () => {
|
||||||
DMRoomMap.setShared(null);
|
DMRoomMap.setShared(null);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.mxCallHandler = null;
|
window.mxCallHandler = null;
|
||||||
|
fakeCall = null;
|
||||||
MatrixClientPeg.unset();
|
MatrixClientPeg.unset();
|
||||||
|
|
||||||
document.body.removeChild(audioElement);
|
document.body.removeChild(audioElement);
|
||||||
SdkConfig.unset();
|
SdkConfig.unset();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should look up the correct user and open the room when a phone number is dialled', async () => {
|
it('should look up the correct user and start a call in the room when a phone number is dialled', async () => {
|
||||||
MatrixClientPeg.get().getThirdpartyUser = jest.fn().mockResolvedValue([{
|
MatrixClientPeg.get().getThirdpartyUser = jest.fn().mockResolvedValue([{
|
||||||
userid: '@user2:example.org',
|
userid: '@user2:example.org',
|
||||||
protocol: "im.vector.protocol.sip_native",
|
protocol: "im.vector.protocol.sip_native",
|
||||||
|
@ -179,6 +180,9 @@ describe('CallHandler', () => {
|
||||||
|
|
||||||
const viewRoomPayload = await untilDispatch('view_room');
|
const viewRoomPayload = await untilDispatch('view_room');
|
||||||
expect(viewRoomPayload.room_id).toEqual(MAPPED_ROOM_ID);
|
expect(viewRoomPayload.room_id).toEqual(MAPPED_ROOM_ID);
|
||||||
|
|
||||||
|
// Check that a call was started
|
||||||
|
expect(fakeCall.roomId).toEqual(MAPPED_ROOM_ID);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should move calls between rooms when remote asserted identity changes', async () => {
|
it('should move calls between rooms when remote asserted identity changes', async () => {
|
||||||
|
|
232
test/PosthogAnalytics-test.ts
Normal file
232
test/PosthogAnalytics-test.ts
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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 {
|
||||||
|
Anonymity,
|
||||||
|
getRedactedCurrentLocation,
|
||||||
|
IAnonymousEvent,
|
||||||
|
IPseudonymousEvent,
|
||||||
|
IRoomEvent,
|
||||||
|
PosthogAnalytics,
|
||||||
|
} from '../src/PosthogAnalytics';
|
||||||
|
|
||||||
|
import SdkConfig from '../src/SdkConfig';
|
||||||
|
|
||||||
|
class FakePosthog {
|
||||||
|
public capture;
|
||||||
|
public init;
|
||||||
|
public identify;
|
||||||
|
public reset;
|
||||||
|
public register;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.capture = jest.fn();
|
||||||
|
this.init = jest.fn();
|
||||||
|
this.identify = jest.fn();
|
||||||
|
this.reset = jest.fn();
|
||||||
|
this.register = jest.fn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITestEvent extends IAnonymousEvent {
|
||||||
|
key: "jest_test_event";
|
||||||
|
properties: {
|
||||||
|
foo: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITestPseudonymousEvent extends IPseudonymousEvent {
|
||||||
|
key: "jest_test_pseudo_event";
|
||||||
|
properties: {
|
||||||
|
foo: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITestRoomEvent extends IRoomEvent {
|
||||||
|
key: "jest_test_room_event";
|
||||||
|
properties: {
|
||||||
|
foo: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("PosthogAnalytics", () => {
|
||||||
|
let fakePosthog: FakePosthog;
|
||||||
|
const shaHashes = {
|
||||||
|
"42": "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049",
|
||||||
|
"some": "a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b",
|
||||||
|
"pii": "bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4",
|
||||||
|
"foo": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fakePosthog = new FakePosthog();
|
||||||
|
|
||||||
|
window.crypto = {
|
||||||
|
subtle: {
|
||||||
|
digest: async (_, encodedMessage) => {
|
||||||
|
const message = new TextDecoder().decode(encodedMessage);
|
||||||
|
const hexHash = shaHashes[message];
|
||||||
|
const bytes = [];
|
||||||
|
for (let c = 0; c < hexHash.length; c += 2) {
|
||||||
|
bytes.push(parseInt(hexHash.substr(c, 2), 16));
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
window.crypto = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Initialisation", () => {
|
||||||
|
it("Should not be enabled without config being set", () => {
|
||||||
|
jest.spyOn(SdkConfig, "get").mockReturnValue({});
|
||||||
|
const analytics = new PosthogAnalytics(fakePosthog);
|
||||||
|
expect(analytics.isEnabled()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should be enabled if config is set", () => {
|
||||||
|
jest.spyOn(SdkConfig, "get").mockReturnValue({
|
||||||
|
posthog: {
|
||||||
|
projectApiKey: "foo",
|
||||||
|
apiHost: "bar",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const analytics = new PosthogAnalytics(fakePosthog);
|
||||||
|
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||||
|
expect(analytics.isEnabled()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Tracking", () => {
|
||||||
|
let analytics: PosthogAnalytics;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(SdkConfig, "get").mockReturnValue({
|
||||||
|
posthog: {
|
||||||
|
projectApiKey: "foo",
|
||||||
|
apiHost: "bar",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
analytics = new PosthogAnalytics(fakePosthog);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should pass trackAnonymousEvent() to posthog", async () => {
|
||||||
|
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||||
|
await analytics.trackAnonymousEvent<ITestEvent>("jest_test_event", {
|
||||||
|
foo: "bar",
|
||||||
|
});
|
||||||
|
expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event");
|
||||||
|
expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should pass trackRoomEvent to posthog", async () => {
|
||||||
|
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||||
|
const roomId = "42";
|
||||||
|
await analytics.trackRoomEvent<IRoomEvent>("jest_test_event", roomId, {
|
||||||
|
foo: "bar",
|
||||||
|
});
|
||||||
|
expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_event");
|
||||||
|
expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar");
|
||||||
|
expect(fakePosthog.capture.mock.calls[0][1]["hashedRoomId"])
|
||||||
|
.toEqual("73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should pass trackPseudonymousEvent() to posthog", async () => {
|
||||||
|
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||||
|
await analytics.trackPseudonymousEvent<ITestEvent>("jest_test_pseudo_event", {
|
||||||
|
foo: "bar",
|
||||||
|
});
|
||||||
|
expect(fakePosthog.capture.mock.calls[0][0]).toBe("jest_test_pseudo_event");
|
||||||
|
expect(fakePosthog.capture.mock.calls[0][1]["foo"]).toEqual("bar");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should not track pseudonymous messages if anonymous", async () => {
|
||||||
|
analytics.setAnonymity(Anonymity.Anonymous);
|
||||||
|
await analytics.trackPseudonymousEvent<ITestEvent>("jest_test_event", {
|
||||||
|
foo: "bar",
|
||||||
|
});
|
||||||
|
expect(fakePosthog.capture.mock.calls.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should not track any events if disabled", async () => {
|
||||||
|
analytics.setAnonymity(Anonymity.Disabled);
|
||||||
|
await analytics.trackPseudonymousEvent<ITestEvent>("jest_test_event", {
|
||||||
|
foo: "bar",
|
||||||
|
});
|
||||||
|
await analytics.trackAnonymousEvent<ITestEvent>("jest_test_event", {
|
||||||
|
foo: "bar",
|
||||||
|
});
|
||||||
|
await analytics.trackRoomEvent<ITestRoomEvent>("room id", "foo", {
|
||||||
|
foo: "bar",
|
||||||
|
});
|
||||||
|
await analytics.trackPageView(200);
|
||||||
|
expect(fakePosthog.capture.mock.calls.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should pseudonymise a location of a known screen", async () => {
|
||||||
|
const location = await getRedactedCurrentLocation(
|
||||||
|
"https://foo.bar", "#/register/some/pii", "/", Anonymity.Pseudonymous);
|
||||||
|
expect(location).toBe(
|
||||||
|
`https://foo.bar/#/register/\
|
||||||
|
a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b/\
|
||||||
|
bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should anonymise a location of a known screen", async () => {
|
||||||
|
const location = await getRedactedCurrentLocation(
|
||||||
|
"https://foo.bar", "#/register/some/pii", "/", Anonymity.Anonymous);
|
||||||
|
expect(location).toBe("https://foo.bar/#/register/<redacted>/<redacted>");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should pseudonymise a location of an unknown screen", async () => {
|
||||||
|
const location = await getRedactedCurrentLocation(
|
||||||
|
"https://foo.bar", "#/not_a_screen_name/some/pii", "/", Anonymity.Pseudonymous);
|
||||||
|
expect(location).toBe(
|
||||||
|
`https://foo.bar/#/<redacted_screen_name>/\
|
||||||
|
a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b/\
|
||||||
|
bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should anonymise a location of an unknown screen", async () => {
|
||||||
|
const location = await getRedactedCurrentLocation(
|
||||||
|
"https://foo.bar", "#/not_a_screen_name/some/pii", "/", Anonymity.Anonymous);
|
||||||
|
expect(location).toBe("https://foo.bar/#/<redacted_screen_name>/<redacted>/<redacted>");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should handle an empty hash", async () => {
|
||||||
|
const location = await getRedactedCurrentLocation(
|
||||||
|
"https://foo.bar", "", "/", Anonymity.Anonymous);
|
||||||
|
expect(location).toBe("https://foo.bar/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should identify the user to posthog if pseudonymous", async () => {
|
||||||
|
analytics.setAnonymity(Anonymity.Pseudonymous);
|
||||||
|
await analytics.identifyUser("foo");
|
||||||
|
expect(fakePosthog.identify.mock.calls[0][0])
|
||||||
|
.toBe("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should not identify the user to posthog if anonymous", async () => {
|
||||||
|
analytics.setAnonymity(Anonymity.Anonymous);
|
||||||
|
await analytics.identifyUser("foo");
|
||||||
|
expect(fakePosthog.identify.mock.calls.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -106,7 +106,7 @@ describe('MemberEventListSummary', function() {
|
||||||
const result = wrapper.props.children;
|
const result = wrapper.props.children;
|
||||||
|
|
||||||
expect(result.props.children).toEqual([
|
expect(result.props.children).toEqual([
|
||||||
<div className="event_tile" key="event0">Expanded membership</div>,
|
<div className="event_tile" key="event0">Expanded membership</div>,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -129,8 +129,8 @@ describe('MemberEventListSummary', function() {
|
||||||
const result = wrapper.props.children;
|
const result = wrapper.props.children;
|
||||||
|
|
||||||
expect(result.props.children).toEqual([
|
expect(result.props.children).toEqual([
|
||||||
<div className="event_tile" key="event0">Expanded membership</div>,
|
<div className="event_tile" key="event0">Expanded membership</div>,
|
||||||
<div className="event_tile" key="event1">Expanded membership</div>,
|
<div className="event_tile" key="event1">Expanded membership</div>,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ describe("ContentRules", function() {
|
||||||
describe("parseContentRules", function() {
|
describe("parseContentRules", function() {
|
||||||
it("should handle there being no keyword rules", function() {
|
it("should handle there being no keyword rules", function() {
|
||||||
const rules = { 'global': { 'content': [
|
const rules = { 'global': { 'content': [
|
||||||
USERNAME_RULE,
|
USERNAME_RULE,
|
||||||
] } };
|
] } };
|
||||||
const parsed = ContentRules.parseContentRules(rules);
|
const parsed = ContentRules.parseContentRules(rules);
|
||||||
expect(parsed.rules).toEqual([]);
|
expect(parsed.rules).toEqual([]);
|
||||||
|
|
|
@ -78,6 +78,7 @@ export function createTestClient() {
|
||||||
},
|
},
|
||||||
mxcUrlToHttp: (mxc) => 'http://this.is.a.url/',
|
mxcUrlToHttp: (mxc) => 'http://this.is.a.url/',
|
||||||
setAccountData: jest.fn(),
|
setAccountData: jest.fn(),
|
||||||
|
setRoomAccountData: jest.fn(),
|
||||||
sendTyping: jest.fn().mockResolvedValue({}),
|
sendTyping: jest.fn().mockResolvedValue({}),
|
||||||
sendMessage: () => jest.fn().mockResolvedValue({}),
|
sendMessage: () => jest.fn().mockResolvedValue({}),
|
||||||
getSyncState: () => "SYNCING",
|
getSyncState: () => "SYNCING",
|
||||||
|
@ -95,6 +96,7 @@ export function createTestClient() {
|
||||||
getItem: jest.fn(),
|
getItem: jest.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
pushRules: {},
|
||||||
decryptEventIfNeeded: () => Promise.resolve(),
|
decryptEventIfNeeded: () => Promise.resolve(),
|
||||||
isUserIgnored: jest.fn().mockReturnValue(false),
|
isUserIgnored: jest.fn().mockReturnValue(false),
|
||||||
getCapabilities: jest.fn().mockResolvedValue({}),
|
getCapabilities: jest.fn().mockResolvedValue({}),
|
||||||
|
@ -129,8 +131,8 @@ export function mkEvent(opts) {
|
||||||
if (opts.skey) {
|
if (opts.skey) {
|
||||||
event.state_key = opts.skey;
|
event.state_key = opts.skey;
|
||||||
} else if (["m.room.name", "m.room.topic", "m.room.create", "m.room.join_rules",
|
} else if (["m.room.name", "m.room.topic", "m.room.create", "m.room.join_rules",
|
||||||
"m.room.power_levels", "m.room.topic", "m.room.history_visibility", "m.room.encryption",
|
"m.room.power_levels", "m.room.topic", "m.room.history_visibility", "m.room.encryption",
|
||||||
"com.example.state"].indexOf(opts.type) !== -1) {
|
"com.example.state"].indexOf(opts.type) !== -1) {
|
||||||
event.state_key = "";
|
event.state_key = "";
|
||||||
}
|
}
|
||||||
return opts.event ? new MatrixEvent(event) : event;
|
return opts.event ? new MatrixEvent(event) : event;
|
||||||
|
|
|
@ -49,7 +49,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() {
|
||||||
|
|
||||||
it.each(
|
it.each(
|
||||||
[[true, true], [true, false],
|
[[true, true], [true, false],
|
||||||
[false, true], [false, false]],
|
[false, true], [false, false]],
|
||||||
)("2 unverified: returns 'normal', self-trust = %s, DM = %s", async (trusted, dm) => {
|
)("2 unverified: returns 'normal', self-trust = %s, DM = %s", async (trusted, dm) => {
|
||||||
const client = mkClient(trusted);
|
const client = mkClient(trusted);
|
||||||
const room = {
|
const room = {
|
||||||
|
@ -62,7 +62,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() {
|
||||||
|
|
||||||
it.each(
|
it.each(
|
||||||
[["verified", true, true], ["verified", true, false],
|
[["verified", true, true], ["verified", true, false],
|
||||||
["verified", false, true], ["warning", false, false]],
|
["verified", false, true], ["warning", false, false]],
|
||||||
)("2 verified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
)("2 verified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||||
const client = mkClient(trusted);
|
const client = mkClient(trusted);
|
||||||
const room = {
|
const room = {
|
||||||
|
@ -75,7 +75,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() {
|
||||||
|
|
||||||
it.each(
|
it.each(
|
||||||
[["normal", true, true], ["normal", true, false],
|
[["normal", true, true], ["normal", true, false],
|
||||||
["normal", false, true], ["warning", false, false]],
|
["normal", false, true], ["warning", false, false]],
|
||||||
)("2 mixed: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
)("2 mixed: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||||
const client = mkClient(trusted);
|
const client = mkClient(trusted);
|
||||||
const room = {
|
const room = {
|
||||||
|
@ -88,7 +88,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() {
|
||||||
|
|
||||||
it.each(
|
it.each(
|
||||||
[["verified", true, true], ["verified", true, false],
|
[["verified", true, true], ["verified", true, false],
|
||||||
["warning", false, true], ["warning", false, false]],
|
["warning", false, true], ["warning", false, false]],
|
||||||
)("0 others: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
)("0 others: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||||
const client = mkClient(trusted);
|
const client = mkClient(trusted);
|
||||||
const room = {
|
const room = {
|
||||||
|
@ -101,7 +101,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() {
|
||||||
|
|
||||||
it.each(
|
it.each(
|
||||||
[["verified", true, true], ["verified", true, false],
|
[["verified", true, true], ["verified", true, false],
|
||||||
["verified", false, true], ["verified", false, false]],
|
["verified", false, true], ["verified", false, false]],
|
||||||
)("1 verified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
)("1 verified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||||
const client = mkClient(trusted);
|
const client = mkClient(trusted);
|
||||||
const room = {
|
const room = {
|
||||||
|
@ -114,7 +114,7 @@ describe("shieldStatusForMembership self-trust behaviour", function() {
|
||||||
|
|
||||||
it.each(
|
it.each(
|
||||||
[["normal", true, true], ["normal", true, false],
|
[["normal", true, true], ["normal", true, false],
|
||||||
["normal", false, true], ["normal", false, false]],
|
["normal", false, true], ["normal", false, false]],
|
||||||
)("1 unverified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
)("1 unverified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||||
const client = mkClient(trusted);
|
const client = mkClient(trusted);
|
||||||
const room = {
|
const room = {
|
||||||
|
|
|
@ -22,10 +22,10 @@
|
||||||
"es2019",
|
"es2019",
|
||||||
"dom",
|
"dom",
|
||||||
"dom.iterable"
|
"dom.iterable"
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./src/**/*.ts",
|
"./src/**/*.ts",
|
||||||
"./src/**/*.tsx"
|
"./src/**/*.tsx"
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|
339
yarn.lock
339
yarn.lock
|
@ -1324,6 +1324,107 @@
|
||||||
"@nodelib/fs.scandir" "2.1.5"
|
"@nodelib/fs.scandir" "2.1.5"
|
||||||
fastq "^1.6.0"
|
fastq "^1.6.0"
|
||||||
|
|
||||||
|
"@octokit/auth-token@^2.4.4":
|
||||||
|
version "2.4.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.5.tgz#568ccfb8cb46f36441fac094ce34f7a875b197f3"
|
||||||
|
integrity sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==
|
||||||
|
dependencies:
|
||||||
|
"@octokit/types" "^6.0.3"
|
||||||
|
|
||||||
|
"@octokit/core@^3.5.0":
|
||||||
|
version "3.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.5.1.tgz#8601ceeb1ec0e1b1b8217b960a413ed8e947809b"
|
||||||
|
integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==
|
||||||
|
dependencies:
|
||||||
|
"@octokit/auth-token" "^2.4.4"
|
||||||
|
"@octokit/graphql" "^4.5.8"
|
||||||
|
"@octokit/request" "^5.6.0"
|
||||||
|
"@octokit/request-error" "^2.0.5"
|
||||||
|
"@octokit/types" "^6.0.3"
|
||||||
|
before-after-hook "^2.2.0"
|
||||||
|
universal-user-agent "^6.0.0"
|
||||||
|
|
||||||
|
"@octokit/endpoint@^6.0.1":
|
||||||
|
version "6.0.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658"
|
||||||
|
integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==
|
||||||
|
dependencies:
|
||||||
|
"@octokit/types" "^6.0.3"
|
||||||
|
is-plain-object "^5.0.0"
|
||||||
|
universal-user-agent "^6.0.0"
|
||||||
|
|
||||||
|
"@octokit/graphql@^4.5.8":
|
||||||
|
version "4.6.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.6.4.tgz#0c3f5bed440822182e972317122acb65d311a5ed"
|
||||||
|
integrity sha512-SWTdXsVheRmlotWNjKzPOb6Js6tjSqA2a8z9+glDJng0Aqjzti8MEWOtuT8ZSu6wHnci7LZNuarE87+WJBG4vg==
|
||||||
|
dependencies:
|
||||||
|
"@octokit/request" "^5.6.0"
|
||||||
|
"@octokit/types" "^6.0.3"
|
||||||
|
universal-user-agent "^6.0.0"
|
||||||
|
|
||||||
|
"@octokit/openapi-types@^9.3.0":
|
||||||
|
version "9.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-9.3.0.tgz#160347858d727527901c6aae7f7d5c2414cc1f2e"
|
||||||
|
integrity sha512-oz60hhL+mDsiOWhEwrj5aWXTOMVtQgcvP+sRzX4C3cH7WOK9QSAoEtjWh0HdOf6V3qpdgAmUMxnQPluzDWR7Fw==
|
||||||
|
|
||||||
|
"@octokit/plugin-paginate-rest@^2.6.2":
|
||||||
|
version "2.15.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.15.0.tgz#9c956c3710b2bd786eb3814eaf5a2b17392c150d"
|
||||||
|
integrity sha512-/vjcb0w6ggVRtsb8OJBcRR9oEm+fpdo8RJk45khaWw/W0c8rlB2TLCLyZt/knmC17NkX7T9XdyQeEY7OHLSV1g==
|
||||||
|
dependencies:
|
||||||
|
"@octokit/types" "^6.23.0"
|
||||||
|
|
||||||
|
"@octokit/plugin-request-log@^1.0.2":
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85"
|
||||||
|
integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==
|
||||||
|
|
||||||
|
"@octokit/plugin-rest-endpoint-methods@5.6.0":
|
||||||
|
version "5.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.6.0.tgz#c28833b88d0f07bf94093405d02d43d73c7de99b"
|
||||||
|
integrity sha512-2G7lIPwjG9XnTlNhe/TRnpI8yS9K2l68W4RP/ki3wqw2+sVeTK8hItPxkqEI30VeH0UwnzpuksMU/yHxiVVctw==
|
||||||
|
dependencies:
|
||||||
|
"@octokit/types" "^6.23.0"
|
||||||
|
deprecation "^2.3.1"
|
||||||
|
|
||||||
|
"@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0":
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677"
|
||||||
|
integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==
|
||||||
|
dependencies:
|
||||||
|
"@octokit/types" "^6.0.3"
|
||||||
|
deprecation "^2.0.0"
|
||||||
|
once "^1.4.0"
|
||||||
|
|
||||||
|
"@octokit/request@^5.6.0":
|
||||||
|
version "5.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.0.tgz#6084861b6e4fa21dc40c8e2a739ec5eff597e672"
|
||||||
|
integrity sha512-4cPp/N+NqmaGQwbh3vUsYqokQIzt7VjsgTYVXiwpUP2pxd5YiZB2XuTedbb0SPtv9XS7nzAKjAuQxmY8/aZkiA==
|
||||||
|
dependencies:
|
||||||
|
"@octokit/endpoint" "^6.0.1"
|
||||||
|
"@octokit/request-error" "^2.1.0"
|
||||||
|
"@octokit/types" "^6.16.1"
|
||||||
|
is-plain-object "^5.0.0"
|
||||||
|
node-fetch "^2.6.1"
|
||||||
|
universal-user-agent "^6.0.0"
|
||||||
|
|
||||||
|
"@octokit/rest@^18.6.7":
|
||||||
|
version "18.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.8.0.tgz#ba24f7ba554f015a7ae2b7cc2aecef5386ddfea5"
|
||||||
|
integrity sha512-lsuNRhgzGnLMn/NmQTNCit/6jplFWiTUlPXhqN0zCMLwf2/9pseHzsnTW+Cjlp4bLMEJJNPa5JOzSLbSCOahKw==
|
||||||
|
dependencies:
|
||||||
|
"@octokit/core" "^3.5.0"
|
||||||
|
"@octokit/plugin-paginate-rest" "^2.6.2"
|
||||||
|
"@octokit/plugin-request-log" "^1.0.2"
|
||||||
|
"@octokit/plugin-rest-endpoint-methods" "5.6.0"
|
||||||
|
|
||||||
|
"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.23.0":
|
||||||
|
version "6.23.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.23.0.tgz#b39f242b20036e89fa8f34f7962b4e9b7ff8f65b"
|
||||||
|
integrity sha512-eG3clC31GSS7K3oBK6C6o7wyXPrkP+mu++eus8CSZdpRytJ5PNszYxudOQ0spWZQ3S9KAtoTG6v1WK5prJcJrA==
|
||||||
|
dependencies:
|
||||||
|
"@octokit/openapi-types" "^9.3.0"
|
||||||
|
|
||||||
"@peculiar/asn1-schema@^2.0.27", "@peculiar/asn1-schema@^2.0.32":
|
"@peculiar/asn1-schema@^2.0.27", "@peculiar/asn1-schema@^2.0.32":
|
||||||
version "2.0.37"
|
version "2.0.37"
|
||||||
resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.0.37.tgz#700476512ab903d809f64a3040fb1b2fe6fb6d4b"
|
resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.0.37.tgz#700476512ab903d809f64a3040fb1b2fe6fb6d4b"
|
||||||
|
@ -1352,6 +1453,11 @@
|
||||||
tslib "^2.2.0"
|
tslib "^2.2.0"
|
||||||
webcrypto-core "^1.2.0"
|
webcrypto-core "^1.2.0"
|
||||||
|
|
||||||
|
"@sentry/types@^6.10.0":
|
||||||
|
version "6.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.10.0.tgz#6b1f44e5ed4dbc2710bead24d1b32fb08daf04e1"
|
||||||
|
integrity sha512-M7s0JFgG7/6/yNVYoPUbxzaXDhnzyIQYRRJJKRaTD77YO4MHvi4Ke8alBWqD5fer0cPIfcSkBqa9BLdqRqcMWw==
|
||||||
|
|
||||||
"@sinonjs/commons@^1.7.0":
|
"@sinonjs/commons@^1.7.0":
|
||||||
version "1.8.3"
|
version "1.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
|
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
|
||||||
|
@ -1850,6 +1956,17 @@ ajv@^8.0.1:
|
||||||
require-from-string "^2.0.2"
|
require-from-string "^2.0.2"
|
||||||
uri-js "^4.2.2"
|
uri-js "^4.2.2"
|
||||||
|
|
||||||
|
"allchange@github:matrix-org/allchange":
|
||||||
|
version "0.0.1"
|
||||||
|
resolved "https://codeload.github.com/matrix-org/allchange/tar.gz/56b37b06339a3ac3fe771f3ec3d0bff798df8dab"
|
||||||
|
dependencies:
|
||||||
|
"@octokit/rest" "^18.6.7"
|
||||||
|
cli-color "^2.0.0"
|
||||||
|
js-yaml "^4.1.0"
|
||||||
|
loglevel "^1.7.1"
|
||||||
|
semver "^7.3.5"
|
||||||
|
yargs "^17.0.1"
|
||||||
|
|
||||||
another-json@^0.2.0:
|
another-json@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/another-json/-/another-json-0.2.0.tgz#b5f4019c973b6dd5c6506a2d93469cb6d32aeedc"
|
resolved "https://registry.yarnpkg.com/another-json/-/another-json-0.2.0.tgz#b5f4019c973b6dd5c6506a2d93469cb6d32aeedc"
|
||||||
|
@ -1867,6 +1984,11 @@ ansi-escapes@^4.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
type-fest "^0.21.3"
|
type-fest "^0.21.3"
|
||||||
|
|
||||||
|
ansi-regex@^2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
|
||||||
|
integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
|
||||||
|
|
||||||
ansi-regex@^4.1.0:
|
ansi-regex@^4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
|
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
|
||||||
|
@ -1914,6 +2036,11 @@ argparse@^1.0.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
sprintf-js "~1.0.2"
|
sprintf-js "~1.0.2"
|
||||||
|
|
||||||
|
argparse@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
|
||||||
|
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||||
|
|
||||||
arr-diff@^4.0.0:
|
arr-diff@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
|
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
|
||||||
|
@ -2206,6 +2333,11 @@ bcrypt-pbkdf@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
tweetnacl "^0.14.3"
|
tweetnacl "^0.14.3"
|
||||||
|
|
||||||
|
before-after-hook@^2.2.0:
|
||||||
|
version "2.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e"
|
||||||
|
integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==
|
||||||
|
|
||||||
binary-extensions@^1.0.0:
|
binary-extensions@^1.0.0:
|
||||||
version "1.13.1"
|
version "1.13.1"
|
||||||
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"
|
||||||
|
@ -2506,6 +2638,18 @@ classnames@*, classnames@^2.2.6:
|
||||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
|
||||||
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
|
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
|
||||||
|
|
||||||
|
cli-color@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.0.tgz#11ecfb58a79278cf6035a60c54e338f9d837897c"
|
||||||
|
integrity sha512-a0VZ8LeraW0jTuCkuAGMNufareGHhyZU9z8OGsW0gXd1hZGi1SRuNRXdbGkraBBKnhyUhyebFWnRbp+dIn0f0A==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^2.1.1"
|
||||||
|
d "^1.0.1"
|
||||||
|
es5-ext "^0.10.51"
|
||||||
|
es6-iterator "^2.0.3"
|
||||||
|
memoizee "^0.4.14"
|
||||||
|
timers-ext "^0.1.7"
|
||||||
|
|
||||||
cliui@^5.0.0:
|
cliui@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
|
resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
|
||||||
|
@ -2524,6 +2668,15 @@ cliui@^6.0.0:
|
||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
wrap-ansi "^6.2.0"
|
wrap-ansi "^6.2.0"
|
||||||
|
|
||||||
|
cliui@^7.0.2:
|
||||||
|
version "7.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
|
||||||
|
integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
|
||||||
|
dependencies:
|
||||||
|
string-width "^4.2.0"
|
||||||
|
strip-ansi "^6.0.0"
|
||||||
|
wrap-ansi "^7.0.0"
|
||||||
|
|
||||||
clone-deep@^4.0.1:
|
clone-deep@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
|
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
|
||||||
|
@ -2788,6 +2941,14 @@ csstype@^3.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340"
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340"
|
||||||
integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==
|
integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==
|
||||||
|
|
||||||
|
d@1, d@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
|
||||||
|
integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==
|
||||||
|
dependencies:
|
||||||
|
es5-ext "^0.10.50"
|
||||||
|
type "^1.0.1"
|
||||||
|
|
||||||
dashdash@^1.12.0:
|
dashdash@^1.12.0:
|
||||||
version "1.14.1"
|
version "1.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||||
|
@ -2895,6 +3056,11 @@ delayed-stream@~1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||||
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
|
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
|
||||||
|
|
||||||
|
deprecation@^2.0.0, deprecation@^2.3.1:
|
||||||
|
version "2.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
|
||||||
|
integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
|
||||||
|
|
||||||
detect-newline@^3.0.0:
|
detect-newline@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
|
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
|
||||||
|
@ -3191,6 +3357,42 @@ es-to-primitive@^1.2.1:
|
||||||
is-date-object "^1.0.1"
|
is-date-object "^1.0.1"
|
||||||
is-symbol "^1.0.2"
|
is-symbol "^1.0.2"
|
||||||
|
|
||||||
|
es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.51, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
|
||||||
|
version "0.10.53"
|
||||||
|
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1"
|
||||||
|
integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==
|
||||||
|
dependencies:
|
||||||
|
es6-iterator "~2.0.3"
|
||||||
|
es6-symbol "~3.1.3"
|
||||||
|
next-tick "~1.0.0"
|
||||||
|
|
||||||
|
es6-iterator@^2.0.3, es6-iterator@~2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
|
||||||
|
integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c=
|
||||||
|
dependencies:
|
||||||
|
d "1"
|
||||||
|
es5-ext "^0.10.35"
|
||||||
|
es6-symbol "^3.1.1"
|
||||||
|
|
||||||
|
es6-symbol@^3.1.1, es6-symbol@~3.1.3:
|
||||||
|
version "3.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18"
|
||||||
|
integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==
|
||||||
|
dependencies:
|
||||||
|
d "^1.0.1"
|
||||||
|
ext "^1.1.2"
|
||||||
|
|
||||||
|
es6-weak-map@^2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53"
|
||||||
|
integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==
|
||||||
|
dependencies:
|
||||||
|
d "1"
|
||||||
|
es5-ext "^0.10.46"
|
||||||
|
es6-iterator "^2.0.3"
|
||||||
|
es6-symbol "^3.1.1"
|
||||||
|
|
||||||
escalade@^3.1.1:
|
escalade@^3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||||
|
@ -3233,9 +3435,9 @@ eslint-config-google@^0.14.0:
|
||||||
resolved "https://registry.yarnpkg.com/eslint-config-google/-/eslint-config-google-0.14.0.tgz#4f5f8759ba6e11b424294a219dbfa18c508bcc1a"
|
resolved "https://registry.yarnpkg.com/eslint-config-google/-/eslint-config-google-0.14.0.tgz#4f5f8759ba6e11b424294a219dbfa18c508bcc1a"
|
||||||
integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==
|
integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==
|
||||||
|
|
||||||
"eslint-plugin-matrix-org@github:matrix-org/eslint-plugin-matrix-org#main":
|
"eslint-plugin-matrix-org@github:matrix-org/eslint-plugin-matrix-org#2306b3d4da4eba908b256014b979f1d3d43d2945":
|
||||||
version "0.3.4"
|
version "0.3.5"
|
||||||
resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/45f6937539192e3820edcafc4d6d4d4187e85a6a"
|
resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/2306b3d4da4eba908b256014b979f1d3d43d2945"
|
||||||
|
|
||||||
eslint-plugin-react-hooks@^4.2.0:
|
eslint-plugin-react-hooks@^4.2.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
|
@ -3383,6 +3585,14 @@ esutils@^2.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||||
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
||||||
|
|
||||||
|
event-emitter@^0.3.5:
|
||||||
|
version "0.3.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
|
||||||
|
integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=
|
||||||
|
dependencies:
|
||||||
|
d "1"
|
||||||
|
es5-ext "~0.10.14"
|
||||||
|
|
||||||
events@^3.2.0:
|
events@^3.2.0:
|
||||||
version "3.3.0"
|
version "3.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
|
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
|
||||||
|
@ -3478,6 +3688,13 @@ expect@^26.6.2:
|
||||||
jest-message-util "^26.6.2"
|
jest-message-util "^26.6.2"
|
||||||
jest-regex-util "^26.0.0"
|
jest-regex-util "^26.0.0"
|
||||||
|
|
||||||
|
ext@^1.1.2:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244"
|
||||||
|
integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==
|
||||||
|
dependencies:
|
||||||
|
type "^2.0.0"
|
||||||
|
|
||||||
extend-shallow@^2.0.1:
|
extend-shallow@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
|
resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
|
||||||
|
@ -3601,6 +3818,11 @@ fbjs@^0.8.4:
|
||||||
setimmediate "^1.0.5"
|
setimmediate "^1.0.5"
|
||||||
ua-parser-js "^0.7.18"
|
ua-parser-js "^0.7.18"
|
||||||
|
|
||||||
|
fflate@^0.4.1:
|
||||||
|
version "0.4.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
|
||||||
|
integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==
|
||||||
|
|
||||||
file-entry-cache@^6.0.0, file-entry-cache@^6.0.1:
|
file-entry-cache@^6.0.0, file-entry-cache@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
|
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
|
||||||
|
@ -3783,7 +4005,7 @@ gensync@^1.0.0-beta.2:
|
||||||
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
|
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
|
||||||
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
|
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
|
||||||
|
|
||||||
get-caller-file@^2.0.1:
|
get-caller-file@^2.0.1, get-caller-file@^2.0.5:
|
||||||
version "2.0.5"
|
version "2.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||||
|
@ -4502,6 +4724,11 @@ is-potential-custom-element-name@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
|
resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
|
||||||
integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
|
integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
|
||||||
|
|
||||||
|
is-promise@^2.2.2:
|
||||||
|
version "2.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
|
||||||
|
integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
|
||||||
|
|
||||||
is-regex@^1.0.3, is-regex@^1.0.5, is-regex@^1.1.2, is-regex@^1.1.3:
|
is-regex@^1.0.3, is-regex@^1.0.5, is-regex@^1.1.2, is-regex@^1.1.3:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f"
|
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f"
|
||||||
|
@ -5122,6 +5349,13 @@ js-yaml@^3.13.1:
|
||||||
argparse "^1.0.7"
|
argparse "^1.0.7"
|
||||||
esprima "^4.0.0"
|
esprima "^4.0.0"
|
||||||
|
|
||||||
|
js-yaml@^4.1.0:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
|
||||||
|
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
|
||||||
|
dependencies:
|
||||||
|
argparse "^2.0.1"
|
||||||
|
|
||||||
jsbn@~0.1.0:
|
jsbn@~0.1.0:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
||||||
|
@ -5396,6 +5630,13 @@ lru-cache@^6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
yallist "^4.0.0"
|
yallist "^4.0.0"
|
||||||
|
|
||||||
|
lru-queue@^0.1.0:
|
||||||
|
version "0.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3"
|
||||||
|
integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=
|
||||||
|
dependencies:
|
||||||
|
es5-ext "~0.10.2"
|
||||||
|
|
||||||
make-dir@^2.0.0, make-dir@^2.1.0:
|
make-dir@^2.0.0, make-dir@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
|
||||||
|
@ -5528,6 +5769,20 @@ memoize-one@^5.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
|
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
|
||||||
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
|
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
|
||||||
|
|
||||||
|
memoizee@^0.4.14:
|
||||||
|
version "0.4.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72"
|
||||||
|
integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==
|
||||||
|
dependencies:
|
||||||
|
d "^1.0.1"
|
||||||
|
es5-ext "^0.10.53"
|
||||||
|
es6-weak-map "^2.0.3"
|
||||||
|
event-emitter "^0.3.5"
|
||||||
|
is-promise "^2.2.2"
|
||||||
|
lru-queue "^0.1.0"
|
||||||
|
next-tick "^1.1.0"
|
||||||
|
timers-ext "^0.1.7"
|
||||||
|
|
||||||
meow@^9.0.0:
|
meow@^9.0.0:
|
||||||
version "9.0.0"
|
version "9.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364"
|
resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364"
|
||||||
|
@ -5701,12 +5956,22 @@ nearley@^2.7.10:
|
||||||
railroad-diagrams "^1.0.0"
|
railroad-diagrams "^1.0.0"
|
||||||
randexp "0.4.6"
|
randexp "0.4.6"
|
||||||
|
|
||||||
|
next-tick@1, next-tick@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
|
||||||
|
integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
|
||||||
|
|
||||||
|
next-tick@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
|
||||||
|
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
|
||||||
|
|
||||||
nice-try@^1.0.4:
|
nice-try@^1.0.4:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
||||||
|
|
||||||
node-fetch@2.6.1:
|
node-fetch@2.6.1, node-fetch@^2.6.1:
|
||||||
version "2.6.1"
|
version "2.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||||
|
@ -6249,6 +6514,13 @@ postcss@^8.0.2:
|
||||||
nanoid "^3.1.23"
|
nanoid "^3.1.23"
|
||||||
source-map-js "^0.6.2"
|
source-map-js "^0.6.2"
|
||||||
|
|
||||||
|
posthog-js@1.12.2:
|
||||||
|
version "1.12.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.12.2.tgz#ff76e26634067e003f8af7df654d7ea0e647d946"
|
||||||
|
integrity sha512-I0d6c+Yu2f91PFidz65AIkkqZM219EY9Z1wlbTkW5Zqfq5oXqogBMKS8BaDBOrMc46LjLX7IH67ytCcBFRo1uw==
|
||||||
|
dependencies:
|
||||||
|
fflate "^0.4.1"
|
||||||
|
|
||||||
prelude-ls@^1.2.1:
|
prelude-ls@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||||
|
@ -6829,6 +7101,11 @@ rimraf@^3.0.0, rimraf@^3.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
glob "^7.1.3"
|
glob "^7.1.3"
|
||||||
|
|
||||||
|
rrweb-snapshot@1.1.7:
|
||||||
|
version "1.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/rrweb-snapshot/-/rrweb-snapshot-1.1.7.tgz#92a3b47b1112a1b566c2fae2edb02fa48a6f6653"
|
||||||
|
integrity sha512-+f2kCCvIQ1hbEeCWnV7mPVPDEdWEExqwcYqMd/r1nfK52QE7qU52jefUOyTe85Vy67rZGqWnfK/B25e/OTSgYg==
|
||||||
|
|
||||||
rst-selector-parser@^2.2.3:
|
rst-selector-parser@^2.2.3:
|
||||||
version "2.2.3"
|
version "2.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91"
|
resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91"
|
||||||
|
@ -7498,6 +7775,14 @@ throat@^5.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
|
resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
|
||||||
integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==
|
integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==
|
||||||
|
|
||||||
|
timers-ext@^0.1.7:
|
||||||
|
version "0.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6"
|
||||||
|
integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==
|
||||||
|
dependencies:
|
||||||
|
es5-ext "~0.10.46"
|
||||||
|
next-tick "1"
|
||||||
|
|
||||||
tiny-invariant@^1.0.6:
|
tiny-invariant@^1.0.6:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
|
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
|
||||||
|
@ -7657,6 +7942,16 @@ type-fest@^0.8.1:
|
||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||||
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
||||||
|
|
||||||
|
type@^1.0.1:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
|
||||||
|
integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
|
||||||
|
|
||||||
|
type@^2.0.0:
|
||||||
|
version "2.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d"
|
||||||
|
integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==
|
||||||
|
|
||||||
typedarray-to-buffer@^3.1.5:
|
typedarray-to-buffer@^3.1.5:
|
||||||
version "3.1.5"
|
version "3.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
|
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
|
||||||
|
@ -7753,6 +8048,11 @@ unist-util-stringify-position@^2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/unist" "^2.0.2"
|
"@types/unist" "^2.0.2"
|
||||||
|
|
||||||
|
universal-user-agent@^6.0.0:
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee"
|
||||||
|
integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==
|
||||||
|
|
||||||
universalify@^0.1.2:
|
universalify@^0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||||
|
@ -8056,6 +8356,15 @@ wrap-ansi@^6.2.0:
|
||||||
string-width "^4.1.0"
|
string-width "^4.1.0"
|
||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
|
wrap-ansi@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
|
dependencies:
|
||||||
|
ansi-styles "^4.0.0"
|
||||||
|
string-width "^4.1.0"
|
||||||
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
wrappy@1:
|
wrappy@1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||||
|
@ -8091,6 +8400,11 @@ y18n@^4.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
||||||
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
|
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
|
||||||
|
|
||||||
|
y18n@^5.0.5:
|
||||||
|
version "5.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||||
|
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
|
||||||
|
|
||||||
yallist@^4.0.0:
|
yallist@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||||
|
@ -8117,7 +8431,7 @@ yargs-parser@^18.1.2:
|
||||||
camelcase "^5.0.0"
|
camelcase "^5.0.0"
|
||||||
decamelize "^1.2.0"
|
decamelize "^1.2.0"
|
||||||
|
|
||||||
yargs-parser@^20.2.3:
|
yargs-parser@^20.2.2, yargs-parser@^20.2.3:
|
||||||
version "20.2.9"
|
version "20.2.9"
|
||||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
|
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
|
||||||
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
|
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
|
||||||
|
@ -8155,6 +8469,19 @@ yargs@^15.4.1:
|
||||||
y18n "^4.0.0"
|
y18n "^4.0.0"
|
||||||
yargs-parser "^18.1.2"
|
yargs-parser "^18.1.2"
|
||||||
|
|
||||||
|
yargs@^17.0.1:
|
||||||
|
version "17.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.0.1.tgz#6a1ced4ed5ee0b388010ba9fd67af83b9362e0bb"
|
||||||
|
integrity sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==
|
||||||
|
dependencies:
|
||||||
|
cliui "^7.0.2"
|
||||||
|
escalade "^3.1.1"
|
||||||
|
get-caller-file "^2.0.5"
|
||||||
|
require-directory "^2.1.1"
|
||||||
|
string-width "^4.2.0"
|
||||||
|
y18n "^5.0.5"
|
||||||
|
yargs-parser "^20.2.2"
|
||||||
|
|
||||||
zwitch@^1.0.0:
|
zwitch@^1.0.0:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"
|
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue