Merge branch 'develop' into johannes/find-myself
This commit is contained in:
commit
d0e9331f07
612 changed files with 3608 additions and 2769 deletions
|
@ -54,12 +54,12 @@ describe("Polls", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPollOption = (pollId: string, optionText: string): Chainable<JQuery> => {
|
const getPollOption = (pollId: string, optionText: string): Chainable<JQuery> => {
|
||||||
return getPollTile(pollId).contains(".mx_MPollBody_option .mx_StyledRadioButton", optionText);
|
return getPollTile(pollId).contains(".mx_PollOption .mx_StyledRadioButton", optionText);
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectPollOptionVoteCount = (pollId: string, optionText: string, votes: number): void => {
|
const expectPollOptionVoteCount = (pollId: string, optionText: string, votes: number): void => {
|
||||||
getPollOption(pollId, optionText).within(() => {
|
getPollOption(pollId, optionText).within(() => {
|
||||||
cy.get(".mx_MPollBody_optionVoteCount").should("contain", `${votes} vote`);
|
cy.get(".mx_PollOption_optionVoteCount").should("contain", `${votes} vote`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -154,11 +154,13 @@
|
||||||
"@types/flux": "^3.1.9",
|
"@types/flux": "^3.1.9",
|
||||||
"@types/fs-extra": "^11.0.0",
|
"@types/fs-extra": "^11.0.0",
|
||||||
"@types/geojson": "^7946.0.8",
|
"@types/geojson": "^7946.0.8",
|
||||||
|
"@types/glob-to-regexp": "^0.4.1",
|
||||||
"@types/jest": "^29.2.1",
|
"@types/jest": "^29.2.1",
|
||||||
"@types/katex": "^0.14.0",
|
"@types/katex": "^0.14.0",
|
||||||
"@types/lodash": "^4.14.168",
|
"@types/lodash": "^4.14.168",
|
||||||
"@types/modernizr": "^3.5.3",
|
"@types/modernizr": "^3.5.3",
|
||||||
"@types/node": "^16",
|
"@types/node": "^16",
|
||||||
|
"@types/node-fetch": "^2.6.2",
|
||||||
"@types/pako": "^2.0.0",
|
"@types/pako": "^2.0.0",
|
||||||
"@types/parse5": "^6.0.0",
|
"@types/parse5": "^6.0.0",
|
||||||
"@types/qrcode": "^1.3.5",
|
"@types/qrcode": "^1.3.5",
|
||||||
|
@ -168,6 +170,7 @@
|
||||||
"@types/react-test-renderer": "^17.0.1",
|
"@types/react-test-renderer": "^17.0.1",
|
||||||
"@types/react-transition-group": "^4.4.0",
|
"@types/react-transition-group": "^4.4.0",
|
||||||
"@types/sanitize-html": "^2.3.1",
|
"@types/sanitize-html": "^2.3.1",
|
||||||
|
"@types/tar-js": "^0.3.2",
|
||||||
"@types/ua-parser-js": "^0.7.36",
|
"@types/ua-parser-js": "^0.7.36",
|
||||||
"@types/zxcvbn": "^4.4.0",
|
"@types/zxcvbn": "^4.4.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.35.1",
|
"@typescript-eslint/eslint-plugin": "^5.35.1",
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
@import "./components/views/context_menus/_KebabContextMenu.pcss";
|
@import "./components/views/context_menus/_KebabContextMenu.pcss";
|
||||||
@import "./components/views/dialogs/polls/_PollListItem.pcss";
|
@import "./components/views/dialogs/polls/_PollListItem.pcss";
|
||||||
@import "./components/views/elements/_FilterDropdown.pcss";
|
@import "./components/views/elements/_FilterDropdown.pcss";
|
||||||
|
@import "./components/views/elements/_FilterTabGroup.pcss";
|
||||||
@import "./components/views/elements/_LearnMore.pcss";
|
@import "./components/views/elements/_LearnMore.pcss";
|
||||||
@import "./components/views/location/_EnableLiveShare.pcss";
|
@import "./components/views/location/_EnableLiveShare.pcss";
|
||||||
@import "./components/views/location/_LiveDurationDropdown.pcss";
|
@import "./components/views/location/_LiveDurationDropdown.pcss";
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
@import "./components/views/messages/_MBeaconBody.pcss";
|
@import "./components/views/messages/_MBeaconBody.pcss";
|
||||||
@import "./components/views/messages/shared/_MediaProcessingError.pcss";
|
@import "./components/views/messages/shared/_MediaProcessingError.pcss";
|
||||||
@import "./components/views/pips/_WidgetPip.pcss";
|
@import "./components/views/pips/_WidgetPip.pcss";
|
||||||
|
@import "./components/views/polls/_PollOption.pcss";
|
||||||
@import "./components/views/settings/devices/_CurrentDeviceSection.pcss";
|
@import "./components/views/settings/devices/_CurrentDeviceSection.pcss";
|
||||||
@import "./components/views/settings/devices/_DeviceDetailHeading.pcss";
|
@import "./components/views/settings/devices/_DeviceDetailHeading.pcss";
|
||||||
@import "./components/views/settings/devices/_DeviceDetails.pcss";
|
@import "./components/views/settings/devices/_DeviceDetails.pcss";
|
||||||
|
|
46
res/css/components/views/elements/_FilterTabGroup.pcss
Normal file
46
res/css/components/views/elements/_FilterTabGroup.pcss
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_FilterTabGroup {
|
||||||
|
color: $primary-content;
|
||||||
|
label {
|
||||||
|
margin-right: $spacing-12;
|
||||||
|
cursor: pointer;
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
line-height: $font-24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input[type="radio"] {
|
||||||
|
appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:hover {
|
||||||
|
& + span {
|
||||||
|
color: $secondary-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + span {
|
||||||
|
color: $accent;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
// underline
|
||||||
|
box-shadow: 0 1.5px 0 0 currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
109
res/css/components/views/polls/_PollOption.pcss
Normal file
109
res/css/components/views/polls/_PollOption.pcss
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_PollOption {
|
||||||
|
border: 1px solid $quinary-content;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
max-width: 550px;
|
||||||
|
background-color: $background;
|
||||||
|
|
||||||
|
.mx_StyledRadioButton_content,
|
||||||
|
.mx_PollOption_endedOption {
|
||||||
|
padding-top: 2px;
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_StyledRadioButton_spacer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_PollOption,
|
||||||
|
/* label has cursor: default in user-agent stylesheet */
|
||||||
|
/* override */
|
||||||
|
.mx_PollOption_live-option {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_PollOption_content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_PollOption_optionVoteCount {
|
||||||
|
color: $secondary-content;
|
||||||
|
font-size: $font-12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_PollOption_winnerIcon {
|
||||||
|
height: 12px;
|
||||||
|
width: 12px;
|
||||||
|
color: $accent;
|
||||||
|
margin-right: $spacing-4;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_PollOption_checked {
|
||||||
|
border-color: $accent;
|
||||||
|
|
||||||
|
.mx_PollOption_popularityBackground {
|
||||||
|
.mx_PollOption_popularityAmount {
|
||||||
|
background-color: $accent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// override checked radio button styling
|
||||||
|
// to show checkmark instead
|
||||||
|
.mx_StyledRadioButton_checked {
|
||||||
|
input[type="radio"] + div {
|
||||||
|
border-width: 2px;
|
||||||
|
border-color: $accent;
|
||||||
|
background-color: $accent;
|
||||||
|
background-image: url("$(res)/img/element-icons/check-white.svg");
|
||||||
|
background-size: 12px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
|
||||||
|
div {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* options not actionable in these states */
|
||||||
|
.mx_PollOption_checked,
|
||||||
|
.mx_PollOption_ended {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_PollOption_popularityBackground {
|
||||||
|
width: 100%;
|
||||||
|
height: 8px;
|
||||||
|
margin-right: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: $system;
|
||||||
|
margin-top: $spacing-8;
|
||||||
|
|
||||||
|
.mx_PollOption_popularityAmount {
|
||||||
|
width: 0%;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: $quaternary-content;
|
||||||
|
}
|
||||||
|
}
|
|
@ -115,6 +115,7 @@ limitations under the License.
|
||||||
padding-left: 30px; /* 18px for the icon, 2px margin to text, 10px regular padding */
|
padding-left: 30px; /* 18px for the icon, 2px margin to text, 10px regular padding */
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
&:nth-child(2) {
|
&:nth-child(2) {
|
||||||
border-left: 1px solid $resend-button-divider-color;
|
border-left: 1px solid $resend-button-divider-color;
|
||||||
|
|
|
@ -47,108 +47,6 @@ limitations under the License.
|
||||||
mask-image: url("$(res)/img/element-icons/room/composer/poll.svg");
|
mask-image: url("$(res)/img/element-icons/room/composer/poll.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MPollBody_option {
|
|
||||||
border: 1px solid $quinary-content;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
max-width: 550px;
|
|
||||||
background-color: $background;
|
|
||||||
|
|
||||||
.mx_StyledRadioButton,
|
|
||||||
.mx_MPollBody_endedOption {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_StyledRadioButton_content,
|
|
||||||
.mx_MPollBody_endedOption {
|
|
||||||
padding-top: 2px;
|
|
||||||
margin-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_StyledRadioButton_spacer {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MPollBody_optionDescription {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.mx_MPollBody_optionVoteCount {
|
|
||||||
color: $secondary-content;
|
|
||||||
font-size: $font-12px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MPollBody_popularityBackground {
|
|
||||||
width: 100%;
|
|
||||||
height: 8px;
|
|
||||||
margin-right: 12px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: $system;
|
|
||||||
|
|
||||||
.mx_MPollBody_popularityAmount {
|
|
||||||
width: 0%;
|
|
||||||
height: 8px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: $quaternary-content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MPollBody_option:last-child {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MPollBody_option_checked {
|
|
||||||
border-color: $accent;
|
|
||||||
|
|
||||||
.mx_MPollBody_popularityBackground {
|
|
||||||
.mx_MPollBody_popularityAmount {
|
|
||||||
background-color: $accent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* options not actionable in these states */
|
|
||||||
.mx_MPollBody_option_checked,
|
|
||||||
.mx_MPollBody_option_ended {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_StyledRadioButton_checked,
|
|
||||||
.mx_MPollBody_endedOptionWinner {
|
|
||||||
input[type="radio"] + div {
|
|
||||||
border-width: 2px;
|
|
||||||
border-color: $accent;
|
|
||||||
background-color: $accent;
|
|
||||||
background-image: url("$(res)/img/element-icons/check-white.svg");
|
|
||||||
background-size: 12px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
|
|
||||||
div {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MPollBody_endedOptionWinner .mx_MPollBody_optionDescription .mx_MPollBody_optionVoteCount::before {
|
|
||||||
content: "";
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 4px;
|
|
||||||
top: 2px;
|
|
||||||
height: 12px;
|
|
||||||
width: 12px;
|
|
||||||
background-color: $accent;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-size: contain;
|
|
||||||
mask-position: center;
|
|
||||||
mask-image: url("$(res)/img/element-icons/trophy.svg");
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MPollBody_totalVotes {
|
.mx_MPollBody_totalVotes {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: inline;
|
flex-direction: inline;
|
||||||
|
@ -168,9 +66,8 @@ limitations under the License.
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MPollBody_option,
|
.mx_MPollBody_allOptions {
|
||||||
/* label has cursor: default in user-agent stylesheet */
|
display: grid;
|
||||||
/* override */
|
grid-gap: $spacing-16;
|
||||||
.mx_MPollBody_live-option {
|
margin-bottom: $spacing-8;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M10.6667 1.33333H9.33333V0.666667C9.33333 0.3 9.03333 0 8.66667 0H3.33333C2.96667 0 2.66667 0.3 2.66667 0.666667V1.33333H1.33333C0.6 1.33333 0 1.93333 0 2.66667V3.33333C0 5.03333 1.28 6.42 2.92667 6.62667C3.34667 7.62667 4.24667 8.38 5.33333 8.6V10.6667H3.33333C2.96667 10.6667 2.66667 10.9667 2.66667 11.3333C2.66667 11.7 2.96667 12 3.33333 12H8.66667C9.03333 12 9.33333 11.7 9.33333 11.3333C9.33333 10.9667 9.03333 10.6667 8.66667 10.6667H6.66667V8.6C7.75333 8.38 8.65333 7.62667 9.07333 6.62667C10.72 6.42 12 5.03333 12 3.33333V2.66667C12 1.93333 11.4 1.33333 10.6667 1.33333ZM1.33333 3.33333V2.66667H2.66667V5.21333C1.89333 4.93333 1.33333 4.2 1.33333 3.33333ZM10.6667 3.33333C10.6667 4.2 10.1067 4.93333 9.33333 5.21333V2.66667H10.6667V3.33333Z" fill="#0DBD8B"/>
|
<path d="M10.6667 1.33333H9.33333V0.666667C9.33333 0.3 9.03333 0 8.66667 0H3.33333C2.96667 0 2.66667 0.3 2.66667 0.666667V1.33333H1.33333C0.6 1.33333 0 1.93333 0 2.66667V3.33333C0 5.03333 1.28 6.42 2.92667 6.62667C3.34667 7.62667 4.24667 8.38 5.33333 8.6V10.6667H3.33333C2.96667 10.6667 2.66667 10.9667 2.66667 11.3333C2.66667 11.7 2.96667 12 3.33333 12H8.66667C9.03333 12 9.33333 11.7 9.33333 11.3333C9.33333 10.9667 9.03333 10.6667 8.66667 10.6667H6.66667V8.6C7.75333 8.38 8.65333 7.62667 9.07333 6.62667C10.72 6.42 12 5.03333 12 3.33333V2.66667C12 1.93333 11.4 1.33333 10.6667 1.33333ZM1.33333 3.33333V2.66667H2.66667V5.21333C1.89333 4.93333 1.33333 4.2 1.33333 3.33333ZM10.6667 3.33333C10.6667 4.2 10.1067 4.93333 9.33333 5.21333V2.66667H10.6667V3.33333Z" fill="currentColor"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 880 B After Width: | Height: | Size: 885 B |
2
src/@types/global.d.ts
vendored
2
src/@types/global.d.ts
vendored
|
@ -218,7 +218,7 @@ declare global {
|
||||||
processorCtor: (new (options?: AudioWorkletNodeOptions) => AudioWorkletProcessor) & {
|
processorCtor: (new (options?: AudioWorkletNodeOptions) => AudioWorkletProcessor) & {
|
||||||
parameterDescriptors?: AudioParamDescriptor[];
|
parameterDescriptors?: AudioParamDescriptor[];
|
||||||
},
|
},
|
||||||
);
|
): void;
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
// eslint-disable-next-line no-var
|
||||||
var grecaptcha:
|
var grecaptcha:
|
||||||
|
|
65
src/@types/opus-recorder.d.ts
vendored
Normal file
65
src/@types/opus-recorder.d.ts
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare module "opus-recorder/dist/recorder.min.js" {
|
||||||
|
export default class Recorder {
|
||||||
|
public static isRecordingSupported(): boolean;
|
||||||
|
|
||||||
|
public constructor(config: {
|
||||||
|
bufferLength?: number;
|
||||||
|
encoderApplication?: number;
|
||||||
|
encoderFrameSize?: number;
|
||||||
|
encoderPath?: string;
|
||||||
|
encoderSampleRate?: number;
|
||||||
|
encoderBitRate?: number;
|
||||||
|
maxFramesPerPage?: number;
|
||||||
|
mediaTrackConstraints?: boolean;
|
||||||
|
monitorGain?: number;
|
||||||
|
numberOfChannels?: number;
|
||||||
|
recordingGain?: number;
|
||||||
|
resampleQuality?: number;
|
||||||
|
streamPages?: boolean;
|
||||||
|
wavBitDepth?: number;
|
||||||
|
sourceNode?: MediaStreamAudioSourceNode;
|
||||||
|
encoderComplexity?: number;
|
||||||
|
});
|
||||||
|
|
||||||
|
public ondataavailable?(data: ArrayBuffer): void;
|
||||||
|
|
||||||
|
public readonly encodedSamplePosition: number;
|
||||||
|
|
||||||
|
public start(): Promise<void>;
|
||||||
|
|
||||||
|
public stop(): Promise<void>;
|
||||||
|
|
||||||
|
public close(): void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "opus-recorder/dist/encoderWorker.min.js" {
|
||||||
|
const path: string;
|
||||||
|
export default path;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "opus-recorder/dist/waveWorker.min.js" {
|
||||||
|
const path: string;
|
||||||
|
export default path;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "opus-recorder/dist/decoderWorker.min.js" {
|
||||||
|
const path: string;
|
||||||
|
export default path;
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix";
|
import { IAuthData, IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { MatrixClientPeg } from "./MatrixClientPeg";
|
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||||
import Modal from "./Modal";
|
import Modal from "./Modal";
|
||||||
|
@ -29,6 +29,12 @@ function getIdServerDomain(): string {
|
||||||
return MatrixClientPeg.get().idBaseUrl.split("://")[1];
|
return MatrixClientPeg.get().idBaseUrl.split("://")[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Binding = {
|
||||||
|
bind: boolean;
|
||||||
|
label: string;
|
||||||
|
errorTitle: string;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows a user to add a third party identifier to their homeserver and,
|
* Allows a user to add a third party identifier to their homeserver and,
|
||||||
* optionally, the identity servers.
|
* optionally, the identity servers.
|
||||||
|
@ -178,7 +184,7 @@ export default class AddThreepid {
|
||||||
* with a "message" property which contains a human-readable message detailing why
|
* with a "message" property which contains a human-readable message detailing why
|
||||||
* the request failed.
|
* the request failed.
|
||||||
*/
|
*/
|
||||||
public async checkEmailLinkClicked(): Promise<any[]> {
|
public async checkEmailLinkClicked(): Promise<[boolean, IAuthData | Error | null]> {
|
||||||
try {
|
try {
|
||||||
if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) {
|
if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) {
|
||||||
if (this.bind) {
|
if (this.bind) {
|
||||||
|
@ -220,7 +226,9 @@ export default class AddThreepid {
|
||||||
continueKind: "primary",
|
continueKind: "primary",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
|
const { finished } = Modal.createDialog<[boolean, IAuthData | Error | null]>(
|
||||||
|
InteractiveAuthDialog,
|
||||||
|
{
|
||||||
title: _t("Add Email Address"),
|
title: _t("Add Email Address"),
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
authData: e.data,
|
authData: e.data,
|
||||||
|
@ -229,7 +237,8 @@ export default class AddThreepid {
|
||||||
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||||
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
);
|
||||||
return finished;
|
return finished;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,10 +42,7 @@ interface IState {
|
||||||
export default class AsyncWrapper extends React.Component<IProps, IState> {
|
export default class AsyncWrapper extends React.Component<IProps, IState> {
|
||||||
private unmounted = false;
|
private unmounted = false;
|
||||||
|
|
||||||
public state = {
|
public state: IState = {};
|
||||||
component: null,
|
|
||||||
error: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
public componentDidMount(): void {
|
public componentDidMount(): void {
|
||||||
// XXX: temporary logging to try to diagnose
|
// XXX: temporary logging to try to diagnose
|
||||||
|
@ -77,7 +74,7 @@ export default class AsyncWrapper extends React.Component<IProps, IState> {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
if (this.state.component) {
|
if (this.state.component) {
|
||||||
const Component = this.state.component;
|
const Component = this.state.component;
|
||||||
return <Component {...this.props} />;
|
return <Component {...this.props} />;
|
||||||
|
|
|
@ -138,7 +138,7 @@ export function getInitialLetter(name: string): string | undefined {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function avatarUrlForRoom(
|
export function avatarUrlForRoom(
|
||||||
room: Room,
|
room: Room | null,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
resizeMethod?: ResizeMethod,
|
resizeMethod?: ResizeMethod,
|
||||||
|
|
|
@ -197,7 +197,7 @@ export default abstract class BasePlatform {
|
||||||
room: Room,
|
room: Room,
|
||||||
ev?: MatrixEvent,
|
ev?: MatrixEvent,
|
||||||
): Notification {
|
): Notification {
|
||||||
const notifBody = {
|
const notifBody: NotificationOptions = {
|
||||||
body: msg,
|
body: msg,
|
||||||
silent: true, // we play our own sounds
|
silent: true, // we play our own sounds
|
||||||
};
|
};
|
||||||
|
|
|
@ -204,7 +204,7 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = {
|
||||||
attribs.style += "height: 100%;";
|
attribs.style += "height: 100%;";
|
||||||
}
|
}
|
||||||
|
|
||||||
attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height);
|
attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height)!;
|
||||||
return { tagName, attribs };
|
return { tagName, attribs };
|
||||||
},
|
},
|
||||||
"code": function (tagName: string, attribs: sanitizeHtml.Attributes) {
|
"code": function (tagName: string, attribs: sanitizeHtml.Attributes) {
|
||||||
|
@ -228,7 +228,7 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = {
|
||||||
|
|
||||||
// Sanitise and transform data-mx-color and data-mx-bg-color to their CSS
|
// Sanitise and transform data-mx-color and data-mx-bg-color to their CSS
|
||||||
// equivalents
|
// equivalents
|
||||||
const customCSSMapper = {
|
const customCSSMapper: Record<string, string> = {
|
||||||
"data-mx-color": "color",
|
"data-mx-color": "color",
|
||||||
"data-mx-bg-color": "background-color",
|
"data-mx-bg-color": "background-color",
|
||||||
// $customAttributeKey: $cssAttributeKey
|
// $customAttributeKey: $cssAttributeKey
|
||||||
|
@ -352,7 +352,7 @@ const topicSanitizeHtmlParams: IExtendedSanitizeOptions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
abstract class BaseHighlighter<T extends React.ReactNode> {
|
abstract class BaseHighlighter<T extends React.ReactNode> {
|
||||||
public constructor(public highlightClass: string, public highlightLink: string) {}
|
public constructor(public highlightClass: string, public highlightLink?: string) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* apply the highlights to a section of text
|
* apply the highlights to a section of text
|
||||||
|
@ -504,7 +504,7 @@ function formatEmojis(message: string, isHtmlMessage: boolean): (JSX.Element | s
|
||||||
export function bodyToHtml(content: IContent, highlights: Optional<string[]>, opts: IOptsReturnString): string;
|
export function bodyToHtml(content: IContent, highlights: Optional<string[]>, opts: IOptsReturnString): string;
|
||||||
export function bodyToHtml(content: IContent, highlights: Optional<string[]>, opts: IOptsReturnNode): ReactNode;
|
export function bodyToHtml(content: IContent, highlights: Optional<string[]>, opts: IOptsReturnNode): ReactNode;
|
||||||
export function bodyToHtml(content: IContent, highlights: Optional<string[]>, opts: IOpts = {}): ReactNode | string {
|
export function bodyToHtml(content: IContent, highlights: Optional<string[]>, opts: IOpts = {}): ReactNode | string {
|
||||||
const isFormattedBody = content.format === "org.matrix.custom.html" && !!content.formatted_body;
|
const isFormattedBody = content.format === "org.matrix.custom.html" && typeof content.formatted_body === "string";
|
||||||
let bodyHasEmoji = false;
|
let bodyHasEmoji = false;
|
||||||
let isHtmlMessage = false;
|
let isHtmlMessage = false;
|
||||||
|
|
||||||
|
@ -514,7 +514,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
|
||||||
}
|
}
|
||||||
|
|
||||||
let strippedBody: string;
|
let strippedBody: string;
|
||||||
let safeBody: string; // safe, sanitised HTML, preferred over `strippedBody` which is fully plaintext
|
let safeBody: string | undefined; // safe, sanitised HTML, preferred over `strippedBody` which is fully plaintext
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// sanitizeHtml can hang if an unclosed HTML tag is thrown at it
|
// sanitizeHtml can hang if an unclosed HTML tag is thrown at it
|
||||||
|
@ -529,7 +529,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
|
||||||
|
|
||||||
if (opts.stripReplyFallback && formattedBody) formattedBody = stripHTMLReply(formattedBody);
|
if (opts.stripReplyFallback && formattedBody) formattedBody = stripHTMLReply(formattedBody);
|
||||||
strippedBody = opts.stripReplyFallback ? stripPlainReply(plainBody) : plainBody;
|
strippedBody = opts.stripReplyFallback ? stripPlainReply(plainBody) : plainBody;
|
||||||
bodyHasEmoji = mightContainEmoji(isFormattedBody ? formattedBody : plainBody);
|
bodyHasEmoji = mightContainEmoji(isFormattedBody ? formattedBody! : plainBody);
|
||||||
|
|
||||||
const highlighter = safeHighlights?.length
|
const highlighter = safeHighlights?.length
|
||||||
? new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink)
|
? new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink)
|
||||||
|
@ -543,11 +543,11 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
|
||||||
// by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either
|
// by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either
|
||||||
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeParams structure.
|
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeParams structure.
|
||||||
sanitizeParams.textFilter = function (safeText) {
|
sanitizeParams.textFilter = function (safeText) {
|
||||||
return highlighter.applyHighlights(safeText, safeHighlights).join("");
|
return highlighter.applyHighlights(safeText, safeHighlights!).join("");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
safeBody = sanitizeHtml(formattedBody, sanitizeParams);
|
safeBody = sanitizeHtml(formattedBody!, sanitizeParams);
|
||||||
const phtml = cheerio.load(safeBody, {
|
const phtml = cheerio.load(safeBody, {
|
||||||
// @ts-ignore: The `_useHtmlParser2` internal option is the
|
// @ts-ignore: The `_useHtmlParser2` internal option is the
|
||||||
// simplest way to both parse and render using `htmlparser2`.
|
// simplest way to both parse and render using `htmlparser2`.
|
||||||
|
@ -574,7 +574,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
|
||||||
safeBody = formatEmojis(safeBody, true).join("");
|
safeBody = formatEmojis(safeBody, true).join("");
|
||||||
}
|
}
|
||||||
} else if (highlighter) {
|
} else if (highlighter) {
|
||||||
safeBody = highlighter.applyHighlights(plainBody, safeHighlights).join("");
|
safeBody = highlighter.applyHighlights(plainBody, safeHighlights!).join("");
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
delete sanitizeParams.textFilter;
|
delete sanitizeParams.textFilter;
|
||||||
|
@ -597,9 +597,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
|
||||||
|
|
||||||
const match = BIGEMOJI_REGEX.exec(contentBodyTrimmed);
|
const match = BIGEMOJI_REGEX.exec(contentBodyTrimmed);
|
||||||
emojiBody =
|
emojiBody =
|
||||||
match &&
|
match?.[0]?.length === contentBodyTrimmed.length &&
|
||||||
match[0] &&
|
|
||||||
match[0].length === contentBodyTrimmed.length &&
|
|
||||||
// Prevent user pills expanding for users with only emoji in
|
// Prevent user pills expanding for users with only emoji in
|
||||||
// their username. Permalinks (links in pills) can be any URL
|
// their username. Permalinks (links in pills) can be any URL
|
||||||
// now, so we just check for an HTTP-looking thing.
|
// now, so we just check for an HTTP-looking thing.
|
||||||
|
@ -614,7 +612,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
|
||||||
"markdown-body": isHtmlMessage && !emojiBody,
|
"markdown-body": isHtmlMessage && !emojiBody,
|
||||||
});
|
});
|
||||||
|
|
||||||
let emojiBodyElements: JSX.Element[];
|
let emojiBodyElements: JSX.Element[] | undefined;
|
||||||
if (!safeBody && bodyHasEmoji) {
|
if (!safeBody && bodyHasEmoji) {
|
||||||
emojiBodyElements = formatEmojis(strippedBody, false) as JSX.Element[];
|
emojiBodyElements = formatEmojis(strippedBody, false) as JSX.Element[];
|
||||||
}
|
}
|
||||||
|
@ -649,7 +647,7 @@ export function topicToHtml(
|
||||||
allowExtendedHtml = false,
|
allowExtendedHtml = false,
|
||||||
): ReactNode {
|
): ReactNode {
|
||||||
if (!SettingsStore.getValue("feature_html_topic")) {
|
if (!SettingsStore.getValue("feature_html_topic")) {
|
||||||
htmlTopic = null;
|
htmlTopic = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let isFormattedTopic = !!htmlTopic;
|
let isFormattedTopic = !!htmlTopic;
|
||||||
|
@ -657,10 +655,10 @@ export function topicToHtml(
|
||||||
let safeTopic = "";
|
let safeTopic = "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
topicHasEmoji = mightContainEmoji(isFormattedTopic ? htmlTopic : topic);
|
topicHasEmoji = mightContainEmoji(isFormattedTopic ? htmlTopic! : topic);
|
||||||
|
|
||||||
if (isFormattedTopic) {
|
if (isFormattedTopic) {
|
||||||
safeTopic = sanitizeHtml(htmlTopic, allowExtendedHtml ? sanitizeHtmlParams : topicSanitizeHtmlParams);
|
safeTopic = sanitizeHtml(htmlTopic!, allowExtendedHtml ? sanitizeHtmlParams : topicSanitizeHtmlParams);
|
||||||
if (topicHasEmoji) {
|
if (topicHasEmoji) {
|
||||||
safeTopic = formatEmojis(safeTopic, true).join("");
|
safeTopic = formatEmojis(safeTopic, true).join("");
|
||||||
}
|
}
|
||||||
|
@ -669,7 +667,7 @@ export function topicToHtml(
|
||||||
isFormattedTopic = false; // Fall back to plain-text topic
|
isFormattedTopic = false; // Fall back to plain-text topic
|
||||||
}
|
}
|
||||||
|
|
||||||
let emojiBodyElements: ReturnType<typeof formatEmojis>;
|
let emojiBodyElements: ReturnType<typeof formatEmojis> | undefined;
|
||||||
if (!isFormattedTopic && topicHasEmoji) {
|
if (!isFormattedTopic && topicHasEmoji) {
|
||||||
emojiBodyElements = formatEmojis(topic, false);
|
emojiBodyElements = formatEmojis(topic, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -169,10 +169,18 @@ export interface IConfigOptions {
|
||||||
inline?: {
|
inline?: {
|
||||||
left?: string;
|
left?: string;
|
||||||
right?: string;
|
right?: string;
|
||||||
|
pattern?: {
|
||||||
|
tex?: string;
|
||||||
|
latex?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
display?: {
|
display?: {
|
||||||
left?: string;
|
left?: string;
|
||||||
right?: string;
|
right?: string;
|
||||||
|
pattern?: {
|
||||||
|
tex?: string;
|
||||||
|
latex?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export const Key = {
|
export const Key = {
|
||||||
HOME: "Home",
|
HOME: "Home",
|
||||||
END: "End",
|
END: "End",
|
||||||
|
@ -76,7 +78,7 @@ export const Key = {
|
||||||
|
|
||||||
export const IS_MAC = navigator.platform.toUpperCase().includes("MAC");
|
export const IS_MAC = navigator.platform.toUpperCase().includes("MAC");
|
||||||
|
|
||||||
export function isOnlyCtrlOrCmdKeyEvent(ev: KeyboardEvent): boolean {
|
export function isOnlyCtrlOrCmdKeyEvent(ev: React.KeyboardEvent | KeyboardEvent): boolean {
|
||||||
if (IS_MAC) {
|
if (IS_MAC) {
|
||||||
return ev.metaKey && !ev.altKey && !ev.ctrlKey && !ev.shiftKey;
|
return ev.metaKey && !ev.altKey && !ev.ctrlKey && !ev.shiftKey;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -158,9 +158,9 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
private transferees = new Map<string, MatrixCall>(); // callId (target) -> call (transferee)
|
private transferees = new Map<string, MatrixCall>(); // callId (target) -> call (transferee)
|
||||||
private audioPromises = new Map<AudioID, Promise<void>>();
|
private audioPromises = new Map<AudioID, Promise<void>>();
|
||||||
private audioElementsWithListeners = new Map<HTMLMediaElement, boolean>();
|
private audioElementsWithListeners = new Map<HTMLMediaElement, boolean>();
|
||||||
private supportsPstnProtocol = null;
|
private supportsPstnProtocol: boolean | null = null;
|
||||||
private pstnSupportPrefixed = null; // True if the server only support the prefixed pstn protocol
|
private pstnSupportPrefixed: boolean | null = null; // True if the server only support the prefixed pstn protocol
|
||||||
private supportsSipNativeVirtual = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native
|
private supportsSipNativeVirtual: boolean | null = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native
|
||||||
|
|
||||||
// Map of the asserted identity users after we've looked them up using the API.
|
// Map of the asserted identity users after we've looked them up using the API.
|
||||||
// We need to be be able to determine the mapped room synchronously, so we
|
// We need to be be able to determine the mapped room synchronously, so we
|
||||||
|
@ -187,7 +187,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
// check asserted identity: if we're not obeying asserted identity,
|
// check asserted identity: if we're not obeying asserted identity,
|
||||||
// this map will never be populated, but we check anyway for sanity
|
// this map will never be populated, but we check anyway for sanity
|
||||||
if (this.shouldObeyAssertedfIdentity()) {
|
if (this.shouldObeyAssertedfIdentity()) {
|
||||||
const nativeUser = this.assertedIdentityNativeUsers[call.callId];
|
const nativeUser = this.assertedIdentityNativeUsers.get(call.callId);
|
||||||
if (nativeUser) {
|
if (nativeUser) {
|
||||||
const room = findDMForUser(MatrixClientPeg.get(), nativeUser);
|
const room = findDMForUser(MatrixClientPeg.get(), nativeUser);
|
||||||
if (room) return room.roomId;
|
if (room) return room.roomId;
|
||||||
|
@ -466,8 +466,8 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
return this.getAllActiveCallsNotInRoom(roomId);
|
return this.getAllActiveCallsNotInRoom(roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTransfereeForCallId(callId: string): MatrixCall {
|
public getTransfereeForCallId(callId: string): MatrixCall | undefined {
|
||||||
return this.transferees[callId];
|
return this.transferees.get(callId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public play(audioId: AudioID): void {
|
public play(audioId: AudioID): void {
|
||||||
|
@ -621,7 +621,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
logger.log(`Asserted identity ${newAssertedIdentity} mapped to ${newNativeAssertedIdentity}`);
|
logger.log(`Asserted identity ${newAssertedIdentity} mapped to ${newNativeAssertedIdentity}`);
|
||||||
|
|
||||||
if (newNativeAssertedIdentity) {
|
if (newNativeAssertedIdentity) {
|
||||||
this.assertedIdentityNativeUsers[call.callId] = newNativeAssertedIdentity;
|
this.assertedIdentityNativeUsers.set(call.callId, newNativeAssertedIdentity);
|
||||||
|
|
||||||
// If we don't already have a room with this user, make one. This will be slightly odd
|
// If we don't already have a room with this user, make one. This will be slightly odd
|
||||||
// if they called us because we'll be inviting them, but there's not much we can do about
|
// if they called us because we'll be inviting them, but there's not much we can do about
|
||||||
|
@ -917,7 +917,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (transferee) {
|
if (transferee) {
|
||||||
this.transferees[call.callId] = transferee;
|
this.transferees.set(call.callId, transferee);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setCallListeners(call);
|
this.setCallListeners(call);
|
||||||
|
|
|
@ -91,12 +91,12 @@ export default class Login {
|
||||||
}
|
}
|
||||||
|
|
||||||
public loginViaPassword(
|
public loginViaPassword(
|
||||||
username: string,
|
username: string | undefined,
|
||||||
phoneCountry: string,
|
phoneCountry: string | undefined,
|
||||||
phoneNumber: string,
|
phoneNumber: string | undefined,
|
||||||
password: string,
|
password: string,
|
||||||
): Promise<IMatrixClientCreds> {
|
): Promise<IMatrixClientCreds> {
|
||||||
const isEmail = username.indexOf("@") > 0;
|
const isEmail = username?.indexOf("@") > 0;
|
||||||
|
|
||||||
let identifier;
|
let identifier;
|
||||||
if (phoneCountry && phoneNumber) {
|
if (phoneCountry && phoneNumber) {
|
||||||
|
|
|
@ -139,7 +139,7 @@ export default class Markdown {
|
||||||
*/
|
*/
|
||||||
private repairLinks(parsed: commonmark.Node): commonmark.Node {
|
private repairLinks(parsed: commonmark.Node): commonmark.Node {
|
||||||
const walker = parsed.walker();
|
const walker = parsed.walker();
|
||||||
let event: commonmark.NodeWalkingStep = null;
|
let event: commonmark.NodeWalkingStep | null = null;
|
||||||
let text = "";
|
let text = "";
|
||||||
let isInPara = false;
|
let isInPara = false;
|
||||||
let previousNode: commonmark.Node | null = null;
|
let previousNode: commonmark.Node | null = null;
|
||||||
|
@ -287,7 +287,7 @@ export default class Markdown {
|
||||||
// However, if it's a blockquote, adds a p tag anyway
|
// However, if it's a blockquote, adds a p tag anyway
|
||||||
// in order to avoid deviation to commonmark and unexpected
|
// in order to avoid deviation to commonmark and unexpected
|
||||||
// results when parsing the formatted HTML.
|
// results when parsing the formatted HTML.
|
||||||
if (node.parent.type === "block_quote" || isMultiLine(node)) {
|
if (node.parent?.type === "block_quote" || isMultiLine(node)) {
|
||||||
realParagraph.call(this, node, entering);
|
realParagraph.call(this, node, entering);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -37,7 +37,7 @@ export enum MediaDeviceHandlerEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class MediaDeviceHandler extends EventEmitter {
|
export default class MediaDeviceHandler extends EventEmitter {
|
||||||
private static internalInstance;
|
private static internalInstance?: MediaDeviceHandler;
|
||||||
|
|
||||||
public static get instance(): MediaDeviceHandler {
|
public static get instance(): MediaDeviceHandler {
|
||||||
if (!MediaDeviceHandler.internalInstance) {
|
if (!MediaDeviceHandler.internalInstance) {
|
||||||
|
@ -67,7 +67,7 @@ export default class MediaDeviceHandler extends EventEmitter {
|
||||||
public static async getDevices(): Promise<IMediaDevices> {
|
public static async getDevices(): Promise<IMediaDevices> {
|
||||||
try {
|
try {
|
||||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||||
const output = {
|
const output: Record<MediaDeviceKindEnum, MediaDeviceInfo[]> = {
|
||||||
[MediaDeviceKindEnum.AudioOutput]: [],
|
[MediaDeviceKindEnum.AudioOutput]: [],
|
||||||
[MediaDeviceKindEnum.AudioInput]: [],
|
[MediaDeviceKindEnum.AudioInput]: [],
|
||||||
[MediaDeviceKindEnum.VideoInput]: [],
|
[MediaDeviceKindEnum.VideoInput]: [],
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ReactInstance } from "react";
|
||||||
import ReactDom from "react-dom";
|
import ReactDom from "react-dom";
|
||||||
|
|
||||||
interface IChildProps {
|
interface IChildProps {
|
||||||
|
@ -41,7 +41,7 @@ interface IProps {
|
||||||
* automatic positional animation, look at react-shuffle or similar libraries.
|
* automatic positional animation, look at react-shuffle or similar libraries.
|
||||||
*/
|
*/
|
||||||
export default class NodeAnimator extends React.Component<IProps> {
|
export default class NodeAnimator extends React.Component<IProps> {
|
||||||
private nodes = {};
|
private nodes: Record<string, ReactInstance> = {};
|
||||||
private children: { [key: string]: React.DetailedReactHTMLElement<any, HTMLElement> };
|
private children: { [key: string]: React.DetailedReactHTMLElement<any, HTMLElement> };
|
||||||
public static defaultProps: Partial<IProps> = {
|
public static defaultProps: Partial<IProps> = {
|
||||||
startStyles: [],
|
startStyles: [],
|
||||||
|
@ -65,7 +65,7 @@ export default class NodeAnimator extends React.Component<IProps> {
|
||||||
*/
|
*/
|
||||||
private applyStyles(node: HTMLElement, styles: React.CSSProperties): void {
|
private applyStyles(node: HTMLElement, styles: React.CSSProperties): void {
|
||||||
Object.entries(styles).forEach(([property, value]) => {
|
Object.entries(styles).forEach(([property, value]) => {
|
||||||
node.style[property] = value;
|
node.style[property as keyof Omit<CSSStyleDeclaration, "length" | "parentRule">] = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ export default class NodeAnimator extends React.Component<IProps> {
|
||||||
this.nodes[k] = node;
|
this.nodes[k] = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
return <>{Object.values(this.children)}</>;
|
return <>{Object.values(this.children)}</>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
158
src/Notifier.ts
158
src/Notifier.ts
|
@ -68,7 +68,7 @@ Override both the content body and the TextForEvent handler for specific msgtype
|
||||||
This is useful when the content body contains fallback text that would explain that the client can't handle a particular
|
This is useful when the content body contains fallback text that would explain that the client can't handle a particular
|
||||||
type of tile.
|
type of tile.
|
||||||
*/
|
*/
|
||||||
const msgTypeHandlers = {
|
const msgTypeHandlers: Record<string, (event: MatrixEvent) => string> = {
|
||||||
[MsgType.KeyVerificationRequest]: (event: MatrixEvent) => {
|
[MsgType.KeyVerificationRequest]: (event: MatrixEvent) => {
|
||||||
const name = (event.sender || {}).name;
|
const name = (event.sender || {}).name;
|
||||||
return _t("%(name)s is requesting verification", { name });
|
return _t("%(name)s is requesting verification", { name });
|
||||||
|
@ -95,22 +95,26 @@ const msgTypeHandlers = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Notifier = {
|
class NotifierClass {
|
||||||
notifsByRoom: {},
|
private notifsByRoom: Record<string, Notification[]> = {};
|
||||||
|
|
||||||
// A list of event IDs that we've received but need to wait until
|
// A list of event IDs that we've received but need to wait until
|
||||||
// they're decrypted until we decide whether to notify for them
|
// they're decrypted until we decide whether to notify for them
|
||||||
// or not
|
// or not
|
||||||
pendingEncryptedEventIds: [],
|
private pendingEncryptedEventIds: string[] = [];
|
||||||
|
|
||||||
notificationMessageForEvent: function (ev: MatrixEvent): string {
|
private toolbarHidden?: boolean;
|
||||||
|
private isSyncing?: boolean;
|
||||||
|
|
||||||
|
public notificationMessageForEvent(ev: MatrixEvent): string {
|
||||||
if (msgTypeHandlers.hasOwnProperty(ev.getContent().msgtype)) {
|
if (msgTypeHandlers.hasOwnProperty(ev.getContent().msgtype)) {
|
||||||
return msgTypeHandlers[ev.getContent().msgtype](ev);
|
return msgTypeHandlers[ev.getContent().msgtype](ev);
|
||||||
}
|
}
|
||||||
return TextForEvent.textForEvent(ev);
|
return TextForEvent.textForEvent(ev);
|
||||||
},
|
}
|
||||||
|
|
||||||
_displayPopupNotification: function (ev: MatrixEvent, room: Room): void {
|
// XXX: exported for tests
|
||||||
|
public displayPopupNotification(ev: MatrixEvent, room: Room): void {
|
||||||
const plaf = PlatformPeg.get();
|
const plaf = PlatformPeg.get();
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (!plaf) {
|
if (!plaf) {
|
||||||
|
@ -165,9 +169,14 @@ export const Notifier = {
|
||||||
if (this.notifsByRoom[ev.getRoomId()] === undefined) this.notifsByRoom[ev.getRoomId()] = [];
|
if (this.notifsByRoom[ev.getRoomId()] === undefined) this.notifsByRoom[ev.getRoomId()] = [];
|
||||||
this.notifsByRoom[ev.getRoomId()].push(notif);
|
this.notifsByRoom[ev.getRoomId()].push(notif);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
getSoundForRoom: function (roomId: string) {
|
public getSoundForRoom(roomId: string): {
|
||||||
|
url: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
size: string;
|
||||||
|
} | null {
|
||||||
// We do no caching here because the SDK caches setting
|
// We do no caching here because the SDK caches setting
|
||||||
// and the browser will cache the sound.
|
// and the browser will cache the sound.
|
||||||
const content = SettingsStore.getValue("notificationSound", roomId);
|
const content = SettingsStore.getValue("notificationSound", roomId);
|
||||||
|
@ -193,9 +202,10 @@ export const Notifier = {
|
||||||
type: content.type,
|
type: content.type,
|
||||||
size: content.size,
|
size: content.size,
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
|
|
||||||
_playAudioNotification: async function (ev: MatrixEvent, room: Room): Promise<void> {
|
// XXX: Exported for tests
|
||||||
|
public async playAudioNotification(ev: MatrixEvent, room: Room): Promise<void> {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (localNotificationsAreSilenced(cli)) {
|
if (localNotificationsAreSilenced(cli)) {
|
||||||
return;
|
return;
|
||||||
|
@ -224,39 +234,32 @@ export const Notifier = {
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
logger.warn("Caught error when trying to fetch room notification sound:", ex);
|
logger.warn("Caught error when trying to fetch room notification sound:", ex);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
start: function (this: typeof Notifier) {
|
public start(): void {
|
||||||
// do not re-bind in the case of repeated call
|
MatrixClientPeg.get().on(RoomEvent.Timeline, this.onEvent);
|
||||||
this.boundOnEvent = this.boundOnEvent || this.onEvent.bind(this);
|
MatrixClientPeg.get().on(RoomEvent.Receipt, this.onRoomReceipt);
|
||||||
this.boundOnSyncStateChange = this.boundOnSyncStateChange || this.onSyncStateChange.bind(this);
|
MatrixClientPeg.get().on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
|
||||||
this.boundOnRoomReceipt = this.boundOnRoomReceipt || this.onRoomReceipt.bind(this);
|
MatrixClientPeg.get().on(ClientEvent.Sync, this.onSyncStateChange);
|
||||||
this.boundOnEventDecrypted = this.boundOnEventDecrypted || this.onEventDecrypted.bind(this);
|
|
||||||
|
|
||||||
MatrixClientPeg.get().on(RoomEvent.Timeline, this.boundOnEvent);
|
|
||||||
MatrixClientPeg.get().on(RoomEvent.Receipt, this.boundOnRoomReceipt);
|
|
||||||
MatrixClientPeg.get().on(MatrixEventEvent.Decrypted, this.boundOnEventDecrypted);
|
|
||||||
MatrixClientPeg.get().on(ClientEvent.Sync, this.boundOnSyncStateChange);
|
|
||||||
this.toolbarHidden = false;
|
this.toolbarHidden = false;
|
||||||
this.isSyncing = false;
|
this.isSyncing = false;
|
||||||
},
|
}
|
||||||
|
|
||||||
stop: function (this: typeof Notifier) {
|
public stop(): void {
|
||||||
if (MatrixClientPeg.get()) {
|
if (MatrixClientPeg.get()) {
|
||||||
MatrixClientPeg.get().removeListener(RoomEvent.Timeline, this.boundOnEvent);
|
MatrixClientPeg.get().removeListener(RoomEvent.Timeline, this.onEvent);
|
||||||
MatrixClientPeg.get().removeListener(RoomEvent.Receipt, this.boundOnRoomReceipt);
|
MatrixClientPeg.get().removeListener(RoomEvent.Receipt, this.onRoomReceipt);
|
||||||
MatrixClientPeg.get().removeListener(MatrixEventEvent.Decrypted, this.boundOnEventDecrypted);
|
MatrixClientPeg.get().removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted);
|
||||||
MatrixClientPeg.get().removeListener(ClientEvent.Sync, this.boundOnSyncStateChange);
|
MatrixClientPeg.get().removeListener(ClientEvent.Sync, this.onSyncStateChange);
|
||||||
}
|
}
|
||||||
this.isSyncing = false;
|
this.isSyncing = false;
|
||||||
},
|
}
|
||||||
|
|
||||||
supportsDesktopNotifications: function () {
|
public supportsDesktopNotifications(): boolean {
|
||||||
const plaf = PlatformPeg.get();
|
return PlatformPeg.get()?.supportsNotifications() ?? false;
|
||||||
return plaf && plaf.supportsNotifications();
|
}
|
||||||
},
|
|
||||||
|
|
||||||
setEnabled: function (enable: boolean, callback?: () => void) {
|
public setEnabled(enable: boolean, callback?: () => void): void {
|
||||||
const plaf = PlatformPeg.get();
|
const plaf = PlatformPeg.get();
|
||||||
if (!plaf) return;
|
if (!plaf) return;
|
||||||
|
|
||||||
|
@ -320,31 +323,30 @@ export const Notifier = {
|
||||||
// set the notifications_hidden flag, as the user has knowingly interacted
|
// set the notifications_hidden flag, as the user has knowingly interacted
|
||||||
// with the setting we shouldn't nag them any further
|
// with the setting we shouldn't nag them any further
|
||||||
this.setPromptHidden(true);
|
this.setPromptHidden(true);
|
||||||
},
|
}
|
||||||
|
|
||||||
isEnabled: function () {
|
public isEnabled(): boolean {
|
||||||
return this.isPossible() && SettingsStore.getValue("notificationsEnabled");
|
return this.isPossible() && SettingsStore.getValue("notificationsEnabled");
|
||||||
},
|
}
|
||||||
|
|
||||||
isPossible: function () {
|
public isPossible(): boolean {
|
||||||
const plaf = PlatformPeg.get();
|
const plaf = PlatformPeg.get();
|
||||||
if (!plaf) return false;
|
if (!plaf?.supportsNotifications()) return false;
|
||||||
if (!plaf.supportsNotifications()) return false;
|
|
||||||
if (!plaf.maySendNotifications()) return false;
|
if (!plaf.maySendNotifications()) return false;
|
||||||
|
|
||||||
return true; // possible, but not necessarily enabled
|
return true; // possible, but not necessarily enabled
|
||||||
},
|
}
|
||||||
|
|
||||||
isBodyEnabled: function () {
|
public isBodyEnabled(): boolean {
|
||||||
return this.isEnabled() && SettingsStore.getValue("notificationBodyEnabled");
|
return this.isEnabled() && SettingsStore.getValue("notificationBodyEnabled");
|
||||||
},
|
}
|
||||||
|
|
||||||
isAudioEnabled: function () {
|
public isAudioEnabled(): boolean {
|
||||||
// We don't route Audio via the HTML Notifications API so it is possible regardless of other things
|
// We don't route Audio via the HTML Notifications API so it is possible regardless of other things
|
||||||
return SettingsStore.getValue("audioNotificationsEnabled");
|
return SettingsStore.getValue("audioNotificationsEnabled");
|
||||||
},
|
}
|
||||||
|
|
||||||
setPromptHidden: function (this: typeof Notifier, hidden: boolean, persistent = true) {
|
public setPromptHidden(hidden: boolean, persistent = true): void {
|
||||||
this.toolbarHidden = hidden;
|
this.toolbarHidden = hidden;
|
||||||
|
|
||||||
hideNotificationsToast();
|
hideNotificationsToast();
|
||||||
|
@ -353,9 +355,9 @@ export const Notifier = {
|
||||||
if (persistent && global.localStorage) {
|
if (persistent && global.localStorage) {
|
||||||
global.localStorage.setItem("notifications_hidden", String(hidden));
|
global.localStorage.setItem("notifications_hidden", String(hidden));
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
shouldShowPrompt: function () {
|
public shouldShowPrompt(): boolean {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -366,25 +368,21 @@ export const Notifier = {
|
||||||
this.supportsDesktopNotifications() &&
|
this.supportsDesktopNotifications() &&
|
||||||
!isPushNotifyDisabled() &&
|
!isPushNotifyDisabled() &&
|
||||||
!this.isEnabled() &&
|
!this.isEnabled() &&
|
||||||
!this._isPromptHidden()
|
!this.isPromptHidden()
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
_isPromptHidden: function (this: typeof Notifier) {
|
private isPromptHidden(): boolean {
|
||||||
// Check localStorage for any such meta data
|
// Check localStorage for any such meta data
|
||||||
if (global.localStorage) {
|
if (global.localStorage) {
|
||||||
return global.localStorage.getItem("notifications_hidden") === "true";
|
return global.localStorage.getItem("notifications_hidden") === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.toolbarHidden;
|
return this.toolbarHidden;
|
||||||
},
|
}
|
||||||
|
|
||||||
onSyncStateChange: function (
|
// XXX: Exported for tests
|
||||||
this: typeof Notifier,
|
public onSyncStateChange = (state: SyncState, prevState?: SyncState, data?: ISyncStateData): void => {
|
||||||
state: SyncState,
|
|
||||||
prevState?: SyncState,
|
|
||||||
data?: ISyncStateData,
|
|
||||||
) {
|
|
||||||
if (state === SyncState.Syncing) {
|
if (state === SyncState.Syncing) {
|
||||||
this.isSyncing = true;
|
this.isSyncing = true;
|
||||||
} else if (state === SyncState.Stopped || state === SyncState.Error) {
|
} else if (state === SyncState.Stopped || state === SyncState.Error) {
|
||||||
|
@ -395,16 +393,15 @@ export const Notifier = {
|
||||||
if (![SyncState.Stopped, SyncState.Error].includes(state) && !data?.fromCache) {
|
if (![SyncState.Stopped, SyncState.Error].includes(state) && !data?.fromCache) {
|
||||||
createLocalNotificationSettingsIfNeeded(MatrixClientPeg.get());
|
createLocalNotificationSettingsIfNeeded(MatrixClientPeg.get());
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
onEvent: function (
|
private onEvent = (
|
||||||
this: typeof Notifier,
|
|
||||||
ev: MatrixEvent,
|
ev: MatrixEvent,
|
||||||
room: Room | undefined,
|
room: Room | undefined,
|
||||||
toStartOfTimeline: boolean | undefined,
|
toStartOfTimeline: boolean | undefined,
|
||||||
removed: boolean,
|
removed: boolean,
|
||||||
data: IRoomTimelineData,
|
data: IRoomTimelineData,
|
||||||
) {
|
): void => {
|
||||||
if (!data.liveEvent) return; // only notify for new things, not old.
|
if (!data.liveEvent) return; // only notify for new things, not old.
|
||||||
if (!this.isSyncing) return; // don't alert for any messages initially
|
if (!this.isSyncing) return; // don't alert for any messages initially
|
||||||
if (ev.getSender() === MatrixClientPeg.get().getUserId()) return;
|
if (ev.getSender() === MatrixClientPeg.get().getUserId()) return;
|
||||||
|
@ -422,10 +419,10 @@ export const Notifier = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._evaluateEvent(ev);
|
this.evaluateEvent(ev);
|
||||||
},
|
};
|
||||||
|
|
||||||
onEventDecrypted: function (ev: MatrixEvent) {
|
private onEventDecrypted = (ev: MatrixEvent): void => {
|
||||||
// 'decrypted' means the decryption process has finished: it may have failed,
|
// 'decrypted' means the decryption process has finished: it may have failed,
|
||||||
// in which case it might decrypt soon if the keys arrive
|
// in which case it might decrypt soon if the keys arrive
|
||||||
if (ev.isDecryptionFailure()) return;
|
if (ev.isDecryptionFailure()) return;
|
||||||
|
@ -434,10 +431,10 @@ export const Notifier = {
|
||||||
if (idx === -1) return;
|
if (idx === -1) return;
|
||||||
|
|
||||||
this.pendingEncryptedEventIds.splice(idx, 1);
|
this.pendingEncryptedEventIds.splice(idx, 1);
|
||||||
this._evaluateEvent(ev);
|
this.evaluateEvent(ev);
|
||||||
},
|
};
|
||||||
|
|
||||||
onRoomReceipt: function (ev: MatrixEvent, room: Room) {
|
private onRoomReceipt = (ev: MatrixEvent, room: Room): void => {
|
||||||
if (room.getUnreadNotificationCount() === 0) {
|
if (room.getUnreadNotificationCount() === 0) {
|
||||||
// ideally we would clear each notification when it was read,
|
// ideally we would clear each notification when it was read,
|
||||||
// but we have no way, given a read receipt, to know whether
|
// but we have no way, given a read receipt, to know whether
|
||||||
|
@ -453,12 +450,12 @@ export const Notifier = {
|
||||||
}
|
}
|
||||||
delete this.notifsByRoom[room.roomId];
|
delete this.notifsByRoom[room.roomId];
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
_evaluateEvent: function (ev: MatrixEvent) {
|
// XXX: exported for tests
|
||||||
|
public evaluateEvent(ev: MatrixEvent): void {
|
||||||
// Mute notifications for broadcast info events
|
// Mute notifications for broadcast info events
|
||||||
if (ev.getType() === VoiceBroadcastInfoEventType) return;
|
if (ev.getType() === VoiceBroadcastInfoEventType) return;
|
||||||
|
|
||||||
let roomId = ev.getRoomId();
|
let roomId = ev.getRoomId();
|
||||||
if (LegacyCallHandler.instance.getSupportsVirtualRooms()) {
|
if (LegacyCallHandler.instance.getSupportsVirtualRooms()) {
|
||||||
// Attempt to translate a virtual room to a native one
|
// Attempt to translate a virtual room to a native one
|
||||||
|
@ -477,7 +474,7 @@ export const Notifier = {
|
||||||
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
||||||
|
|
||||||
if (actions?.notify) {
|
if (actions?.notify) {
|
||||||
this._performCustomEventHandling(ev);
|
this.performCustomEventHandling(ev);
|
||||||
|
|
||||||
const store = SdkContextClass.instance.roomViewStore;
|
const store = SdkContextClass.instance.roomViewStore;
|
||||||
const isViewingRoom = store.getRoomId() === room.roomId;
|
const isViewingRoom = store.getRoomId() === room.roomId;
|
||||||
|
@ -492,19 +489,19 @@ export const Notifier = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isEnabled()) {
|
if (this.isEnabled()) {
|
||||||
this._displayPopupNotification(ev, room);
|
this.displayPopupNotification(ev, room);
|
||||||
}
|
}
|
||||||
if (actions.tweaks.sound && this.isAudioEnabled()) {
|
if (actions.tweaks.sound && this.isAudioEnabled()) {
|
||||||
PlatformPeg.get().loudNotification(ev, room);
|
PlatformPeg.get().loudNotification(ev, room);
|
||||||
this._playAudioNotification(ev, room);
|
this.playAudioNotification(ev, room);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Some events require special handling such as showing in-app toasts
|
* Some events require special handling such as showing in-app toasts
|
||||||
*/
|
*/
|
||||||
_performCustomEventHandling: function (ev: MatrixEvent) {
|
private performCustomEventHandling(ev: MatrixEvent): void {
|
||||||
if (ElementCall.CALL_EVENT_TYPE.names.includes(ev.getType()) && SettingsStore.getValue("feature_group_calls")) {
|
if (ElementCall.CALL_EVENT_TYPE.names.includes(ev.getType()) && SettingsStore.getValue("feature_group_calls")) {
|
||||||
ToastStore.sharedInstance().addOrReplaceToast({
|
ToastStore.sharedInstance().addOrReplaceToast({
|
||||||
key: getIncomingCallToastKey(ev.getStateKey()),
|
key: getIncomingCallToastKey(ev.getStateKey()),
|
||||||
|
@ -514,11 +511,12 @@ export const Notifier = {
|
||||||
props: { callEvent: ev },
|
props: { callEvent: ev },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
if (!window.mxNotifier) {
|
if (!window.mxNotifier) {
|
||||||
window.mxNotifier = Notifier;
|
window.mxNotifier = new NotifierClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default window.mxNotifier;
|
export default window.mxNotifier;
|
||||||
|
export const Notifier: NotifierClass = window.mxNotifier;
|
||||||
|
|
|
@ -132,8 +132,8 @@ export class PosthogAnalytics {
|
||||||
private anonymity = Anonymity.Disabled;
|
private anonymity = Anonymity.Disabled;
|
||||||
// set true during the constructor if posthog config is present, otherwise false
|
// set true during the constructor if posthog config is present, otherwise false
|
||||||
private readonly enabled: boolean = false;
|
private readonly enabled: boolean = false;
|
||||||
private static _instance = null;
|
private static _instance: PosthogAnalytics | null = null;
|
||||||
private platformSuperProperties = {};
|
private platformSuperProperties: Properties = {};
|
||||||
public static readonly ANALYTICS_EVENT_TYPE = "im.vector.analytics";
|
public static readonly ANALYTICS_EVENT_TYPE = "im.vector.analytics";
|
||||||
private propertiesForNextEvent: Partial<Record<"$set" | "$set_once", UserProperties>> = {};
|
private propertiesForNextEvent: Partial<Record<"$set" | "$set_once", UserProperties>> = {};
|
||||||
private userPropertyCache: UserProperties = {};
|
private userPropertyCache: UserProperties = {};
|
||||||
|
|
|
@ -120,7 +120,7 @@ export class PosthogScreenTracker extends PureComponent<{ screenName: ScreenName
|
||||||
PosthogTrackers.instance.clearOverride(this.props.screenName);
|
PosthogTrackers.instance.clearOverride(this.props.screenName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
return null; // no need to render anything, we just need to hook into the React lifecycle
|
return null; // no need to render anything, we just need to hook into the React lifecycle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,9 +33,9 @@ enum State {
|
||||||
}
|
}
|
||||||
|
|
||||||
class Presence {
|
class Presence {
|
||||||
private unavailableTimer: Timer = null;
|
private unavailableTimer: Timer | null = null;
|
||||||
private dispatcherRef: string = null;
|
private dispatcherRef: string | null = null;
|
||||||
private state: State = null;
|
private state: State | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start listening the user activity to evaluate his presence state.
|
* Start listening the user activity to evaluate his presence state.
|
||||||
|
@ -73,14 +73,14 @@ class Presence {
|
||||||
* Get the current presence state.
|
* Get the current presence state.
|
||||||
* @returns {string} the presence state (see PRESENCE enum)
|
* @returns {string} the presence state (see PRESENCE enum)
|
||||||
*/
|
*/
|
||||||
public getState(): State {
|
public getState(): State | null {
|
||||||
return this.state;
|
return this.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onAction = (payload: ActionPayload): void => {
|
private onAction = (payload: ActionPayload): void => {
|
||||||
if (payload.action === "user_activity") {
|
if (payload.action === "user_activity") {
|
||||||
this.setState(State.Online);
|
this.setState(State.Online);
|
||||||
this.unavailableTimer.restart();
|
this.unavailableTimer?.restart();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ export default class Resend {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static resend(event: MatrixEvent): Promise<void> {
|
public static resend(event: MatrixEvent): Promise<void> {
|
||||||
const room = MatrixClientPeg.get().getRoom(event.getRoomId());
|
const room = MatrixClientPeg.get().getRoom(event.getRoomId())!;
|
||||||
return MatrixClientPeg.get()
|
return MatrixClientPeg.get()
|
||||||
.resendEvent(event, room)
|
.resendEvent(event, room)
|
||||||
.then(
|
.then(
|
||||||
|
|
|
@ -30,6 +30,6 @@ export function storeRoomAliasInCache(alias: string, id: string): void {
|
||||||
aliasToIDMap.set(alias, id);
|
aliasToIDMap.set(alias, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCachedRoomIDForAlias(alias: string): string {
|
export function getCachedRoomIDForAlias(alias: string): string | undefined {
|
||||||
return aliasToIDMap.get(alias);
|
return aliasToIDMap.get(alias);
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,7 +112,7 @@ export function inviteUsersToRoom(
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return inviteMultipleToRoom(roomId, userIds, sendSharedHistoryKeys, progressCallback)
|
return inviteMultipleToRoom(roomId, userIds, sendSharedHistoryKeys, progressCallback)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
const room = MatrixClientPeg.get().getRoom(roomId)!;
|
||||||
showAnyInviteErrors(result.states, room, result.inviter);
|
showAnyInviteErrors(result.states, room, result.inviter);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
@ -175,14 +175,14 @@ export function showAnyInviteErrors(
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24) : null}
|
url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24) : null}
|
||||||
name={name}
|
name={name}
|
||||||
idName={user.userId}
|
idName={user?.userId}
|
||||||
width={36}
|
width={36}
|
||||||
height={36}
|
height={36}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_InviteDialog_tile_nameStack">
|
<div className="mx_InviteDialog_tile_nameStack">
|
||||||
<span className="mx_InviteDialog_tile_nameStack_name">{name}</span>
|
<span className="mx_InviteDialog_tile_nameStack_name">{name}</span>
|
||||||
<span className="mx_InviteDialog_tile_nameStack_userId">{user.userId}</span>
|
<span className="mx_InviteDialog_tile_nameStack_userId">{user?.userId}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_InviteDialog_tile--inviterError_errorText">
|
<div className="mx_InviteDialog_tile--inviterError_errorText">
|
||||||
{inviter.getErrorText(addr)}
|
{inviter.getErrorText(addr)}
|
||||||
|
|
|
@ -46,7 +46,7 @@ export function getRoomNotifsState(client: MatrixClient, roomId: string): RoomNo
|
||||||
}
|
}
|
||||||
|
|
||||||
// for everything else, look at the room rule.
|
// for everything else, look at the room rule.
|
||||||
let roomRule = null;
|
let roomRule: IPushRule | undefined;
|
||||||
try {
|
try {
|
||||||
roomRule = client.getRoomPushRule("global", roomId);
|
roomRule = client.getRoomPushRule("global", roomId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -106,7 +106,7 @@ export function getUnreadNotificationCount(room: Room, type: NotificationCountTy
|
||||||
|
|
||||||
function setRoomNotifsStateMuted(roomId: string): Promise<any> {
|
function setRoomNotifsStateMuted(roomId: string): Promise<any> {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const promises = [];
|
const promises: Promise<unknown>[] = [];
|
||||||
|
|
||||||
// delete the room rule
|
// delete the room rule
|
||||||
const roomRule = cli.getRoomPushRule("global", roomId);
|
const roomRule = cli.getRoomPushRule("global", roomId);
|
||||||
|
@ -137,7 +137,7 @@ function setRoomNotifsStateMuted(roomId: string): Promise<any> {
|
||||||
|
|
||||||
function setRoomNotifsStateUnmuted(roomId: string, newState: RoomNotifState): Promise<any> {
|
function setRoomNotifsStateUnmuted(roomId: string, newState: RoomNotifState): Promise<any> {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const promises = [];
|
const promises: Promise<unknown>[] = [];
|
||||||
|
|
||||||
const overrideMuteRule = findOverrideMuteRule(roomId);
|
const overrideMuteRule = findOverrideMuteRule(roomId);
|
||||||
if (overrideMuteRule) {
|
if (overrideMuteRule) {
|
||||||
|
|
10
src/Rooms.ts
10
src/Rooms.ts
|
@ -29,13 +29,13 @@ import AliasCustomisations from "./customisations/Alias";
|
||||||
* @param {Object} room The room object
|
* @param {Object} room The room object
|
||||||
* @returns {string} A display alias for the given room
|
* @returns {string} A display alias for the given room
|
||||||
*/
|
*/
|
||||||
export function getDisplayAliasForRoom(room: Room): string | undefined {
|
export function getDisplayAliasForRoom(room: Room): string | null {
|
||||||
return getDisplayAliasForAliasSet(room.getCanonicalAlias(), room.getAltAliases());
|
return getDisplayAliasForAliasSet(room.getCanonicalAlias(), room.getAltAliases());
|
||||||
}
|
}
|
||||||
|
|
||||||
// The various display alias getters should all feed through this one path so
|
// The various display alias getters should all feed through this one path so
|
||||||
// there's a single place to change the logic.
|
// there's a single place to change the logic.
|
||||||
export function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: string[]): string {
|
export function getDisplayAliasForAliasSet(canonicalAlias: string | null, altAliases: string[]): string | null {
|
||||||
if (AliasCustomisations.getDisplayAliasForAliasSet) {
|
if (AliasCustomisations.getDisplayAliasForAliasSet) {
|
||||||
return AliasCustomisations.getDisplayAliasForAliasSet(canonicalAlias, altAliases);
|
return AliasCustomisations.getDisplayAliasForAliasSet(canonicalAlias, altAliases);
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ export function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: s
|
||||||
export function guessAndSetDMRoom(room: Room, isDirect: boolean): Promise<void> {
|
export function guessAndSetDMRoom(room: Room, isDirect: boolean): Promise<void> {
|
||||||
let newTarget;
|
let newTarget;
|
||||||
if (isDirect) {
|
if (isDirect) {
|
||||||
const guessedUserId = guessDMRoomTargetId(room, MatrixClientPeg.get().getUserId());
|
const guessedUserId = guessDMRoomTargetId(room, MatrixClientPeg.get().getUserId()!);
|
||||||
newTarget = guessedUserId;
|
newTarget = guessedUserId;
|
||||||
} else {
|
} else {
|
||||||
newTarget = null;
|
newTarget = null;
|
||||||
|
@ -118,7 +118,7 @@ function guessDMRoomTargetId(room: Room, myUserId: string): string {
|
||||||
|
|
||||||
if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) {
|
if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) {
|
||||||
oldestUser = user;
|
oldestUser = user;
|
||||||
oldestTs = user.events.member.getTs();
|
oldestTs = user.events.member?.getTs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldestUser) return oldestUser.userId;
|
if (oldestUser) return oldestUser.userId;
|
||||||
|
@ -129,7 +129,7 @@ function guessDMRoomTargetId(room: Room, myUserId: string): string {
|
||||||
|
|
||||||
if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) {
|
if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) {
|
||||||
oldestUser = user;
|
oldestUser = user;
|
||||||
oldestTs = user.events.member.getTs();
|
oldestTs = user.events.member?.getTs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -182,7 +182,7 @@ async function getSecretStorageKey({
|
||||||
|
|
||||||
export async function getDehydrationKey(
|
export async function getDehydrationKey(
|
||||||
keyInfo: ISecretStorageKeyInfo,
|
keyInfo: ISecretStorageKeyInfo,
|
||||||
checkFunc: (Uint8Array) => void,
|
checkFunc: (data: Uint8Array) => void,
|
||||||
): Promise<Uint8Array> {
|
): Promise<Uint8Array> {
|
||||||
const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.();
|
const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.();
|
||||||
if (keyFromCustomisations) {
|
if (keyFromCustomisations) {
|
||||||
|
@ -196,7 +196,7 @@ export async function getDehydrationKey(
|
||||||
/* props= */
|
/* props= */
|
||||||
{
|
{
|
||||||
keyInfo,
|
keyInfo,
|
||||||
checkPrivateKey: async (input): Promise<boolean> => {
|
checkPrivateKey: async (input: KeyParams): Promise<boolean> => {
|
||||||
const key = await inputToKey(input);
|
const key = await inputToKey(input);
|
||||||
try {
|
try {
|
||||||
checkFunc(key);
|
checkFunc(key);
|
||||||
|
@ -290,7 +290,7 @@ export async function promptForBackupPassphrase(): Promise<Uint8Array> {
|
||||||
RestoreKeyBackupDialog,
|
RestoreKeyBackupDialog,
|
||||||
{
|
{
|
||||||
showSummary: false,
|
showSummary: false,
|
||||||
keyCallback: (k) => (key = k),
|
keyCallback: (k: Uint8Array) => (key = k),
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
/* priority = */ false,
|
/* priority = */ false,
|
||||||
|
|
|
@ -697,11 +697,8 @@ export const Commands = [
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viaServers) {
|
if (viaServers) {
|
||||||
// For the join
|
// For the join, these are passed down to the js-sdk's /join call
|
||||||
dispatch["opts"] = {
|
dispatch["opts"] = { viaServers };
|
||||||
// These are passed down to the js-sdk's /join call
|
|
||||||
viaServers: viaServers,
|
|
||||||
};
|
|
||||||
|
|
||||||
// For if the join fails (rejoin button)
|
// For if the join fails (rejoin button)
|
||||||
dispatch["via_servers"] = viaServers;
|
dispatch["via_servers"] = viaServers;
|
||||||
|
@ -1042,7 +1039,7 @@ export const Commands = [
|
||||||
throw newTranslatableError("Session already verified!");
|
throw newTranslatableError("Session already verified!");
|
||||||
} else {
|
} else {
|
||||||
throw newTranslatableError(
|
throw newTranslatableError(
|
||||||
"WARNING: Session already verified, but keys do NOT MATCH!",
|
"WARNING: session already verified, but keys do NOT MATCH!",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
src/Terms.ts
14
src/Terms.ts
|
@ -52,11 +52,13 @@ export type Policies = {
|
||||||
[policy: string]: Policy;
|
[policy: string]: Policy;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TermsInteractionCallback = (
|
export type ServicePolicyPair = {
|
||||||
policiesAndServicePairs: {
|
|
||||||
service: Service;
|
|
||||||
policies: Policies;
|
policies: Policies;
|
||||||
}[],
|
service: Service;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TermsInteractionCallback = (
|
||||||
|
policiesAndServicePairs: ServicePolicyPair[],
|
||||||
agreedUrls: string[],
|
agreedUrls: string[],
|
||||||
extraClassNames?: string,
|
extraClassNames?: string,
|
||||||
) => Promise<string[]>;
|
) => Promise<string[]>;
|
||||||
|
@ -117,9 +119,9 @@ export async function startTermsFlow(
|
||||||
// but then they'd assume they can un-check the boxes to un-agree to a policy,
|
// but then they'd assume they can un-check the boxes to un-agree to a policy,
|
||||||
// but that is not a thing the API supports, so probably best to just show
|
// but that is not a thing the API supports, so probably best to just show
|
||||||
// things they've not agreed to yet.
|
// things they've not agreed to yet.
|
||||||
const unagreedPoliciesAndServicePairs = [];
|
const unagreedPoliciesAndServicePairs: ServicePolicyPair[] = [];
|
||||||
for (const { service, policies } of policiesAndServicePairs) {
|
for (const { service, policies } of policiesAndServicePairs) {
|
||||||
const unagreedPolicies = {};
|
const unagreedPolicies: Policies = {};
|
||||||
for (const [policyName, policy] of Object.entries(policies)) {
|
for (const [policyName, policy] of Object.entries(policies)) {
|
||||||
let policyAgreed = false;
|
let policyAgreed = false;
|
||||||
for (const lang of Object.keys(policy)) {
|
for (const lang of Object.keys(policy)) {
|
||||||
|
|
|
@ -33,7 +33,7 @@ import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases";
|
||||||
import defaultDispatcher from "./dispatcher/dispatcher";
|
import defaultDispatcher from "./dispatcher/dispatcher";
|
||||||
import { MatrixClientPeg } from "./MatrixClientPeg";
|
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||||
import { ROOM_SECURITY_TAB } from "./components/views/dialogs/RoomSettingsDialog";
|
import { ROOM_SECURITY_TAB } from "./components/views/dialogs/RoomSettingsDialog";
|
||||||
import AccessibleButton from "./components/views/elements/AccessibleButton";
|
import AccessibleButton, { ButtonEvent } from "./components/views/elements/AccessibleButton";
|
||||||
import RightPanelStore from "./stores/right-panel/RightPanelStore";
|
import RightPanelStore from "./stores/right-panel/RightPanelStore";
|
||||||
import { highlightEvent, isLocationEvent } from "./utils/EventUtils";
|
import { highlightEvent, isLocationEvent } from "./utils/EventUtils";
|
||||||
import { ElementCall } from "./models/Call";
|
import { ElementCall } from "./models/Call";
|
||||||
|
@ -308,7 +308,7 @@ function textForServerACLEvent(ev: MatrixEvent): () => string | null {
|
||||||
allow_ip_literals: prevContent.allow_ip_literals !== false,
|
allow_ip_literals: prevContent.allow_ip_literals !== false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let getText = null;
|
let getText: () => string = null;
|
||||||
if (prev.deny.length === 0 && prev.allow.length === 0) {
|
if (prev.deny.length === 0 && prev.allow.length === 0) {
|
||||||
getText = () => _t("%(senderDisplayName)s set the server ACLs for this room.", { senderDisplayName });
|
getText = () => _t("%(senderDisplayName)s set the server ACLs for this room.", { senderDisplayName });
|
||||||
} else {
|
} else {
|
||||||
|
@ -360,8 +360,8 @@ function textForCanonicalAliasEvent(ev: MatrixEvent): () => string | null {
|
||||||
const oldAltAliases = ev.getPrevContent().alt_aliases || [];
|
const oldAltAliases = ev.getPrevContent().alt_aliases || [];
|
||||||
const newAlias = ev.getContent().alias;
|
const newAlias = ev.getContent().alias;
|
||||||
const newAltAliases = ev.getContent().alt_aliases || [];
|
const newAltAliases = ev.getContent().alt_aliases || [];
|
||||||
const removedAltAliases = oldAltAliases.filter((alias) => !newAltAliases.includes(alias));
|
const removedAltAliases = oldAltAliases.filter((alias: string) => !newAltAliases.includes(alias));
|
||||||
const addedAltAliases = newAltAliases.filter((alias) => !oldAltAliases.includes(alias));
|
const addedAltAliases = newAltAliases.filter((alias: string) => !oldAltAliases.includes(alias));
|
||||||
|
|
||||||
if (!removedAltAliases.length && !addedAltAliases.length) {
|
if (!removedAltAliases.length && !addedAltAliases.length) {
|
||||||
if (newAlias) {
|
if (newAlias) {
|
||||||
|
@ -533,8 +533,8 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
|
||||||
const senderName = getSenderName(event);
|
const senderName = getSenderName(event);
|
||||||
const roomId = event.getRoomId();
|
const roomId = event.getRoomId();
|
||||||
|
|
||||||
const pinned = event.getContent().pinned ?? [];
|
const pinned = event.getContent<{ pinned: string[] }>().pinned ?? [];
|
||||||
const previouslyPinned = event.getPrevContent().pinned ?? [];
|
const previouslyPinned: string[] = event.getPrevContent().pinned ?? [];
|
||||||
const newlyPinned = pinned.filter((item) => previouslyPinned.indexOf(item) < 0);
|
const newlyPinned = pinned.filter((item) => previouslyPinned.indexOf(item) < 0);
|
||||||
const newlyUnpinned = previouslyPinned.filter((item) => pinned.indexOf(item) < 0);
|
const newlyUnpinned = previouslyPinned.filter((item) => pinned.indexOf(item) < 0);
|
||||||
|
|
||||||
|
@ -550,7 +550,10 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
|
||||||
{ senderName },
|
{ senderName },
|
||||||
{
|
{
|
||||||
a: (sub) => (
|
a: (sub) => (
|
||||||
<AccessibleButton kind="link_inline" onClick={(e) => highlightEvent(roomId, messageId)}>
|
<AccessibleButton
|
||||||
|
kind="link_inline"
|
||||||
|
onClick={(e: ButtonEvent) => highlightEvent(roomId, messageId)}
|
||||||
|
>
|
||||||
{sub}
|
{sub}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
),
|
),
|
||||||
|
@ -580,7 +583,10 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
|
||||||
{ senderName },
|
{ senderName },
|
||||||
{
|
{
|
||||||
a: (sub) => (
|
a: (sub) => (
|
||||||
<AccessibleButton kind="link_inline" onClick={(e) => highlightEvent(roomId, messageId)}>
|
<AccessibleButton
|
||||||
|
kind="link_inline"
|
||||||
|
onClick={(e: ButtonEvent) => highlightEvent(roomId, messageId)}
|
||||||
|
>
|
||||||
{sub}
|
{sub}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
),
|
),
|
||||||
|
|
|
@ -168,7 +168,7 @@ export default class UserActivity {
|
||||||
return this.activeRecentlyTimeout.isRunning();
|
return this.activeRecentlyTimeout.isRunning();
|
||||||
}
|
}
|
||||||
|
|
||||||
private onPageVisibilityChanged = (e): void => {
|
private onPageVisibilityChanged = (e: Event): void => {
|
||||||
if (this.document.visibilityState === "hidden") {
|
if (this.document.visibilityState === "hidden") {
|
||||||
this.activeNowTimeout.abort();
|
this.activeNowTimeout.abort();
|
||||||
this.activeRecentlyTimeout.abort();
|
this.activeRecentlyTimeout.abort();
|
||||||
|
@ -182,11 +182,12 @@ export default class UserActivity {
|
||||||
this.activeRecentlyTimeout.abort();
|
this.activeRecentlyTimeout.abort();
|
||||||
};
|
};
|
||||||
|
|
||||||
private onUserActivity = (event: MouseEvent): void => {
|
// XXX: exported for tests
|
||||||
|
public onUserActivity = (event: Event): void => {
|
||||||
// ignore anything if the window isn't focused
|
// ignore anything if the window isn't focused
|
||||||
if (!this.document.hasFocus()) return;
|
if (!this.document.hasFocus()) return;
|
||||||
|
|
||||||
if (event.screenX && event.type === "mousemove") {
|
if (event.type === "mousemove" && this.isMouseEvent(event)) {
|
||||||
if (event.screenX === this.lastScreenX && event.screenY === this.lastScreenY) {
|
if (event.screenX === this.lastScreenX && event.screenY === this.lastScreenY) {
|
||||||
// mouse hasn't actually moved
|
// mouse hasn't actually moved
|
||||||
return;
|
return;
|
||||||
|
@ -223,4 +224,8 @@ export default class UserActivity {
|
||||||
}
|
}
|
||||||
attachedTimers.forEach((t) => t.abort());
|
attachedTimers.forEach((t) => t.abort());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isMouseEvent(event: Event): event is MouseEvent {
|
||||||
|
return event.type.startsWith("mouse");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ export default class VoipUserMapper {
|
||||||
return window.mxVoipUserMapper;
|
return window.mxVoipUserMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async userToVirtualUser(userId: string): Promise<string> {
|
private async userToVirtualUser(userId: string): Promise<string | null> {
|
||||||
const results = await LegacyCallHandler.instance.sipVirtualLookup(userId);
|
const results = await LegacyCallHandler.instance.sipVirtualLookup(userId);
|
||||||
if (results.length === 0 || !results[0].fields.lookup_success) return null;
|
if (results.length === 0 || !results[0].fields.lookup_success) return null;
|
||||||
return results[0].userid;
|
return results[0].userid;
|
||||||
|
@ -59,11 +59,11 @@ export default class VoipUserMapper {
|
||||||
if (!virtualUser) return null;
|
if (!virtualUser) return null;
|
||||||
|
|
||||||
const virtualRoomId = await ensureVirtualRoomExists(MatrixClientPeg.get(), virtualUser, roomId);
|
const virtualRoomId = await ensureVirtualRoomExists(MatrixClientPeg.get(), virtualUser, roomId);
|
||||||
MatrixClientPeg.get().setRoomAccountData(virtualRoomId, VIRTUAL_ROOM_EVENT_TYPE, {
|
MatrixClientPeg.get().setRoomAccountData(virtualRoomId!, VIRTUAL_ROOM_EVENT_TYPE, {
|
||||||
native_room: roomId,
|
native_room: roomId,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.virtualToNativeRoomIdCache.set(virtualRoomId, roomId);
|
this.virtualToNativeRoomIdCache.set(virtualRoomId!, roomId);
|
||||||
|
|
||||||
return virtualRoomId;
|
return virtualRoomId;
|
||||||
}
|
}
|
||||||
|
@ -72,9 +72,9 @@ export default class VoipUserMapper {
|
||||||
* Gets the ID of the virtual room for a room, or null if the room has no
|
* Gets the ID of the virtual room for a room, or null if the room has no
|
||||||
* virtual room
|
* virtual room
|
||||||
*/
|
*/
|
||||||
public async getVirtualRoomForRoom(roomId: string): Promise<Room | null> {
|
public async getVirtualRoomForRoom(roomId: string): Promise<Room | undefined> {
|
||||||
const virtualUser = await this.getVirtualUserForRoom(roomId);
|
const virtualUser = await this.getVirtualUserForRoom(roomId);
|
||||||
if (!virtualUser) return null;
|
if (!virtualUser) return undefined;
|
||||||
|
|
||||||
return findDMForUser(MatrixClientPeg.get(), virtualUser);
|
return findDMForUser(MatrixClientPeg.get(), virtualUser);
|
||||||
}
|
}
|
||||||
|
@ -121,8 +121,12 @@ export default class VoipUserMapper {
|
||||||
if (!LegacyCallHandler.instance.getSupportsVirtualRooms()) return;
|
if (!LegacyCallHandler.instance.getSupportsVirtualRooms()) return;
|
||||||
|
|
||||||
const inviterId = invitedRoom.getDMInviter();
|
const inviterId = invitedRoom.getDMInviter();
|
||||||
|
if (!inviterId) {
|
||||||
|
logger.error("Could not find DM inviter for room id: " + invitedRoom.roomId);
|
||||||
|
}
|
||||||
|
|
||||||
logger.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
|
logger.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
|
||||||
const result = await LegacyCallHandler.instance.sipNativeLookup(inviterId);
|
const result = await LegacyCallHandler.instance.sipNativeLookup(inviterId!);
|
||||||
if (result.length === 0) {
|
if (result.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -141,11 +145,11 @@ export default class VoipUserMapper {
|
||||||
// (possibly we should only join if we've also joined the native room, then we'd also have
|
// (possibly we should only join if we've also joined the native room, then we'd also have
|
||||||
// to make sure we joined virtual rooms on joining a native one)
|
// to make sure we joined virtual rooms on joining a native one)
|
||||||
MatrixClientPeg.get().joinRoom(invitedRoom.roomId);
|
MatrixClientPeg.get().joinRoom(invitedRoom.roomId);
|
||||||
}
|
|
||||||
|
|
||||||
// also put this room in the virtual room ID cache so isVirtualRoom return the right answer
|
// also put this room in the virtual room ID cache so isVirtualRoom return the right answer
|
||||||
// in however long it takes for the echo of setAccountData to come down the sync
|
// in however long it takes for the echo of setAccountData to come down the sync
|
||||||
this.virtualToNativeRoomIdCache.set(invitedRoom.roomId, nativeRoom.roomId);
|
this.virtualToNativeRoomIdCache.set(invitedRoom.roomId, nativeRoom.roomId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,11 +21,11 @@ import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||||
import { _t } from "./languageHandler";
|
import { _t } from "./languageHandler";
|
||||||
|
|
||||||
export function usersTypingApartFromMeAndIgnored(room: Room): RoomMember[] {
|
export function usersTypingApartFromMeAndIgnored(room: Room): RoomMember[] {
|
||||||
return usersTyping(room, [MatrixClientPeg.get().getUserId()].concat(MatrixClientPeg.get().getIgnoredUsers()));
|
return usersTyping(room, [MatrixClientPeg.get().getUserId()!].concat(MatrixClientPeg.get().getIgnoredUsers()));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usersTypingApartFromMe(room: Room): RoomMember[] {
|
export function usersTypingApartFromMe(room: Room): RoomMember[] {
|
||||||
return usersTyping(room, [MatrixClientPeg.get().getUserId()]);
|
return usersTyping(room, [MatrixClientPeg.get().getUserId()!]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,7 +36,7 @@ export function usersTypingApartFromMe(room: Room): RoomMember[] {
|
||||||
* @returns {RoomMember[]} list of user objects who are typing.
|
* @returns {RoomMember[]} list of user objects who are typing.
|
||||||
*/
|
*/
|
||||||
export function usersTyping(room: Room, exclude: string[] = []): RoomMember[] {
|
export function usersTyping(room: Room, exclude: string[] = []): RoomMember[] {
|
||||||
const whoIsTyping = [];
|
const whoIsTyping: RoomMember[] = [];
|
||||||
|
|
||||||
const memberKeys = Object.keys(room.currentState.members);
|
const memberKeys = Object.keys(room.currentState.members);
|
||||||
for (const userId of memberKeys) {
|
for (const userId of memberKeys) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {
|
||||||
KEYBOARD_SHORTCUTS,
|
KEYBOARD_SHORTCUTS,
|
||||||
MAC_ONLY_SHORTCUTS,
|
MAC_ONLY_SHORTCUTS,
|
||||||
} from "./KeyboardShortcuts";
|
} from "./KeyboardShortcuts";
|
||||||
|
import { IBaseSetting } from "../settings/Settings";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function gets the keyboard shortcuts that should be presented in the UI
|
* This function gets the keyboard shortcuts that should be presented in the UI
|
||||||
|
@ -103,7 +104,7 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.reduce((o, key) => {
|
.reduce((o, key) => {
|
||||||
o[key] = KEYBOARD_SHORTCUTS[key];
|
o[key as KeyBindingAction] = KEYBOARD_SHORTCUTS[key as KeyBindingAction];
|
||||||
return o;
|
return o;
|
||||||
}, {} as IKeyboardShortcuts);
|
}, {} as IKeyboardShortcuts);
|
||||||
};
|
};
|
||||||
|
@ -112,7 +113,10 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
|
||||||
* Gets keyboard shortcuts that should be presented to the user in the UI.
|
* Gets keyboard shortcuts that should be presented to the user in the UI.
|
||||||
*/
|
*/
|
||||||
export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => {
|
export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => {
|
||||||
const entries = [...Object.entries(getUIOnlyShortcuts()), ...Object.entries(getKeyboardShortcuts())];
|
const entries = [...Object.entries(getUIOnlyShortcuts()), ...Object.entries(getKeyboardShortcuts())] as [
|
||||||
|
KeyBindingAction,
|
||||||
|
IBaseSetting<KeyCombo>,
|
||||||
|
][];
|
||||||
|
|
||||||
return entries.reduce((acc, [key, value]) => {
|
return entries.reduce((acc, [key, value]) => {
|
||||||
acc[key] = value;
|
acc[key] = value;
|
||||||
|
@ -120,11 +124,11 @@ export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => {
|
||||||
}, {} as IKeyboardShortcuts);
|
}, {} as IKeyboardShortcuts);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getKeyboardShortcutValue = (name: string): KeyCombo | undefined => {
|
export const getKeyboardShortcutValue = (name: KeyBindingAction): KeyCombo | undefined => {
|
||||||
return getKeyboardShortcutsForUI()[name]?.default;
|
return getKeyboardShortcutsForUI()[name]?.default;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getKeyboardShortcutDisplayName = (name: string): string | undefined => {
|
export const getKeyboardShortcutDisplayName = (name: KeyBindingAction): string | undefined => {
|
||||||
const keyboardShortcutDisplayName = getKeyboardShortcutsForUI()[name]?.displayName;
|
const keyboardShortcutDisplayName = getKeyboardShortcutsForUI()[name]?.displayName;
|
||||||
return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName);
|
return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName as string);
|
||||||
};
|
};
|
||||||
|
|
|
@ -156,10 +156,8 @@ export enum KeyBindingAction {
|
||||||
|
|
||||||
type KeyboardShortcutSetting = IBaseSetting<KeyCombo>;
|
type KeyboardShortcutSetting = IBaseSetting<KeyCombo>;
|
||||||
|
|
||||||
export type IKeyboardShortcuts = {
|
// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
|
||||||
// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
|
export type IKeyboardShortcuts = Partial<Record<KeyBindingAction, KeyboardShortcutSetting>>;
|
||||||
[k in KeyBindingAction]?: KeyboardShortcutSetting;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface ICategory {
|
export interface ICategory {
|
||||||
categoryLabel?: string;
|
categoryLabel?: string;
|
||||||
|
|
|
@ -25,6 +25,7 @@ import React, {
|
||||||
Reducer,
|
Reducer,
|
||||||
Dispatch,
|
Dispatch,
|
||||||
RefObject,
|
RefObject,
|
||||||
|
ReactNode,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|
||||||
import { getKeyBindingsManager } from "../KeyBindingsManager";
|
import { getKeyBindingsManager } from "../KeyBindingsManager";
|
||||||
|
@ -158,8 +159,8 @@ interface IProps {
|
||||||
handleHomeEnd?: boolean;
|
handleHomeEnd?: boolean;
|
||||||
handleUpDown?: boolean;
|
handleUpDown?: boolean;
|
||||||
handleLeftRight?: boolean;
|
handleLeftRight?: boolean;
|
||||||
children(renderProps: { onKeyDownHandler(ev: React.KeyboardEvent) });
|
children(renderProps: { onKeyDownHandler(ev: React.KeyboardEvent): void }): ReactNode;
|
||||||
onKeyDown?(ev: React.KeyboardEvent, state: IState);
|
onKeyDown?(ev: React.KeyboardEvent, state: IState): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const findSiblingElement = (
|
export const findSiblingElement = (
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||||
|
|
||||||
interface IProps extends React.ComponentProps<typeof StyledCheckbox> {
|
interface IProps extends React.ComponentProps<typeof StyledCheckbox> {
|
||||||
label?: string;
|
label?: string;
|
||||||
onChange(); // we handle keyup/down ourselves so lose the ChangeEvent
|
onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent
|
||||||
onClose(): void; // gets called after onChange on KeyBindingAction.ActivateSelectedButton
|
onClose(): void; // gets called after onChange on KeyBindingAction.ActivateSelectedButton
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||||
|
|
||||||
interface IProps extends React.ComponentProps<typeof StyledRadioButton> {
|
interface IProps extends React.ComponentProps<typeof StyledRadioButton> {
|
||||||
label?: string;
|
label?: string;
|
||||||
onChange(); // we handle keyup/down ourselves so lose the ChangeEvent
|
onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent
|
||||||
onClose(): void; // gets called after onChange on KeyBindingAction.Enter
|
onClose(): void; // gets called after onChange on KeyBindingAction.Enter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ export const RovingAccessibleButton: React.FC<IProps> = ({ inputRef, onFocus, ..
|
||||||
return (
|
return (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
{...props}
|
{...props}
|
||||||
onFocus={(event) => {
|
onFocus={(event: React.FocusEvent) => {
|
||||||
onFocusInternal();
|
onFocusInternal();
|
||||||
onFocus?.(event);
|
onFocus?.(event);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const RovingAccessibleTooltipButton: React.FC<IProps> = ({ inputRef, onFo
|
||||||
return (
|
return (
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
{...props}
|
{...props}
|
||||||
onFocus={(event) => {
|
onFocus={(event: React.FocusEvent) => {
|
||||||
onFocusInternal();
|
onFocusInternal();
|
||||||
onFocus?.(event);
|
onFocus?.(event);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ReactElement } from "react";
|
||||||
|
|
||||||
import { useRovingTabIndex } from "../RovingTabIndex";
|
import { useRovingTabIndex } from "../RovingTabIndex";
|
||||||
import { FocusHandler, Ref } from "./types";
|
import { FocusHandler, Ref } from "./types";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
inputRef?: Ref;
|
inputRef?: Ref;
|
||||||
children(renderProps: { onFocus: FocusHandler; isActive: boolean; ref: Ref });
|
children(renderProps: { onFocus: FocusHandler; isActive: boolean; ref: Ref }): ReactElement<any, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper to allow use of useRovingTabIndex outside of React Functional Components.
|
// Wrapper to allow use of useRovingTabIndex outside of React Functional Components.
|
||||||
|
|
|
@ -54,7 +54,7 @@ export default class RoomListActions {
|
||||||
oldIndex: number | null,
|
oldIndex: number | null,
|
||||||
newIndex: number | null,
|
newIndex: number | null,
|
||||||
): AsyncActionPayload {
|
): AsyncActionPayload {
|
||||||
let metaData = null;
|
let metaData: Parameters<MatrixClient["setRoomTag"]>[2] | null = null;
|
||||||
|
|
||||||
// Is the tag ordered manually?
|
// Is the tag ordered manually?
|
||||||
const store = RoomListStore.instance;
|
const store = RoomListStore.instance;
|
||||||
|
@ -81,7 +81,7 @@ export default class RoomListActions {
|
||||||
return asyncAction(
|
return asyncAction(
|
||||||
"RoomListActions.tagRoom",
|
"RoomListActions.tagRoom",
|
||||||
() => {
|
() => {
|
||||||
const promises = [];
|
const promises: Promise<any>[] = [];
|
||||||
const roomId = room.roomId;
|
const roomId = room.roomId;
|
||||||
|
|
||||||
// Evil hack to get DMs behaving
|
// Evil hack to get DMs behaving
|
||||||
|
@ -120,7 +120,7 @@ export default class RoomListActions {
|
||||||
if (newTag && newTag !== DefaultTagID.DM && (hasChangedSubLists || metaData)) {
|
if (newTag && newTag !== DefaultTagID.DM && (hasChangedSubLists || metaData)) {
|
||||||
// metaData is the body of the PUT to set the tag, so it must
|
// metaData is the body of the PUT to set the tag, so it must
|
||||||
// at least be an empty object.
|
// at least be an empty object.
|
||||||
metaData = metaData || {};
|
metaData = metaData || ({} as typeof metaData);
|
||||||
|
|
||||||
const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function (err) {
|
const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function (err) {
|
||||||
logger.error("Failed to add tag " + newTag + " to room: " + err);
|
logger.error("Failed to add tag " + newTag + " to room: " + err);
|
||||||
|
|
|
@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ChangeEvent } from "react";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
import SdkConfig from "../../../../SdkConfig";
|
import SdkConfig from "../../../../SdkConfig";
|
||||||
|
@ -27,6 +28,7 @@ import Field from "../../../../components/views/elements/Field";
|
||||||
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
||||||
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
||||||
|
import { IIndexStats } from "../../../../indexing/BaseEventIndexManager";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {}
|
interface IProps extends IDialogProps {}
|
||||||
|
|
||||||
|
@ -43,7 +45,7 @@ interface IState {
|
||||||
* Allows the user to introspect the event index state and disable it.
|
* Allows the user to introspect the event index state and disable it.
|
||||||
*/
|
*/
|
||||||
export default class ManageEventIndexDialog extends React.Component<IProps, IState> {
|
export default class ManageEventIndexDialog extends React.Component<IProps, IState> {
|
||||||
public constructor(props) {
|
public constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -56,9 +58,9 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateCurrentRoom = async (room): Promise<void> => {
|
public updateCurrentRoom = async (room: Room): Promise<void> => {
|
||||||
const eventIndex = EventIndexPeg.get();
|
const eventIndex = EventIndexPeg.get();
|
||||||
let stats;
|
let stats: IIndexStats;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
stats = await eventIndex.getStats();
|
stats = await eventIndex.getStats();
|
||||||
|
@ -136,12 +138,12 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
|
||||||
Modal.createDialog(DisableEventIndexDialog, null, null, /* priority = */ false, /* static = */ true);
|
Modal.createDialog(DisableEventIndexDialog, null, null, /* priority = */ false, /* static = */ true);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onCrawlerSleepTimeChange = (e): void => {
|
private onCrawlerSleepTimeChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
this.setState({ crawlerSleepTime: e.target.value });
|
this.setState({ crawlerSleepTime: parseInt(e.target.value, 10) });
|
||||||
SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value);
|
SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const brand = SdkConfig.get().brand;
|
const brand = SdkConfig.get().brand;
|
||||||
|
|
||||||
let crawlerState;
|
let crawlerState;
|
||||||
|
|
|
@ -239,7 +239,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent<IProps, I
|
||||||
<form onSubmit={this.onPassPhraseNextClick}>
|
<form onSubmit={this.onPassPhraseNextClick}>
|
||||||
<p>
|
<p>
|
||||||
{_t(
|
{_t(
|
||||||
"<b>Warning</b>: You should only set up key backup from a trusted computer.",
|
"<b>Warning</b>: you should only set up key backup from a trusted computer.",
|
||||||
{},
|
{},
|
||||||
{ b: (sub) => <b>{sub}</b> },
|
{ b: (sub) => <b>{sub}</b> },
|
||||||
)}
|
)}
|
||||||
|
@ -459,7 +459,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent<IProps, I
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
let content;
|
let content;
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
content = (
|
content = (
|
||||||
|
|
|
@ -20,7 +20,7 @@ import FileSaver from "file-saver";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
||||||
import { TrustInfo } from "matrix-js-sdk/src/crypto/backup";
|
import { TrustInfo } from "matrix-js-sdk/src/crypto/backup";
|
||||||
import { CrossSigningKeys } from "matrix-js-sdk/src/matrix";
|
import { CrossSigningKeys, UIAFlow } from "matrix-js-sdk/src/matrix";
|
||||||
import { IRecoveryKey } from "matrix-js-sdk/src/crypto/api";
|
import { IRecoveryKey } from "matrix-js-sdk/src/crypto/api";
|
||||||
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
||||||
|
|
||||||
|
@ -206,7 +206,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
logger.log("uploadDeviceSigningKeys advertised no flows!");
|
logger.log("uploadDeviceSigningKeys advertised no flows!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const canUploadKeysWithPasswordOnly = error.data.flows.some((f) => {
|
const canUploadKeysWithPasswordOnly = error.data.flows.some((f: UIAFlow) => {
|
||||||
return f.stages.length === 1 && f.stages[0] === "m.login.password";
|
return f.stages.length === 1 && f.stages[0] === "m.login.password";
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -842,7 +842,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
let content;
|
let content;
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
content = (
|
content = (
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import FileSaver from "file-saver";
|
import FileSaver from "file-saver";
|
||||||
import React from "react";
|
import React, { ChangeEvent } from "react";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
} as Pick<IState, AnyPassphrase>);
|
} as Pick<IState, AnyPassphrase>);
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const disableForm = this.state.phase === Phase.Exporting;
|
const disableForm = this.state.phase === Phase.Exporting;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -163,7 +163,9 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
<Field
|
<Field
|
||||||
label={_t("Enter passphrase")}
|
label={_t("Enter passphrase")}
|
||||||
value={this.state.passphrase1}
|
value={this.state.passphrase1}
|
||||||
onChange={(e) => this.onPassphraseChange(e, "passphrase1")}
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
this.onPassphraseChange(e, "passphrase1")
|
||||||
|
}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
size={64}
|
size={64}
|
||||||
type="password"
|
type="password"
|
||||||
|
@ -174,7 +176,9 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
<Field
|
<Field
|
||||||
label={_t("Confirm passphrase")}
|
label={_t("Confirm passphrase")}
|
||||||
value={this.state.passphrase2}
|
value={this.state.passphrase2}
|
||||||
onChange={(e) => this.onPassphraseChange(e, "passphrase2")}
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
this.onPassphraseChange(e, "passphrase2")
|
||||||
|
}
|
||||||
size={64}
|
size={64}
|
||||||
type="password"
|
type="password"
|
||||||
disabled={disableForm}
|
disabled={disableForm}
|
||||||
|
|
|
@ -73,9 +73,9 @@ export default class ImportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
}
|
}
|
||||||
|
|
||||||
private onFormChange = (): void => {
|
private onFormChange = (): void => {
|
||||||
const files = this.file.current.files || [];
|
const files = this.file.current.files;
|
||||||
this.setState({
|
this.setState({
|
||||||
enableSubmit: this.state.passphrase !== "" && files.length > 0,
|
enableSubmit: this.state.passphrase !== "" && !!files?.length,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ export default class ImportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const disableForm = this.state.phase !== Phase.Edit;
|
const disableForm = this.state.phase !== Phase.Edit;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -48,13 +48,13 @@ export default class NewRecoveryMethodDialog extends React.PureComponent<IProps>
|
||||||
{
|
{
|
||||||
onFinished: this.props.onFinished,
|
onFinished: this.props.onFinished,
|
||||||
},
|
},
|
||||||
null,
|
undefined,
|
||||||
/* priority = */ false,
|
/* priority = */ false,
|
||||||
/* static = */ true,
|
/* static = */ true,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const title = <span className="mx_KeyBackupFailedDialog_title">{_t("New Recovery Method")}</span>;
|
const title = <span className="mx_KeyBackupFailedDialog_title">{_t("New Recovery Method")}</span>;
|
||||||
|
|
||||||
const newMethodDetected = <p>{_t("A new Security Phrase and key for Secure Messages have been detected.")}</p>;
|
const newMethodDetected = <p>{_t("A new Security Phrase and key for Secure Messages have been detected.")}</p>;
|
||||||
|
|
|
@ -37,14 +37,14 @@ export default class RecoveryMethodRemovedDialog extends React.PureComponent<IPr
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
Modal.createDialogAsync(
|
Modal.createDialogAsync(
|
||||||
import("./CreateKeyBackupDialog") as unknown as Promise<ComponentType<{}>>,
|
import("./CreateKeyBackupDialog") as unknown as Promise<ComponentType<{}>>,
|
||||||
null,
|
undefined,
|
||||||
null,
|
null,
|
||||||
/* priority = */ false,
|
/* priority = */ false,
|
||||||
/* static = */ true,
|
/* static = */ true,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const title = <span className="mx_KeyBackupFailedDialog_title">{_t("Recovery Method Removed")}</span>;
|
const title = <span className="mx_KeyBackupFailedDialog_title">{_t("Recovery Method Removed")}</span>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -91,7 +91,7 @@ export class PlaybackQueue {
|
||||||
|
|
||||||
public unsortedEnqueue(mxEvent: MatrixEvent, playback: Playback): void {
|
public unsortedEnqueue(mxEvent: MatrixEvent, playback: Playback): void {
|
||||||
// We don't ever detach our listeners: we expect the Playback to clean up for us
|
// We don't ever detach our listeners: we expect the Playback to clean up for us
|
||||||
this.playbacks.set(mxEvent.getId(), playback);
|
this.playbacks.set(mxEvent.getId()!, playback);
|
||||||
playback.on(UPDATE_EVENT, (state) => this.onPlaybackStateChange(playback, mxEvent, state));
|
playback.on(UPDATE_EVENT, (state) => this.onPlaybackStateChange(playback, mxEvent, state));
|
||||||
playback.clockInfo.liveData.onUpdate((clock) => this.onPlaybackClock(playback, mxEvent, clock));
|
playback.clockInfo.liveData.onUpdate((clock) => this.onPlaybackClock(playback, mxEvent, clock));
|
||||||
}
|
}
|
||||||
|
@ -99,12 +99,12 @@ export class PlaybackQueue {
|
||||||
private onPlaybackStateChange(playback: Playback, mxEvent: MatrixEvent, newState: PlaybackState): void {
|
private onPlaybackStateChange(playback: Playback, mxEvent: MatrixEvent, newState: PlaybackState): void {
|
||||||
// Remember where the user got to in playback
|
// Remember where the user got to in playback
|
||||||
const wasLastPlaying = this.currentPlaybackId === mxEvent.getId();
|
const wasLastPlaying = this.currentPlaybackId === mxEvent.getId();
|
||||||
if (newState === PlaybackState.Stopped && this.clockStates.has(mxEvent.getId()) && !wasLastPlaying) {
|
if (newState === PlaybackState.Stopped && this.clockStates.has(mxEvent.getId()!) && !wasLastPlaying) {
|
||||||
// noinspection JSIgnoredPromiseFromCall
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
playback.skipTo(this.clockStates.get(mxEvent.getId())!);
|
playback.skipTo(this.clockStates.get(mxEvent.getId()!)!);
|
||||||
} else if (newState === PlaybackState.Stopped) {
|
} else if (newState === PlaybackState.Stopped) {
|
||||||
// Remove the now-useless clock for some space savings
|
// Remove the now-useless clock for some space savings
|
||||||
this.clockStates.delete(mxEvent.getId());
|
this.clockStates.delete(mxEvent.getId()!);
|
||||||
|
|
||||||
if (wasLastPlaying) {
|
if (wasLastPlaying) {
|
||||||
this.recentFullPlays.add(this.currentPlaybackId);
|
this.recentFullPlays.add(this.currentPlaybackId);
|
||||||
|
@ -133,7 +133,7 @@ export class PlaybackQueue {
|
||||||
// timeline is already most recent last, so we can iterate down that.
|
// timeline is already most recent last, so we can iterate down that.
|
||||||
const timeline = arrayFastClone(this.room.getLiveTimeline().getEvents());
|
const timeline = arrayFastClone(this.room.getLiveTimeline().getEvents());
|
||||||
let scanForVoiceMessage = false;
|
let scanForVoiceMessage = false;
|
||||||
let nextEv: MatrixEvent;
|
let nextEv: MatrixEvent | undefined;
|
||||||
for (const event of timeline) {
|
for (const event of timeline) {
|
||||||
if (event.getId() === mxEvent.getId()) {
|
if (event.getId() === mxEvent.getId()) {
|
||||||
scanForVoiceMessage = true;
|
scanForVoiceMessage = true;
|
||||||
|
@ -149,8 +149,8 @@ export class PlaybackQueue {
|
||||||
break; // Stop automatic playback: next useful event is not a voice message
|
break; // Stop automatic playback: next useful event is not a voice message
|
||||||
}
|
}
|
||||||
|
|
||||||
const havePlayback = this.playbacks.has(event.getId());
|
const havePlayback = this.playbacks.has(event.getId()!);
|
||||||
const isRecentlyCompleted = this.recentFullPlays.has(event.getId());
|
const isRecentlyCompleted = this.recentFullPlays.has(event.getId()!);
|
||||||
if (havePlayback && !isRecentlyCompleted) {
|
if (havePlayback && !isRecentlyCompleted) {
|
||||||
nextEv = event;
|
nextEv = event;
|
||||||
break;
|
break;
|
||||||
|
@ -164,7 +164,7 @@ export class PlaybackQueue {
|
||||||
} else {
|
} else {
|
||||||
this.playbackIdOrder = orderClone;
|
this.playbackIdOrder = orderClone;
|
||||||
|
|
||||||
const instance = this.playbacks.get(nextEv.getId());
|
const instance = this.playbacks.get(nextEv.getId()!);
|
||||||
PlaybackManager.instance.pauseAllExcept(instance);
|
PlaybackManager.instance.pauseAllExcept(instance);
|
||||||
|
|
||||||
// This should cause a Play event, which will re-populate our playback order
|
// This should cause a Play event, which will re-populate our playback order
|
||||||
|
@ -196,7 +196,7 @@ export class PlaybackQueue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentPlaybackId = mxEvent.getId();
|
this.currentPlaybackId = mxEvent.getId()!;
|
||||||
if (order.length === 0 || order[order.length - 1] !== this.currentPlaybackId) {
|
if (order.length === 0 || order[order.length - 1] !== this.currentPlaybackId) {
|
||||||
order.push(this.currentPlaybackId);
|
order.push(this.currentPlaybackId);
|
||||||
}
|
}
|
||||||
|
@ -214,7 +214,7 @@ export class PlaybackQueue {
|
||||||
if (playback.currentState === PlaybackState.Decoding) return; // ignore pre-ready values
|
if (playback.currentState === PlaybackState.Decoding) return; // ignore pre-ready values
|
||||||
|
|
||||||
if (playback.currentState !== PlaybackState.Stopped) {
|
if (playback.currentState !== PlaybackState.Stopped) {
|
||||||
this.clockStates.set(mxEvent.getId(), clocks[0]); // [0] is the current seek position
|
this.clockStates.set(mxEvent.getId()!, clocks[0]); // [0] is the current seek position
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,11 @@ class MxVoiceWorklet extends AudioWorkletProcessor {
|
||||||
private nextAmplitudeSecond = 0;
|
private nextAmplitudeSecond = 0;
|
||||||
private amplitudeIndex = 0;
|
private amplitudeIndex = 0;
|
||||||
|
|
||||||
public process(inputs, outputs, parameters): boolean {
|
public process(
|
||||||
|
inputs: Float32Array[][],
|
||||||
|
outputs: Float32Array[][],
|
||||||
|
parameters: Record<string, Float32Array>,
|
||||||
|
): boolean {
|
||||||
const currentSecond = roundTimeToTargetFreq(currentTime);
|
const currentSecond = roundTimeToTargetFreq(currentTime);
|
||||||
// We special case the first ping because there's a fairly good chance that we'll miss the zeroth
|
// We special case the first ping because there's a fairly good chance that we'll miss the zeroth
|
||||||
// update. Firefox for instance takes 0.06 seconds (roughly) to call this function for the first
|
// update. Firefox for instance takes 0.06 seconds (roughly) to call this function for the first
|
||||||
|
|
|
@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
import Recorder from "opus-recorder/dist/recorder.min.js";
|
import Recorder from "opus-recorder/dist/recorder.min.js";
|
||||||
import encoderPath from "opus-recorder/dist/encoderWorker.min.js";
|
import encoderPath from "opus-recorder/dist/encoderWorker.min.js";
|
||||||
import { SimpleObservable } from "matrix-widget-api";
|
import { SimpleObservable } from "matrix-widget-api";
|
||||||
|
@ -78,7 +77,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
|
||||||
private targetMaxLength: number | null = TARGET_MAX_LENGTH;
|
private targetMaxLength: number | null = TARGET_MAX_LENGTH;
|
||||||
public amplitudes: number[] = []; // at each second mark, generated
|
public amplitudes: number[] = []; // at each second mark, generated
|
||||||
private liveWaveform = new FixedRollingArray(RECORDING_PLAYBACK_SAMPLES, 0);
|
private liveWaveform = new FixedRollingArray(RECORDING_PLAYBACK_SAMPLES, 0);
|
||||||
public onDataAvailable: (data: ArrayBuffer) => void;
|
public onDataAvailable?: (data: ArrayBuffer) => void;
|
||||||
|
|
||||||
public get contentType(): string {
|
public get contentType(): string {
|
||||||
return "audio/ogg";
|
return "audio/ogg";
|
||||||
|
@ -182,7 +181,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
|
||||||
});
|
});
|
||||||
|
|
||||||
// not using EventEmitter here because it leads to detached bufferes
|
// not using EventEmitter here because it leads to detached bufferes
|
||||||
this.recorder.ondataavailable = (data: ArrayBuffer) => this?.onDataAvailable(data);
|
this.recorder.ondataavailable = (data: ArrayBuffer) => this.onDataAvailable?.(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Error starting recording: ", e);
|
logger.error("Error starting recording: ", e);
|
||||||
if (e instanceof DOMException) {
|
if (e instanceof DOMException) {
|
||||||
|
|
|
@ -69,7 +69,7 @@ export function decodeOgg(audioBuffer: ArrayBuffer): Promise<ArrayBuffer> {
|
||||||
command: "encode",
|
command: "encode",
|
||||||
buffers: ev.data,
|
buffers: ev.data,
|
||||||
},
|
},
|
||||||
ev.data.map((b) => b.buffer),
|
ev.data.map((b: Float32Array) => b.buffer),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ export default abstract class AutocompleteProvider {
|
||||||
* @param {boolean} force True if the user is forcing completion
|
* @param {boolean} force True if the user is forcing completion
|
||||||
* @return {object} { command, range } where both objects fields are null if no match
|
* @return {object} { command, range } where both objects fields are null if no match
|
||||||
*/
|
*/
|
||||||
public getCurrentCommand(query: string, selection: ISelectionRange, force = false): ICommand | null {
|
public getCurrentCommand(query: string, selection: ISelectionRange, force = false): Partial<ICommand> {
|
||||||
let commandRegex = this.commandRegex;
|
let commandRegex = this.commandRegex;
|
||||||
|
|
||||||
if (force && this.shouldForceComplete()) {
|
if (force && this.shouldForceComplete()) {
|
||||||
|
@ -78,7 +78,7 @@ export default abstract class AutocompleteProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!commandRegex) {
|
if (!commandRegex) {
|
||||||
return null;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
commandRegex.lastIndex = 0;
|
commandRegex.lastIndex = 0;
|
||||||
|
|
|
@ -95,7 +95,7 @@ export default class CommandProvider extends AutocompleteProvider {
|
||||||
description={_t(result.description)}
|
description={_t(result.description)}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
range,
|
range: range!,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { uniq, sortBy } from "lodash";
|
import { uniq, sortBy, ListIteratee } from "lodash";
|
||||||
import EMOTICON_REGEX from "emojibase-regex/emoticon";
|
import EMOTICON_REGEX from "emojibase-regex/emoticon";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
|
@ -55,7 +55,11 @@ const SORTED_EMOJI: ISortedEmoji[] = EMOJI.sort((a, b) => {
|
||||||
_orderBy: index,
|
_orderBy: index,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function score(query: string, space: string): number {
|
function score(query: string, space: string[] | string): number {
|
||||||
|
if (Array.isArray(space)) {
|
||||||
|
return Math.min(...space.map((s) => score(query, s)));
|
||||||
|
}
|
||||||
|
|
||||||
const index = space.indexOf(query);
|
const index = space.indexOf(query);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
return Infinity;
|
return Infinity;
|
||||||
|
@ -90,7 +94,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
shouldMatchWordsOnly: true,
|
shouldMatchWordsOnly: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.recentlyUsed = Array.from(new Set(recent.get().map(getEmojiFromUnicode).filter(Boolean)));
|
this.recentlyUsed = Array.from(new Set(recent.get().map(getEmojiFromUnicode).filter(Boolean))) as IEmoji[];
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getCompletions(
|
public async getCompletions(
|
||||||
|
@ -113,7 +117,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
// Do second match with shouldMatchWordsOnly in order to match against 'name'
|
// Do second match with shouldMatchWordsOnly in order to match against 'name'
|
||||||
completions = completions.concat(this.nameMatcher.match(matchedString));
|
completions = completions.concat(this.nameMatcher.match(matchedString));
|
||||||
|
|
||||||
let sorters = [];
|
let sorters: ListIteratee<ISortedEmoji>[] = [];
|
||||||
// make sure that emoticons come first
|
// make sure that emoticons come first
|
||||||
sorters.push((c) => score(matchedString, c.emoji.emoticon || ""));
|
sorters.push((c) => score(matchedString, c.emoji.emoticon || ""));
|
||||||
|
|
||||||
|
@ -148,7 +152,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
<span>{c.emoji.unicode}</span>
|
<span>{c.emoji.unicode}</span>
|
||||||
</PillCompletion>
|
</PillCompletion>
|
||||||
),
|
),
|
||||||
range,
|
range: range!,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
|
|
|
@ -40,12 +40,13 @@ export default class NotifProvider extends AutocompleteProvider {
|
||||||
): Promise<ICompletion[]> {
|
): Promise<ICompletion[]> {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
|
|
||||||
if (!this.room.currentState.mayTriggerNotifOfType("room", client.credentials.userId)) return [];
|
if (!this.room.currentState.mayTriggerNotifOfType("room", client.credentials.userId!)) return [];
|
||||||
|
|
||||||
const { command, range } = this.getCurrentCommand(query, selection, force);
|
const { command, range } = this.getCurrentCommand(query, selection, force);
|
||||||
if (
|
if (
|
||||||
command?.[0].length > 1 &&
|
command?.[0] &&
|
||||||
["@room", "@channel", "@everyone", "@here"].some((c) => c.startsWith(command[0]))
|
command[0].length > 1 &&
|
||||||
|
["@room", "@channel", "@everyone", "@here"].some((c) => c.startsWith(command![0]))
|
||||||
) {
|
) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -58,7 +59,7 @@ export default class NotifProvider extends AutocompleteProvider {
|
||||||
<RoomAvatar width={24} height={24} room={this.room} />
|
<RoomAvatar width={24} height={24} room={this.room} />
|
||||||
</PillCompletion>
|
</PillCompletion>
|
||||||
),
|
),
|
||||||
range,
|
range: range!,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ export default class QueryMatcher<T extends {}> {
|
||||||
if (!this._items.has(key)) {
|
if (!this._items.has(key)) {
|
||||||
this._items.set(key, []);
|
this._items.set(key, []);
|
||||||
}
|
}
|
||||||
this._items.get(key).push({
|
this._items.get(key)!.push({
|
||||||
keyWeight: Number(index),
|
keyWeight: Number(index),
|
||||||
object,
|
object,
|
||||||
});
|
});
|
||||||
|
@ -104,7 +104,11 @@ export default class QueryMatcher<T extends {}> {
|
||||||
if (query.length === 0) {
|
if (query.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const matches = [];
|
const matches: {
|
||||||
|
index: number;
|
||||||
|
object: T;
|
||||||
|
keyWeight: number;
|
||||||
|
}[] = [];
|
||||||
// Iterate through the map & check each key.
|
// Iterate through the map & check each key.
|
||||||
// ES6 Map iteration order is defined to be insertion order, so results
|
// ES6 Map iteration order is defined to be insertion order, so results
|
||||||
// here will come out in the order they were put in.
|
// here will come out in the order they were put in.
|
||||||
|
|
|
@ -39,12 +39,12 @@ function canonicalScore(displayedAlias: string, room: Room): number {
|
||||||
|
|
||||||
function matcherObject(
|
function matcherObject(
|
||||||
room: Room,
|
room: Room,
|
||||||
displayedAlias: string | null,
|
displayedAlias: string,
|
||||||
matchName = "",
|
matchName = "",
|
||||||
): {
|
): {
|
||||||
room: Room;
|
room: Room;
|
||||||
matchName: string;
|
matchName: string;
|
||||||
displayedAlias: string | null;
|
displayedAlias: string;
|
||||||
} {
|
} {
|
||||||
return {
|
return {
|
||||||
room,
|
room,
|
||||||
|
@ -81,7 +81,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
||||||
// the only reason we need to do this is because Fuse only matches on properties
|
// the only reason we need to do this is because Fuse only matches on properties
|
||||||
let matcherObjects = this.getRooms().reduce<ReturnType<typeof matcherObject>[]>((aliases, room) => {
|
let matcherObjects = this.getRooms().reduce<ReturnType<typeof matcherObject>[]>((aliases, room) => {
|
||||||
if (room.getCanonicalAlias()) {
|
if (room.getCanonicalAlias()) {
|
||||||
aliases = aliases.concat(matcherObject(room, room.getCanonicalAlias(), room.name));
|
aliases = aliases.concat(matcherObject(room, room.getCanonicalAlias()!, room.name));
|
||||||
}
|
}
|
||||||
if (room.getAltAliases().length) {
|
if (room.getAltAliases().length) {
|
||||||
const altAliases = room.getAltAliases().map((alias) => matcherObject(room, alias));
|
const altAliases = room.getAltAliases().map((alias) => matcherObject(room, alias));
|
||||||
|
@ -122,7 +122,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
||||||
<RoomAvatar width={24} height={24} room={room.room} />
|
<RoomAvatar width={24} height={24} room={room.room} />
|
||||||
</PillCompletion>
|
</PillCompletion>
|
||||||
),
|
),
|
||||||
range,
|
range: range!,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.filter((completion) => !!completion.completion && completion.completion.length > 0);
|
.filter((completion) => !!completion.completion && completion.completion.length > 0);
|
||||||
|
|
|
@ -44,7 +44,7 @@ const FORCED_USER_REGEX = /[^/,:; \t\n]\S*/g;
|
||||||
|
|
||||||
export default class UserProvider extends AutocompleteProvider {
|
export default class UserProvider extends AutocompleteProvider {
|
||||||
public matcher: QueryMatcher<RoomMember>;
|
public matcher: QueryMatcher<RoomMember>;
|
||||||
public users: RoomMember[];
|
public users: RoomMember[] | null;
|
||||||
public room: Room;
|
public room: Room;
|
||||||
|
|
||||||
public constructor(room: Room, renderingType?: TimelineRenderingType) {
|
public constructor(room: Room, renderingType?: TimelineRenderingType) {
|
||||||
|
@ -54,7 +54,7 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
renderingType,
|
renderingType,
|
||||||
});
|
});
|
||||||
this.room = room;
|
this.room = room;
|
||||||
this.matcher = new QueryMatcher([], {
|
this.matcher = new QueryMatcher<RoomMember>([], {
|
||||||
keys: ["name"],
|
keys: ["name"],
|
||||||
funcs: [(obj) => obj.userId.slice(1)], // index by user id minus the leading '@'
|
funcs: [(obj) => obj.userId.slice(1)], // index by user id minus the leading '@'
|
||||||
shouldMatchWordsOnly: false,
|
shouldMatchWordsOnly: false,
|
||||||
|
@ -73,7 +73,7 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
|
|
||||||
private onRoomTimeline = (
|
private onRoomTimeline = (
|
||||||
ev: MatrixEvent,
|
ev: MatrixEvent,
|
||||||
room: Room | null,
|
room: Room | undefined,
|
||||||
toStartOfTimeline: boolean,
|
toStartOfTimeline: boolean,
|
||||||
removed: boolean,
|
removed: boolean,
|
||||||
data: IRoomTimelineData,
|
data: IRoomTimelineData,
|
||||||
|
@ -110,18 +110,15 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
// lazy-load user list into matcher
|
// lazy-load user list into matcher
|
||||||
if (!this.users) this.makeUsers();
|
if (!this.users) this.makeUsers();
|
||||||
|
|
||||||
let completions = [];
|
|
||||||
const { command, range } = this.getCurrentCommand(rawQuery, selection, force);
|
const { command, range } = this.getCurrentCommand(rawQuery, selection, force);
|
||||||
|
|
||||||
if (!command) return completions;
|
const fullMatch = command?.[0];
|
||||||
|
|
||||||
const fullMatch = command[0];
|
|
||||||
// Don't search if the query is a single "@"
|
// Don't search if the query is a single "@"
|
||||||
if (fullMatch && fullMatch !== "@") {
|
if (fullMatch && fullMatch !== "@") {
|
||||||
// Don't include the '@' in our search query - it's only used as a way to trigger completion
|
// Don't include the '@' in our search query - it's only used as a way to trigger completion
|
||||||
const query = fullMatch.startsWith("@") ? fullMatch.substring(1) : fullMatch;
|
const query = fullMatch.startsWith("@") ? fullMatch.substring(1) : fullMatch;
|
||||||
completions = this.matcher.match(query, limit).map((user) => {
|
return this.matcher.match(query, limit).map((user) => {
|
||||||
const description = UserIdentifierCustomisations.getDisplayUserIdentifier(user.userId, {
|
const description = UserIdentifierCustomisations.getDisplayUserIdentifier?.(user.userId, {
|
||||||
roomId: this.room.roomId,
|
roomId: this.room.roomId,
|
||||||
withDisplayName: true,
|
withDisplayName: true,
|
||||||
});
|
});
|
||||||
|
@ -132,18 +129,18 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
completion: user.rawDisplayName,
|
completion: user.rawDisplayName,
|
||||||
completionId: user.userId,
|
completionId: user.userId,
|
||||||
type: "user",
|
type: "user",
|
||||||
suffix: selection.beginning && range.start === 0 ? ": " : " ",
|
suffix: selection.beginning && range!.start === 0 ? ": " : " ",
|
||||||
href: makeUserPermalink(user.userId),
|
href: makeUserPermalink(user.userId),
|
||||||
component: (
|
component: (
|
||||||
<PillCompletion title={displayName} description={description}>
|
<PillCompletion title={displayName} description={description}>
|
||||||
<MemberAvatar member={user} width={24} height={24} />
|
<MemberAvatar member={user} width={24} height={24} />
|
||||||
</PillCompletion>
|
</PillCompletion>
|
||||||
),
|
),
|
||||||
range,
|
range: range!,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return completions;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public getName(): string {
|
public getName(): string {
|
||||||
|
@ -152,10 +149,10 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
|
|
||||||
private makeUsers(): void {
|
private makeUsers(): void {
|
||||||
const events = this.room.getLiveTimeline().getEvents();
|
const events = this.room.getLiveTimeline().getEvents();
|
||||||
const lastSpoken = {};
|
const lastSpoken: Record<string, number> = {};
|
||||||
|
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
lastSpoken[event.getSender()] = event.getTs();
|
lastSpoken[event.getSender()!] = event.getTs();
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUserId = MatrixClientPeg.get().credentials.userId;
|
const currentUserId = MatrixClientPeg.get().credentials.userId;
|
||||||
|
@ -167,7 +164,7 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
this.matcher.setObjects(this.users);
|
this.matcher.setObjects(this.users);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onUserSpoke(user: RoomMember): void {
|
public onUserSpoke(user: RoomMember | null): void {
|
||||||
if (!this.users) return;
|
if (!this.users) return;
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
if (user.userId === MatrixClientPeg.get().credentials.userId) return;
|
if (user.userId === MatrixClientPeg.get().credentials.userId) return;
|
||||||
|
|
|
@ -55,7 +55,7 @@ export default class AutoHideScrollbar<T extends keyof JSX.IntrinsicElements> ex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { element, className, onScroll, tabIndex, wrappedRef, children, ...otherProps } = this.props;
|
const { element, className, onScroll, tabIndex, wrappedRef, children, ...otherProps } = this.props;
|
||||||
|
|
||||||
|
|
|
@ -99,9 +99,9 @@ export interface IProps extends MenuProps {
|
||||||
closeOnInteraction?: boolean;
|
closeOnInteraction?: boolean;
|
||||||
|
|
||||||
// Function to be called on menu close
|
// Function to be called on menu close
|
||||||
onFinished();
|
onFinished(): void;
|
||||||
// on resize callback
|
// on resize callback
|
||||||
windowResize?();
|
windowResize?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -119,8 +119,8 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||||
managed: true,
|
managed: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
public constructor(props, context) {
|
public constructor(props: IProps) {
|
||||||
super(props, context);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
contextMenuElem: null,
|
contextMenuElem: null,
|
||||||
|
@ -387,13 +387,13 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||||
menuStyle["paddingRight"] = menuPaddingRight;
|
menuStyle["paddingRight"] = menuPaddingRight;
|
||||||
}
|
}
|
||||||
|
|
||||||
const wrapperStyle = {};
|
const wrapperStyle: CSSProperties = {};
|
||||||
if (!isNaN(Number(zIndex))) {
|
if (!isNaN(Number(zIndex))) {
|
||||||
menuStyle["zIndex"] = zIndex + 1;
|
menuStyle["zIndex"] = zIndex + 1;
|
||||||
wrapperStyle["zIndex"] = zIndex;
|
wrapperStyle["zIndex"] = zIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
let background;
|
let background: JSX.Element;
|
||||||
if (hasBackground) {
|
if (hasBackground) {
|
||||||
background = (
|
background = (
|
||||||
<div
|
<div
|
||||||
|
@ -624,7 +624,7 @@ export function createMenu(
|
||||||
ElementClass: typeof React.Component,
|
ElementClass: typeof React.Component,
|
||||||
props: Record<string, any>,
|
props: Record<string, any>,
|
||||||
): { close: (...args: any[]) => void } {
|
): { close: (...args: any[]) => void } {
|
||||||
const onFinished = function (...args): void {
|
const onFinished = function (...args: any[]): void {
|
||||||
ReactDOM.unmountComponentAtNode(getOrCreateContainer());
|
ReactDOM.unmountComponentAtNode(getOrCreateContainer());
|
||||||
props?.onFinished?.apply(null, args);
|
props?.onFinished?.apply(null, args);
|
||||||
};
|
};
|
||||||
|
|
|
@ -118,7 +118,7 @@ export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
// HACK: Workaround for the context's MatrixClient not updating.
|
// HACK: Workaround for the context's MatrixClient not updating.
|
||||||
const client = this.context || MatrixClientPeg.get();
|
const client = this.context || MatrixClientPeg.get();
|
||||||
const isGuest = client ? client.isGuest() : true;
|
const isGuest = client ? client.isGuest() : true;
|
||||||
|
|
|
@ -43,7 +43,7 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
timelineSet: EventTimelineSet;
|
timelineSet: EventTimelineSet | null;
|
||||||
narrow: boolean;
|
narrow: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ class FilePanel extends React.Component<IProps, IState> {
|
||||||
public noRoom: boolean;
|
public noRoom: boolean;
|
||||||
private card = createRef<HTMLDivElement>();
|
private card = createRef<HTMLDivElement>();
|
||||||
|
|
||||||
public state = {
|
public state: IState = {
|
||||||
timelineSet: null,
|
timelineSet: null,
|
||||||
narrow: false,
|
narrow: false,
|
||||||
};
|
};
|
||||||
|
@ -223,7 +223,7 @@ class FilePanel extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
return (
|
return (
|
||||||
<BaseCard className="mx_FilePanel mx_RoomView_messageListWrapper" onClose={this.props.onClose}>
|
<BaseCard className="mx_FilePanel mx_RoomView_messageListWrapper" onClose={this.props.onClose}>
|
||||||
|
|
|
@ -79,6 +79,12 @@ export function GenericDropdownMenuGroup<T extends Key>({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isGenericDropdownMenuGroupArray<T>(
|
||||||
|
items: readonly GenericDropdownMenuItem<T>[],
|
||||||
|
): items is GenericDropdownMenuGroup<T>[] {
|
||||||
|
return isGenericDropdownMenuGroup(items[0]);
|
||||||
|
}
|
||||||
|
|
||||||
function isGenericDropdownMenuGroup<T>(item: GenericDropdownMenuItem<T>): item is GenericDropdownMenuGroup<T> {
|
function isGenericDropdownMenuGroup<T>(item: GenericDropdownMenuItem<T>): item is GenericDropdownMenuGroup<T> {
|
||||||
return "options" in item;
|
return "options" in item;
|
||||||
}
|
}
|
||||||
|
@ -123,19 +129,19 @@ export function GenericDropdownMenu<T>({
|
||||||
.flatMap((it) => (isGenericDropdownMenuGroup(it) ? [it, ...it.options] : [it]))
|
.flatMap((it) => (isGenericDropdownMenuGroup(it) ? [it, ...it.options] : [it]))
|
||||||
.find((option) => (toKey ? toKey(option.key) === toKey(value) : option.key === value));
|
.find((option) => (toKey ? toKey(option.key) === toKey(value) : option.key === value));
|
||||||
let contextMenuOptions: JSX.Element;
|
let contextMenuOptions: JSX.Element;
|
||||||
if (options && isGenericDropdownMenuGroup(options[0])) {
|
if (options && isGenericDropdownMenuGroupArray(options)) {
|
||||||
contextMenuOptions = (
|
contextMenuOptions = (
|
||||||
<>
|
<>
|
||||||
{options.map((group) => (
|
{options.map((group) => (
|
||||||
<GenericDropdownMenuGroup
|
<GenericDropdownMenuGroup
|
||||||
key={toKey?.(group.key) ?? group.key}
|
key={toKey?.(group.key) ?? (group.key as Key)}
|
||||||
label={group.label}
|
label={group.label}
|
||||||
description={group.description}
|
description={group.description}
|
||||||
adornment={group.adornment}
|
adornment={group.adornment}
|
||||||
>
|
>
|
||||||
{group.options.map((option) => (
|
{group.options.map((option) => (
|
||||||
<GenericDropdownMenuOption
|
<GenericDropdownMenuOption
|
||||||
key={toKey?.(option.key) ?? option.key}
|
key={toKey?.(option.key) ?? (option.key as Key)}
|
||||||
label={option.label}
|
label={option.label}
|
||||||
description={option.description}
|
description={option.description}
|
||||||
onClick={(ev: ButtonEvent) => {
|
onClick={(ev: ButtonEvent) => {
|
||||||
|
@ -156,7 +162,7 @@ export function GenericDropdownMenu<T>({
|
||||||
<>
|
<>
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<GenericDropdownMenuOption
|
<GenericDropdownMenuOption
|
||||||
key={toKey?.(option.key) ?? option.key}
|
key={toKey?.(option.key) ?? (option.key as Key)}
|
||||||
label={option.label}
|
label={option.label}
|
||||||
description={option.description}
|
description={option.description}
|
||||||
onClick={(ev: ButtonEvent) => {
|
onClick={(ev: ButtonEvent) => {
|
||||||
|
|
|
@ -22,7 +22,7 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class GenericErrorPage extends React.PureComponent<IProps> {
|
export default class GenericErrorPage extends React.PureComponent<IProps> {
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<div className="mx_GenericErrorPage">
|
<div className="mx_GenericErrorPage">
|
||||||
<div className="mx_GenericErrorPage_box">
|
<div className="mx_GenericErrorPage_box">
|
||||||
|
|
|
@ -177,7 +177,7 @@ export default class IndicatorScrollbar<T extends keyof JSX.IntrinsicElements> e
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { children, trackHorizontalOverflow, verticalScrollsHorizontally, ...otherProps } = this.props;
|
const { children, trackHorizontalOverflow, verticalScrollsHorizontally, ...otherProps } = this.props;
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ interface IProps {
|
||||||
// Called when the stage changes, or the stage's phase changes. First
|
// Called when the stage changes, or the stage's phase changes. First
|
||||||
// argument is the stage, second is the phase. Some stages do not have
|
// argument is the stage, second is the phase. Some stages do not have
|
||||||
// phases and will be counted as 0 (numeric).
|
// phases and will be counted as 0 (numeric).
|
||||||
onStagePhaseChange?(stage: string, phase: string | number): void;
|
onStagePhaseChange?(stage: AuthType, phase: number): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -99,7 +99,7 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
|
||||||
|
|
||||||
private unmounted = false;
|
private unmounted = false;
|
||||||
|
|
||||||
public constructor(props) {
|
public constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -249,7 +249,7 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
|
||||||
this.authLogic.setEmailSid(sid);
|
this.authLogic.setEmailSid(sid);
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const stage = this.state.authStage;
|
const stage = this.state.authStage;
|
||||||
if (!stage) {
|
if (!stage) {
|
||||||
if (this.state.busy) {
|
if (this.state.busy) {
|
||||||
|
|
|
@ -68,7 +68,7 @@ interface IState {
|
||||||
export default class LeftPanel extends React.Component<IProps, IState> {
|
export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
private listContainerRef = createRef<HTMLDivElement>();
|
private listContainerRef = createRef<HTMLDivElement>();
|
||||||
private roomListRef = createRef<RoomList>();
|
private roomListRef = createRef<RoomList>();
|
||||||
private focusedElement = null;
|
private focusedElement: Element = null;
|
||||||
private isDoingStickyHeaders = false;
|
private isDoingStickyHeaders = false;
|
||||||
|
|
||||||
public constructor(props: IProps) {
|
public constructor(props: IProps) {
|
||||||
|
|
|
@ -136,8 +136,8 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
protected backgroundImageWatcherRef: string;
|
protected backgroundImageWatcherRef: string;
|
||||||
protected resizer: Resizer;
|
protected resizer: Resizer;
|
||||||
|
|
||||||
public constructor(props, context) {
|
public constructor(props: IProps) {
|
||||||
super(props, context);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
syncErrorData: undefined,
|
syncErrorData: undefined,
|
||||||
|
@ -229,8 +229,8 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private createResizer(): Resizer {
|
private createResizer(): Resizer {
|
||||||
let panelSize;
|
let panelSize: number;
|
||||||
let panelCollapsed;
|
let panelCollapsed: boolean;
|
||||||
const collapseConfig: ICollapseConfig = {
|
const collapseConfig: ICollapseConfig = {
|
||||||
// TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel
|
// TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel
|
||||||
toggleSize: 206 - 50,
|
toggleSize: 206 - 50,
|
||||||
|
@ -341,7 +341,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
|
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
|
||||||
if (!serverNoticeList) return;
|
if (!serverNoticeList) return;
|
||||||
|
|
||||||
const events = [];
|
const events: MatrixEvent[] = [];
|
||||||
let pinnedEventTs = 0;
|
let pinnedEventTs = 0;
|
||||||
for (const room of serverNoticeList) {
|
for (const room of serverNoticeList) {
|
||||||
const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", "");
|
const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", "");
|
||||||
|
@ -369,7 +369,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
e.getContent()["server_notice_type"] === "m.server_notice.usage_limit_reached"
|
e.getContent()["server_notice_type"] === "m.server_notice.usage_limit_reached"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
const usageLimitEventContent = usageLimitEvent && usageLimitEvent.getContent();
|
const usageLimitEventContent = usageLimitEvent?.getContent<IUsageLimit>();
|
||||||
this.calculateServerLimitToast(this.state.syncErrorData, usageLimitEventContent);
|
this.calculateServerLimitToast(this.state.syncErrorData, usageLimitEventContent);
|
||||||
this.setState({
|
this.setState({
|
||||||
usageLimitEventContent,
|
usageLimitEventContent,
|
||||||
|
@ -422,13 +422,13 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
We also listen with a native listener on the document to get keydown events when no element is focused.
|
We also listen with a native listener on the document to get keydown events when no element is focused.
|
||||||
Bubbling is irrelevant here as the target is the body element.
|
Bubbling is irrelevant here as the target is the body element.
|
||||||
*/
|
*/
|
||||||
private onReactKeyDown = (ev): void => {
|
private onReactKeyDown = (ev: React.KeyboardEvent): void => {
|
||||||
// events caught while bubbling up on the root element
|
// events caught while bubbling up on the root element
|
||||||
// of this component, so something must be focused.
|
// of this component, so something must be focused.
|
||||||
this.onKeyDown(ev);
|
this.onKeyDown(ev);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onNativeKeyDown = (ev): void => {
|
private onNativeKeyDown = (ev: KeyboardEvent): void => {
|
||||||
// only pass this if there is no focused element.
|
// only pass this if there is no focused element.
|
||||||
// if there is, onKeyDown will be called by the
|
// if there is, onKeyDown will be called by the
|
||||||
// react keydown handler that respects the react bubbling order.
|
// react keydown handler that respects the react bubbling order.
|
||||||
|
@ -437,7 +437,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onKeyDown = (ev): void => {
|
private onKeyDown = (ev: React.KeyboardEvent | KeyboardEvent): void => {
|
||||||
let handled = false;
|
let handled = false;
|
||||||
|
|
||||||
const roomAction = getKeyBindingsManager().getRoomAction(ev);
|
const roomAction = getKeyBindingsManager().getRoomAction(ev);
|
||||||
|
@ -571,7 +571,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
) {
|
) {
|
||||||
dis.dispatch<SwitchSpacePayload>({
|
dis.dispatch<SwitchSpacePayload>({
|
||||||
action: Action.SwitchSpace,
|
action: Action.SwitchSpace,
|
||||||
num: ev.code.slice(5), // Cut off the first 5 characters - "Digit"
|
num: parseInt(ev.code.slice(5), 10), // Cut off the first 5 characters - "Digit"
|
||||||
});
|
});
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
|
@ -615,13 +615,11 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
* dispatch a page-up/page-down/etc to the appropriate component
|
* dispatch a page-up/page-down/etc to the appropriate component
|
||||||
* @param {Object} ev The key event
|
* @param {Object} ev The key event
|
||||||
*/
|
*/
|
||||||
private onScrollKeyPressed = (ev): void => {
|
private onScrollKeyPressed = (ev: React.KeyboardEvent | KeyboardEvent): void => {
|
||||||
if (this._roomView.current) {
|
this._roomView.current?.handleScrollKey(ev);
|
||||||
this._roomView.current.handleScrollKey(ev);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
let pageElement;
|
let pageElement;
|
||||||
|
|
||||||
switch (this.props.page_type) {
|
switch (this.props.page_type) {
|
||||||
|
|
|
@ -47,7 +47,7 @@ export default class MainSplit extends React.Component<IProps> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private loadSidePanelSize(): { height: string | number; width: number } {
|
private loadSidePanelSize(): { height: string | number; width: number } {
|
||||||
let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size"), 10);
|
let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size")!, 10);
|
||||||
|
|
||||||
if (isNaN(rhsSize)) {
|
if (isNaN(rhsSize)) {
|
||||||
rhsSize = 350;
|
rhsSize = 350;
|
||||||
|
@ -59,7 +59,7 @@ export default class MainSplit extends React.Component<IProps> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const bodyView = React.Children.only(this.props.children);
|
const bodyView = React.Children.only(this.props.children);
|
||||||
const panelView = this.props.panel;
|
const panelView = this.props.panel;
|
||||||
|
|
||||||
|
|
|
@ -419,7 +419,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
window.addEventListener("resize", this.onWindowResized);
|
window.addEventListener("resize", this.onWindowResized);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(prevProps, prevState): void {
|
public componentDidUpdate(prevProps: IProps, prevState: IState): void {
|
||||||
if (this.shouldTrackPageChange(prevState, this.state)) {
|
if (this.shouldTrackPageChange(prevState, this.state)) {
|
||||||
const durationMs = this.stopPageChangeTimer();
|
const durationMs = this.stopPageChangeTimer();
|
||||||
PosthogTrackers.instance.trackPageChange(this.state.view, this.state.page_type, durationMs);
|
PosthogTrackers.instance.trackPageChange(this.state.view, this.state.page_type, durationMs);
|
||||||
|
@ -544,12 +544,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
if (state.view === undefined) {
|
if (state.view === undefined) {
|
||||||
throw new Error("setStateForNewView with no view!");
|
throw new Error("setStateForNewView with no view!");
|
||||||
}
|
}
|
||||||
const newState = {
|
this.setState({
|
||||||
currentUserId: null,
|
currentUserId: undefined,
|
||||||
justRegistered: false,
|
justRegistered: false,
|
||||||
};
|
...state,
|
||||||
Object.assign(newState, state);
|
} as IState);
|
||||||
this.setState(newState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onAction = (payload: ActionPayload): void => {
|
private onAction = (payload: ActionPayload): void => {
|
||||||
|
@ -2022,7 +2021,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
return fragmentAfterLogin;
|
return fragmentAfterLogin;
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const fragmentAfterLogin = this.getFragmentAfterLogin();
|
const fragmentAfterLogin = this.getFragmentAfterLogin();
|
||||||
let view = null;
|
let view = null;
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createRef, KeyboardEvent, ReactNode, TransitionEvent } from "react";
|
import React, { createRef, ReactNode, TransitionEvent } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||||
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
||||||
|
@ -34,7 +34,7 @@ import SettingsStore from "../../settings/SettingsStore";
|
||||||
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||||
import { Layout } from "../../settings/enums/Layout";
|
import { Layout } from "../../settings/enums/Layout";
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
import EventTile, { UnwrappedEventTile, GetRelationsForEvent, IReadReceiptProps } from "../views/rooms/EventTile";
|
import EventTile, { GetRelationsForEvent, IReadReceiptProps, UnwrappedEventTile } from "../views/rooms/EventTile";
|
||||||
import { hasText } from "../../TextForEvent";
|
import { hasText } from "../../TextForEvent";
|
||||||
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
|
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
|
||||||
import DMRoomMap from "../../utils/DMRoomMap";
|
import DMRoomMap from "../../utils/DMRoomMap";
|
||||||
|
@ -272,7 +272,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
// A map to allow groupers to maintain consistent keys even if their first event is uprooted due to back-pagination.
|
// A map to allow groupers to maintain consistent keys even if their first event is uprooted due to back-pagination.
|
||||||
public grouperKeyMap = new WeakMap<MatrixEvent, string>();
|
public grouperKeyMap = new WeakMap<MatrixEvent, string>();
|
||||||
|
|
||||||
public constructor(props, context) {
|
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -308,7 +308,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
SettingsStore.unwatchSetting(this.showTypingNotificationsWatcherRef);
|
SettingsStore.unwatchSetting(this.showTypingNotificationsWatcherRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(prevProps, prevState): void {
|
public componentDidUpdate(prevProps: IProps, prevState: IState): void {
|
||||||
if (prevProps.layout !== this.props.layout) {
|
if (prevProps.layout !== this.props.layout) {
|
||||||
this.calculateRoomMembersCount();
|
this.calculateRoomMembersCount();
|
||||||
}
|
}
|
||||||
|
@ -410,17 +410,13 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
/* jump to the top of the content.
|
/* jump to the top of the content.
|
||||||
*/
|
*/
|
||||||
public scrollToTop(): void {
|
public scrollToTop(): void {
|
||||||
if (this.scrollPanel.current) {
|
this.scrollPanel.current?.scrollToTop();
|
||||||
this.scrollPanel.current.scrollToTop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* jump to the bottom of the content.
|
/* jump to the bottom of the content.
|
||||||
*/
|
*/
|
||||||
public scrollToBottom(): void {
|
public scrollToBottom(): void {
|
||||||
if (this.scrollPanel.current) {
|
this.scrollPanel.current?.scrollToBottom();
|
||||||
this.scrollPanel.current.scrollToBottom();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -428,10 +424,8 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
*
|
*
|
||||||
* @param {KeyboardEvent} ev: the keyboard event to handle
|
* @param {KeyboardEvent} ev: the keyboard event to handle
|
||||||
*/
|
*/
|
||||||
public handleScrollKey(ev: KeyboardEvent): void {
|
public handleScrollKey(ev: React.KeyboardEvent | KeyboardEvent): void {
|
||||||
if (this.scrollPanel.current) {
|
this.scrollPanel.current?.handleScrollKey(ev);
|
||||||
this.scrollPanel.current.handleScrollKey(ev);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* jump to the given event id.
|
/* jump to the given event id.
|
||||||
|
@ -752,7 +746,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
const readReceipts = this.readReceiptsByEvent[eventId];
|
const readReceipts = this.readReceiptsByEvent[eventId];
|
||||||
|
|
||||||
let isLastSuccessful = false;
|
let isLastSuccessful = false;
|
||||||
const isSentState = (s): boolean => !s || s === "sent";
|
const isSentState = (s: EventStatus): boolean => !s || s === EventStatus.SENT;
|
||||||
const isSent = isSentState(mxEv.getAssociatedStatus());
|
const isSent = isSentState(mxEv.getAssociatedStatus());
|
||||||
const hasNextEvent = nextEvent && this.shouldShowEvent(nextEvent);
|
const hasNextEvent = nextEvent && this.shouldShowEvent(nextEvent);
|
||||||
if (!hasNextEvent && isSent) {
|
if (!hasNextEvent && isSent) {
|
||||||
|
@ -869,8 +863,14 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
// should be shown next to that event. If a hidden event has read receipts,
|
// should be shown next to that event. If a hidden event has read receipts,
|
||||||
// they are folded into the receipts of the last shown event.
|
// they are folded into the receipts of the last shown event.
|
||||||
private getReadReceiptsByShownEvent(): Record<string, IReadReceiptProps[]> {
|
private getReadReceiptsByShownEvent(): Record<string, IReadReceiptProps[]> {
|
||||||
const receiptsByEvent = {};
|
const receiptsByEvent: Record<string, IReadReceiptProps[]> = {};
|
||||||
const receiptsByUserId = {};
|
const receiptsByUserId: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
lastShownEventId: string;
|
||||||
|
receipt: IReadReceiptProps;
|
||||||
|
}
|
||||||
|
> = {};
|
||||||
|
|
||||||
let lastShownEventId;
|
let lastShownEventId;
|
||||||
for (const event of this.props.events) {
|
for (const event of this.props.events) {
|
||||||
|
@ -982,7 +982,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
let topSpinner;
|
let topSpinner;
|
||||||
let bottomSpinner;
|
let bottomSpinner;
|
||||||
if (this.props.backPaginating) {
|
if (this.props.backPaginating) {
|
||||||
|
|
|
@ -27,8 +27,8 @@ interface IState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class NonUrgentToastContainer extends React.PureComponent<IProps, IState> {
|
export default class NonUrgentToastContainer extends React.PureComponent<IProps, IState> {
|
||||||
public constructor(props, context) {
|
public constructor(props: IProps) {
|
||||||
super(props, context);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
toasts: NonUrgentToastStore.instance.components,
|
toasts: NonUrgentToastStore.instance.components,
|
||||||
|
@ -45,7 +45,7 @@ export default class NonUrgentToastContainer extends React.PureComponent<IProps,
|
||||||
this.setState({ toasts: NonUrgentToastStore.instance.components });
|
this.setState({ toasts: NonUrgentToastStore.instance.components });
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const toasts = this.state.toasts.map((t, i) => {
|
const toasts = this.state.toasts.map((t, i) => {
|
||||||
return (
|
return (
|
||||||
<div className="mx_NonUrgentToastContainer_toast" key={`toast-${i}`}>
|
<div className="mx_NonUrgentToastContainer_toast" key={`toast-${i}`}>
|
||||||
|
|
|
@ -43,7 +43,7 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
|
||||||
|
|
||||||
private card = React.createRef<HTMLDivElement>();
|
private card = React.createRef<HTMLDivElement>();
|
||||||
|
|
||||||
public constructor(props) {
|
public constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -55,7 +55,7 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
|
||||||
this.setState({ narrow });
|
this.setState({ narrow });
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const emptyState = (
|
const emptyState = (
|
||||||
<div className="mx_RightPanel_empty mx_NotificationPanel_empty">
|
<div className="mx_RightPanel_empty mx_NotificationPanel_empty">
|
||||||
<h2>{_t("You're all caught up")}</h2>
|
<h2>{_t("You're all caught up")}</h2>
|
||||||
|
|
|
@ -245,7 +245,7 @@ export default class PictureInPictureDragger extends React.Component<IProps> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const style = {
|
const style = {
|
||||||
transform: `translateX(${this.translationX}px) translateY(${this.translationY}px)`,
|
transform: `translateX(${this.translationX}px) translateY(${this.translationY}px)`,
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { MutableRefObject, useContext, useRef } from "react";
|
import React, { MutableRefObject, ReactNode, useContext, useRef } from "react";
|
||||||
import { CallEvent, CallState, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
import { CallEvent, CallState, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { Optional } from "matrix-events-sdk";
|
import { Optional } from "matrix-events-sdk";
|
||||||
|
@ -288,7 +288,7 @@ class PipContainerInner extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): ReactNode {
|
||||||
const pipMode = true;
|
const pipMode = true;
|
||||||
let pipContent: Array<CreatePipChildren> = [];
|
let pipContent: Array<CreatePipChildren> = [];
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
||||||
public static contextType = MatrixClientContext;
|
public static contextType = MatrixClientContext;
|
||||||
public context!: React.ContextType<typeof MatrixClientContext>;
|
public context!: React.ContextType<typeof MatrixClientContext>;
|
||||||
|
|
||||||
public constructor(props, context) {
|
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -149,7 +149,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
||||||
this.setState({ searchQuery });
|
this.setState({ searchQuery });
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
let card = <div />;
|
let card = <div />;
|
||||||
const roomId = this.props.room?.roomId;
|
const roomId = this.props.room?.roomId;
|
||||||
const phase = this.props.overwriteCard?.phase ?? this.state.phase;
|
const phase = this.props.overwriteCard?.phase ?? this.state.phase;
|
||||||
|
|
|
@ -111,11 +111,11 @@ export const RoomSearchView = forwardRef<ScrollPanel, Props>(
|
||||||
);
|
);
|
||||||
if (!bundledRelationship || event.getThread()) continue;
|
if (!bundledRelationship || event.getThread()) continue;
|
||||||
const room = client.getRoom(event.getRoomId());
|
const room = client.getRoom(event.getRoomId());
|
||||||
const thread = room.findThreadForEvent(event);
|
const thread = room?.findThreadForEvent(event);
|
||||||
if (thread) {
|
if (thread) {
|
||||||
event.setThread(thread);
|
event.setThread(thread);
|
||||||
} else {
|
} else {
|
||||||
room.createThread(event.getId(), event, [], true);
|
room?.createThread(event.getId()!, event, [], true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,7 +214,7 @@ export const RoomSearchView = forwardRef<ScrollPanel, Props>(
|
||||||
scrollPanel?.checkScroll();
|
scrollPanel?.checkScroll();
|
||||||
};
|
};
|
||||||
|
|
||||||
let lastRoomId: string;
|
let lastRoomId: string | undefined;
|
||||||
let mergedTimeline: MatrixEvent[] = [];
|
let mergedTimeline: MatrixEvent[] = [];
|
||||||
let ourEventsIndexes: number[] = [];
|
let ourEventsIndexes: number[] = [];
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,11 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ReactNode } from "react";
|
||||||
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { SyncState, ISyncStateData } from "matrix-js-sdk/src/sync";
|
import { SyncState, ISyncStateData } from "matrix-js-sdk/src/sync";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import { MatrixError } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { _t, _td } from "../../languageHandler";
|
import { _t, _td } from "../../languageHandler";
|
||||||
import Resend from "../../Resend";
|
import Resend from "../../Resend";
|
||||||
|
@ -192,10 +193,10 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
|
||||||
private getUnsentMessageContent(): JSX.Element {
|
private getUnsentMessageContent(): JSX.Element {
|
||||||
const unsentMessages = this.state.unsentMessages;
|
const unsentMessages = this.state.unsentMessages;
|
||||||
|
|
||||||
let title;
|
let title: ReactNode;
|
||||||
|
|
||||||
let consentError = null;
|
let consentError: MatrixError | null = null;
|
||||||
let resourceLimitError = null;
|
let resourceLimitError: MatrixError | null = null;
|
||||||
for (const m of unsentMessages) {
|
for (const m of unsentMessages) {
|
||||||
if (m.error && m.error.errcode === "M_CONSENT_NOT_GIVEN") {
|
if (m.error && m.error.errcode === "M_CONSENT_NOT_GIVEN") {
|
||||||
consentError = m.error;
|
consentError = m.error;
|
||||||
|
@ -212,7 +213,7 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
consentLink: (sub) => (
|
consentLink: (sub) => (
|
||||||
<a href={consentError.data && consentError.data.consent_uri} target="_blank">
|
<a href={consentError!.data?.consent_uri} target="_blank">
|
||||||
{sub}
|
{sub}
|
||||||
</a>
|
</a>
|
||||||
),
|
),
|
||||||
|
@ -271,7 +272,7 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
if (this.shouldShowConnectionError()) {
|
if (this.shouldShowConnectionError()) {
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomStatusBar">
|
<div className="mx_RoomStatusBar">
|
||||||
|
|
|
@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ReactElement } from "react";
|
import React, { ReactElement, ReactNode } from "react";
|
||||||
|
|
||||||
import { StaticNotificationState } from "../../stores/notifications/StaticNotificationState";
|
import { StaticNotificationState } from "../../stores/notifications/StaticNotificationState";
|
||||||
import NotificationBadge from "../views/rooms/NotificationBadge";
|
import NotificationBadge from "../views/rooms/NotificationBadge";
|
||||||
|
|
||||||
interface RoomStatusBarUnsentMessagesProps {
|
interface RoomStatusBarUnsentMessagesProps {
|
||||||
title: string;
|
title: ReactNode;
|
||||||
description?: string;
|
description?: string;
|
||||||
notificationState: StaticNotificationState;
|
notificationState: StaticNotificationState;
|
||||||
buttons: ReactElement;
|
buttons: ReactElement;
|
||||||
|
|
|
@ -33,6 +33,7 @@ import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
||||||
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
|
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
|
||||||
import { HistoryVisibility } from "matrix-js-sdk/src/@types/partials";
|
import { HistoryVisibility } from "matrix-js-sdk/src/@types/partials";
|
||||||
import { ISearchResults } from "matrix-js-sdk/src/@types/search";
|
import { ISearchResults } from "matrix-js-sdk/src/@types/search";
|
||||||
|
import { IRoomTimelineData } from "matrix-js-sdk/src/models/event-timeline-set";
|
||||||
|
|
||||||
import shouldHideEvent from "../../shouldHideEvent";
|
import shouldHideEvent from "../../shouldHideEvent";
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
|
@ -49,7 +50,7 @@ import RoomScrollStateStore, { ScrollState } from "../../stores/RoomScrollStateS
|
||||||
import WidgetEchoStore from "../../stores/WidgetEchoStore";
|
import WidgetEchoStore from "../../stores/WidgetEchoStore";
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
import { Layout } from "../../settings/enums/Layout";
|
import { Layout } from "../../settings/enums/Layout";
|
||||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
|
||||||
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||||
import { E2EStatus, shieldStatusForRoom } from "../../utils/ShieldUtils";
|
import { E2EStatus, shieldStatusForRoom } from "../../utils/ShieldUtils";
|
||||||
import { Action } from "../../dispatcher/actions";
|
import { Action } from "../../dispatcher/actions";
|
||||||
|
@ -223,6 +224,7 @@ export interface IRoomState {
|
||||||
narrow: boolean;
|
narrow: boolean;
|
||||||
// List of undecryptable events currently visible on-screen
|
// List of undecryptable events currently visible on-screen
|
||||||
visibleDecryptionFailures?: MatrixEvent[];
|
visibleDecryptionFailures?: MatrixEvent[];
|
||||||
|
msc3946ProcessDynamicPredecessor: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LocalRoomViewProps {
|
interface LocalRoomViewProps {
|
||||||
|
@ -416,6 +418,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
liveTimeline: undefined,
|
liveTimeline: undefined,
|
||||||
narrow: false,
|
narrow: false,
|
||||||
visibleDecryptionFailures: [],
|
visibleDecryptionFailures: [],
|
||||||
|
msc3946ProcessDynamicPredecessor: SettingsStore.getValue("feature_dynamic_room_predecessors"),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
|
@ -467,6 +470,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
),
|
),
|
||||||
SettingsStore.watchSetting("urlPreviewsEnabled", null, this.onUrlPreviewsEnabledChange),
|
SettingsStore.watchSetting("urlPreviewsEnabled", null, this.onUrlPreviewsEnabledChange),
|
||||||
SettingsStore.watchSetting("urlPreviewsEnabled_e2ee", null, this.onUrlPreviewsEnabledChange),
|
SettingsStore.watchSetting("urlPreviewsEnabled_e2ee", null, this.onUrlPreviewsEnabledChange),
|
||||||
|
SettingsStore.watchSetting("feature_dynamic_room_predecessors", null, (...[, , , value]) =>
|
||||||
|
this.setState({ msc3946ProcessDynamicPredecessor: value as boolean }),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -851,7 +857,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
window.addEventListener("beforeunload", this.onPageUnload);
|
window.addEventListener("beforeunload", this.onPageUnload);
|
||||||
}
|
}
|
||||||
|
|
||||||
public shouldComponentUpdate(nextProps, nextState): boolean {
|
public shouldComponentUpdate(nextProps: IRoomProps, nextState: IRoomState): boolean {
|
||||||
const hasPropsDiff = objectHasDiff(this.props, nextProps);
|
const hasPropsDiff = objectHasDiff(this.props, nextProps);
|
||||||
|
|
||||||
const { upgradeRecommendation, ...state } = this.state;
|
const { upgradeRecommendation, ...state } = this.state;
|
||||||
|
@ -953,7 +959,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onPageUnload = (event): string => {
|
private onPageUnload = (event: BeforeUnloadEvent): string => {
|
||||||
if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) {
|
if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) {
|
||||||
return (event.returnValue = _t("You seem to be uploading files, are you sure you want to quit?"));
|
return (event.returnValue = _t("You seem to be uploading files, are you sure you want to quit?"));
|
||||||
} else if (this.getCallForRoom() && this.state.callState !== "ended") {
|
} else if (this.getCallForRoom() && this.state.callState !== "ended") {
|
||||||
|
@ -961,7 +967,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onReactKeyDown = (ev): void => {
|
private onReactKeyDown = (ev: React.KeyboardEvent): void => {
|
||||||
let handled = false;
|
let handled = false;
|
||||||
|
|
||||||
const action = getKeyBindingsManager().getRoomAction(ev);
|
const action = getKeyBindingsManager().getRoomAction(ev);
|
||||||
|
@ -1125,7 +1131,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
createRoomFromLocalRoom(this.context.client, this.state.room as LocalRoom);
|
createRoomFromLocalRoom(this.context.client, this.state.room as LocalRoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onRoomTimeline = (ev: MatrixEvent, room: Room | null, toStartOfTimeline: boolean, removed, data): void => {
|
private onRoomTimeline = (
|
||||||
|
ev: MatrixEvent,
|
||||||
|
room: Room | null,
|
||||||
|
toStartOfTimeline: boolean,
|
||||||
|
removed: boolean,
|
||||||
|
data?: IRoomTimelineData,
|
||||||
|
): void => {
|
||||||
if (this.unmounted) return;
|
if (this.unmounted) return;
|
||||||
|
|
||||||
// ignore events for other rooms or the notification timeline set
|
// ignore events for other rooms or the notification timeline set
|
||||||
|
@ -1145,7 +1157,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
|
|
||||||
// ignore anything but real-time updates at the end of the room:
|
// ignore anything but real-time updates at the end of the room:
|
||||||
// updates from pagination will happen when the paginate completes.
|
// updates from pagination will happen when the paginate completes.
|
||||||
if (toStartOfTimeline || !data || !data.liveEvent) return;
|
if (toStartOfTimeline || !data?.liveEvent) return;
|
||||||
|
|
||||||
// no point handling anything while we're waiting for the join to finish:
|
// no point handling anything while we're waiting for the join to finish:
|
||||||
// we'll only be showing a spinner.
|
// we'll only be showing a spinner.
|
||||||
|
@ -1697,7 +1709,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// update the read marker to match the read-receipt
|
// update the read marker to match the read-receipt
|
||||||
private forgetReadMarker = (ev): void => {
|
private forgetReadMarker = (ev: ButtonEvent): void => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.messagePanel.forgetReadMarker();
|
this.messagePanel.forgetReadMarker();
|
||||||
};
|
};
|
||||||
|
@ -1770,7 +1782,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
*
|
*
|
||||||
* We pass it down to the scroll panel.
|
* We pass it down to the scroll panel.
|
||||||
*/
|
*/
|
||||||
public handleScrollKey = (ev): void => {
|
public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => {
|
||||||
let panel: ScrollPanel | TimelinePanel;
|
let panel: ScrollPanel | TimelinePanel;
|
||||||
if (this.searchResultsPanel.current) {
|
if (this.searchResultsPanel.current) {
|
||||||
panel = this.searchResultsPanel.current;
|
panel = this.searchResultsPanel.current;
|
||||||
|
@ -1793,15 +1805,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
|
|
||||||
// this has to be a proper method rather than an unnamed function,
|
// this has to be a proper method rather than an unnamed function,
|
||||||
// otherwise react calls it with null on each update.
|
// otherwise react calls it with null on each update.
|
||||||
private gatherTimelinePanelRef = (r): void => {
|
private gatherTimelinePanelRef = (r?: TimelinePanel): void => {
|
||||||
this.messagePanel = r;
|
this.messagePanel = r;
|
||||||
};
|
};
|
||||||
|
|
||||||
private getOldRoom(): Room | null {
|
private getOldRoom(): Room | null {
|
||||||
const createEvent = this.state.room.currentState.getStateEvents(EventType.RoomCreate, "");
|
const { roomId } = this.state.room?.findPredecessor(this.state.msc3946ProcessDynamicPredecessor) || {};
|
||||||
if (!createEvent || !createEvent.getContent()["predecessor"]) return null;
|
return this.context.client?.getRoom(roomId) || null;
|
||||||
|
|
||||||
return this.context.client.getRoom(createEvent.getContent()["predecessor"]["room_id"]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getHiddenHighlightCount(): number {
|
public getHiddenHighlightCount(): number {
|
||||||
|
@ -1869,7 +1879,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
if (this.state.room instanceof LocalRoom) {
|
if (this.state.room instanceof LocalRoom) {
|
||||||
if (this.state.room.state === LocalRoomState.CREATING) {
|
if (this.state.room.state === LocalRoomState.CREATING) {
|
||||||
return this.renderLocalRoomCreateLoader();
|
return this.renderLocalRoomCreateLoader();
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createRef, CSSProperties, ReactNode, KeyboardEvent } from "react";
|
import React, { createRef, CSSProperties, ReactNode } from "react";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
|
@ -195,8 +195,8 @@ export default class ScrollPanel extends React.Component<IProps> {
|
||||||
private heightUpdateInProgress: boolean;
|
private heightUpdateInProgress: boolean;
|
||||||
private divScroll: HTMLDivElement;
|
private divScroll: HTMLDivElement;
|
||||||
|
|
||||||
public constructor(props, context) {
|
public constructor(props: IProps) {
|
||||||
super(props, context);
|
super(props);
|
||||||
|
|
||||||
this.props.resizeNotifier?.on("middlePanelResizedNoisy", this.onResize);
|
this.props.resizeNotifier?.on("middlePanelResizedNoisy", this.onResize);
|
||||||
|
|
||||||
|
@ -440,9 +440,9 @@ export default class ScrollPanel extends React.Component<IProps> {
|
||||||
// pagination.
|
// pagination.
|
||||||
//
|
//
|
||||||
// If backwards is true, we unpaginate (remove) tiles from the back (top).
|
// If backwards is true, we unpaginate (remove) tiles from the back (top).
|
||||||
let tile;
|
let tile: HTMLElement;
|
||||||
for (let i = 0; i < tiles.length; i++) {
|
for (let i = 0; i < tiles.length; i++) {
|
||||||
tile = tiles[backwards ? i : tiles.length - 1 - i];
|
tile = tiles[backwards ? i : tiles.length - 1 - i] as HTMLElement;
|
||||||
// Subtract height of tile as if it were unpaginated
|
// Subtract height of tile as if it were unpaginated
|
||||||
excessHeight -= tile.clientHeight;
|
excessHeight -= tile.clientHeight;
|
||||||
//If removing the tile would lead to future pagination, break before setting scroll token
|
//If removing the tile would lead to future pagination, break before setting scroll token
|
||||||
|
@ -587,7 +587,7 @@ export default class ScrollPanel extends React.Component<IProps> {
|
||||||
* Scroll up/down in response to a scroll key
|
* Scroll up/down in response to a scroll key
|
||||||
* @param {object} ev the keyboard event
|
* @param {object} ev the keyboard event
|
||||||
*/
|
*/
|
||||||
public handleScrollKey = (ev: KeyboardEvent): void => {
|
public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => {
|
||||||
const roomAction = getKeyBindingsManager().getRoomAction(ev);
|
const roomAction = getKeyBindingsManager().getRoomAction(ev);
|
||||||
switch (roomAction) {
|
switch (roomAction) {
|
||||||
case KeyBindingAction.ScrollUp:
|
case KeyBindingAction.ScrollUp:
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||||
|
|
||||||
interface IProps extends HTMLProps<HTMLInputElement> {
|
interface IProps extends HTMLProps<HTMLInputElement> {
|
||||||
onSearch?: (query: string) => void;
|
onSearch: (query: string) => void;
|
||||||
onCleared?: (source?: string) => void;
|
onCleared?: (source?: string) => void;
|
||||||
onKeyDown?: (ev: React.KeyboardEvent) => void;
|
onKeyDown?: (ev: React.KeyboardEvent) => void;
|
||||||
onFocus?: (ev: React.FocusEvent) => void;
|
onFocus?: (ev: React.FocusEvent) => void;
|
||||||
|
@ -62,7 +62,7 @@ export default class SearchBox extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onSearch = throttle(
|
private onSearch = throttle(
|
||||||
(): void => {
|
(): void => {
|
||||||
this.props.onSearch(this.search.current.value);
|
this.props.onSearch(this.search.current?.value);
|
||||||
},
|
},
|
||||||
200,
|
200,
|
||||||
{ trailing: true, leading: true },
|
{ trailing: true, leading: true },
|
||||||
|
@ -101,7 +101,7 @@ export default class SearchBox extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
|
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
|
||||||
const {
|
const {
|
||||||
onSearch,
|
onSearch,
|
||||||
|
|
|
@ -280,7 +280,7 @@ const Tile: React.FC<ITileProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
if (showChildren) {
|
if (showChildren) {
|
||||||
const onChildrenKeyDown = (e): void => {
|
const onChildrenKeyDown = (e: React.KeyboardEvent): void => {
|
||||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case KeyBindingAction.ArrowLeft:
|
case KeyBindingAction.ArrowLeft:
|
||||||
|
|
|
@ -318,7 +318,7 @@ const SpaceSetupFirstRooms: React.FC<{
|
||||||
label={_t("Room name")}
|
label={_t("Room name")}
|
||||||
placeholder={placeholders[i]}
|
placeholder={placeholders[i]}
|
||||||
value={roomNames[i]}
|
value={roomNames[i]}
|
||||||
onChange={(ev) => setRoomName(i, ev.target.value)}
|
onChange={(ev: React.ChangeEvent<HTMLInputElement>) => setRoomName(i, ev.target.value)}
|
||||||
autoFocus={i === 2}
|
autoFocus={i === 2}
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
@ -814,7 +814,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const rightPanel =
|
const rightPanel =
|
||||||
this.state.showRightPanel && this.state.phase === Phase.Landing ? (
|
this.state.showRightPanel && this.state.phase === Phase.Landing ? (
|
||||||
<RightPanel room={this.props.space} resizeNotifier={this.props.resizeNotifier} />
|
<RightPanel room={this.props.space} resizeNotifier={this.props.resizeNotifier} />
|
||||||
|
|
|
@ -137,7 +137,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(prevProps): void {
|
public componentDidUpdate(prevProps: IProps): void {
|
||||||
if (prevProps.mxEvent !== this.props.mxEvent) {
|
if (prevProps.mxEvent !== this.props.mxEvent) {
|
||||||
this.setupThread(this.props.mxEvent);
|
this.setupThread(this.props.mxEvent);
|
||||||
}
|
}
|
||||||
|
@ -316,7 +316,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private get threadRelation(): IEventRelation {
|
private get threadRelation(): IEventRelation {
|
||||||
const relation = {
|
const relation: IEventRelation = {
|
||||||
rel_type: THREAD_RELATION_TYPE.name,
|
rel_type: THREAD_RELATION_TYPE.name,
|
||||||
event_id: this.state.thread?.id,
|
event_id: this.state.thread?.id,
|
||||||
is_falling_back: true,
|
is_falling_back: true,
|
||||||
|
@ -343,7 +343,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const highlightedEventId = this.props.isInitialEventHighlighted ? this.props.initialEvent?.getId() : null;
|
const highlightedEventId = this.props.isInitialEventHighlighted ? this.props.initialEvent?.getId() : null;
|
||||||
|
|
||||||
const threadRelation = this.threadRelation;
|
const threadRelation = this.threadRelation;
|
||||||
|
|
|
@ -1316,7 +1316,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
*
|
*
|
||||||
* We pass it down to the scroll panel.
|
* We pass it down to the scroll panel.
|
||||||
*/
|
*/
|
||||||
public handleScrollKey = (ev: React.KeyboardEvent): void => {
|
public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => {
|
||||||
if (!this.messagePanel.current) return;
|
if (!this.messagePanel.current) return;
|
||||||
|
|
||||||
// jump to the live timeline on ctrl-end, rather than the end of the
|
// jump to the live timeline on ctrl-end, rather than the end of the
|
||||||
|
@ -1886,7 +1886,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
this.callEventGroupers = buildLegacyCallEventGroupers(this.callEventGroupers, events);
|
this.callEventGroupers = buildLegacyCallEventGroupers(this.callEventGroupers, events);
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
// just show a spinner while the timeline loads.
|
// just show a spinner while the timeline loads.
|
||||||
//
|
//
|
||||||
// put it in a div of the right class (mx_RoomView_messagePanel) so
|
// put it in a div of the right class (mx_RoomView_messagePanel) so
|
||||||
|
@ -1977,9 +1977,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
*
|
*
|
||||||
* @return An event ID list for every timeline in every timelineSet
|
* @return An event ID list for every timeline in every timelineSet
|
||||||
*/
|
*/
|
||||||
function serializeEventIdsFromTimelineSets(timelineSets): { [key: string]: string[] }[] {
|
function serializeEventIdsFromTimelineSets(timelineSets: EventTimelineSet[]): { [key: string]: string[] }[] {
|
||||||
const serializedEventIdsInTimelineSet = timelineSets.map((timelineSet) => {
|
const serializedEventIdsInTimelineSet = timelineSets.map((timelineSet) => {
|
||||||
const timelineMap = {};
|
const timelineMap: Record<string, string[]> = {};
|
||||||
|
|
||||||
const timelines = timelineSet.getTimelines();
|
const timelines = timelineSet.getTimelines();
|
||||||
const liveTimeline = timelineSet.getLiveTimeline();
|
const liveTimeline = timelineSet.getLiveTimeline();
|
||||||
|
|
|
@ -25,8 +25,8 @@ interface IState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ToastContainer extends React.Component<{}, IState> {
|
export default class ToastContainer extends React.Component<{}, IState> {
|
||||||
public constructor(props, context) {
|
public constructor(props: {}) {
|
||||||
super(props, context);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
toasts: ToastStore.sharedInstance().getToasts(),
|
toasts: ToastStore.sharedInstance().getToasts(),
|
||||||
countSeen: ToastStore.sharedInstance().getCountSeen(),
|
countSeen: ToastStore.sharedInstance().getCountSeen(),
|
||||||
|
@ -50,7 +50,7 @@ export default class ToastContainer extends React.Component<{}, IState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const totalCount = this.state.toasts.length;
|
const totalCount = this.state.toasts.length;
|
||||||
const isStacked = totalCount > 1;
|
const isStacked = totalCount > 1;
|
||||||
let toast;
|
let toast;
|
||||||
|
|
|
@ -57,7 +57,7 @@ export default class UploadBar extends React.PureComponent<IProps, IState> {
|
||||||
private dispatcherRef: Optional<string>;
|
private dispatcherRef: Optional<string>;
|
||||||
private mounted = false;
|
private mounted = false;
|
||||||
|
|
||||||
public constructor(props) {
|
public constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
// Set initial state to any available upload in this room - we might be mounting
|
// Set initial state to any available upload in this room - we might be mounting
|
||||||
|
@ -103,7 +103,7 @@ export default class UploadBar extends React.PureComponent<IProps, IState> {
|
||||||
ContentMessages.sharedInstance().cancelUpload(this.state.currentUpload!);
|
ContentMessages.sharedInstance().cancelUpload(this.state.currentUpload!);
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
if (!this.state.currentFile) {
|
if (!this.state.currentFile) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -429,7 +429,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const avatarSize = 32; // should match border-radius of the avatar
|
const avatarSize = 32; // should match border-radius of the avatar
|
||||||
|
|
||||||
const userId = MatrixClientPeg.get().getUserId();
|
const userId = MatrixClientPeg.get().getUserId();
|
||||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
import Modal from "../../Modal";
|
import Modal from "../../Modal";
|
||||||
|
@ -31,7 +32,7 @@ import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases
|
||||||
import { UserOnboardingPage } from "../views/user-onboarding/UserOnboardingPage";
|
import { UserOnboardingPage } from "../views/user-onboarding/UserOnboardingPage";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
userId?: string;
|
userId: string;
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier: ResizeNotifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +67,7 @@ export default class UserView extends React.Component<IProps, IState> {
|
||||||
private async loadProfileInfo(): Promise<void> {
|
private async loadProfileInfo(): Promise<void> {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
let profileInfo;
|
let profileInfo: Awaited<ReturnType<MatrixClient["getProfileInfo"]>>;
|
||||||
try {
|
try {
|
||||||
profileInfo = await cli.getProfileInfo(this.props.userId);
|
profileInfo = await cli.getProfileInfo(this.props.userId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -83,7 +84,7 @@ export default class UserView extends React.Component<IProps, IState> {
|
||||||
this.setState({ member, loading: false });
|
this.setState({ member, loading: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
if (this.state.loading) {
|
if (this.state.loading) {
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
} else if (this.state.member) {
|
} else if (this.state.member) {
|
||||||
|
|
|
@ -142,7 +142,7 @@ export default class ViewSource extends React.Component<IProps, IState> {
|
||||||
return room.currentState.mayClientSendStateEvent(mxEvent.getType(), cli);
|
return room.currentState.mayClientSendStateEvent(mxEvent.getType(), cli);
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit
|
const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit
|
||||||
|
|
||||||
const isEditing = this.state.isEditing;
|
const isEditing = this.state.isEditing;
|
||||||
|
|
|
@ -57,7 +57,7 @@ export default class CompleteSecurity extends React.Component<IProps, IState> {
|
||||||
store.stop();
|
store.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
const { phase, lostKeys } = this.state;
|
const { phase, lostKeys } = this.state;
|
||||||
let icon;
|
let icon;
|
||||||
let title;
|
let title;
|
||||||
|
|
|
@ -27,7 +27,7 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class E2eSetup extends React.Component<IProps> {
|
export default class E2eSetup extends React.Component<IProps> {
|
||||||
public render(): JSX.Element {
|
public render(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<AuthPage>
|
<AuthPage>
|
||||||
<CompleteSecurityBody>
|
<CompleteSecurityBody>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue