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> => {
|
||||
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 => {
|
||||
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/fs-extra": "^11.0.0",
|
||||
"@types/geojson": "^7946.0.8",
|
||||
"@types/glob-to-regexp": "^0.4.1",
|
||||
"@types/jest": "^29.2.1",
|
||||
"@types/katex": "^0.14.0",
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@types/modernizr": "^3.5.3",
|
||||
"@types/node": "^16",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/pako": "^2.0.0",
|
||||
"@types/parse5": "^6.0.0",
|
||||
"@types/qrcode": "^1.3.5",
|
||||
|
@ -168,6 +170,7 @@
|
|||
"@types/react-test-renderer": "^17.0.1",
|
||||
"@types/react-transition-group": "^4.4.0",
|
||||
"@types/sanitize-html": "^2.3.1",
|
||||
"@types/tar-js": "^0.3.2",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@types/zxcvbn": "^4.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.35.1",
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
@import "./components/views/context_menus/_KebabContextMenu.pcss";
|
||||
@import "./components/views/dialogs/polls/_PollListItem.pcss";
|
||||
@import "./components/views/elements/_FilterDropdown.pcss";
|
||||
@import "./components/views/elements/_FilterTabGroup.pcss";
|
||||
@import "./components/views/elements/_LearnMore.pcss";
|
||||
@import "./components/views/location/_EnableLiveShare.pcss";
|
||||
@import "./components/views/location/_LiveDurationDropdown.pcss";
|
||||
|
@ -32,6 +33,7 @@
|
|||
@import "./components/views/messages/_MBeaconBody.pcss";
|
||||
@import "./components/views/messages/shared/_MediaProcessingError.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/_DeviceDetailHeading.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 */
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
|
||||
&:nth-child(2) {
|
||||
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");
|
||||
}
|
||||
|
||||
.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 {
|
||||
display: flex;
|
||||
flex-direction: inline;
|
||||
|
@ -168,9 +66,8 @@ limitations under the License.
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mx_MPollBody_option,
|
||||
/* label has cursor: default in user-agent stylesheet */
|
||||
/* override */
|
||||
.mx_MPollBody_live-option {
|
||||
cursor: pointer;
|
||||
.mx_MPollBody_allOptions {
|
||||
display: grid;
|
||||
grid-gap: $spacing-16;
|
||||
margin-bottom: $spacing-8;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
<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>
|
||||
|
|
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) & {
|
||||
parameterDescriptors?: AudioParamDescriptor[];
|
||||
},
|
||||
);
|
||||
): void;
|
||||
|
||||
// eslint-disable-next-line no-var
|
||||
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.
|
||||
*/
|
||||
|
||||
import { IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix";
|
||||
import { IAuthData, IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||
import Modal from "./Modal";
|
||||
|
@ -29,6 +29,12 @@ function getIdServerDomain(): string {
|
|||
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,
|
||||
* optionally, the identity servers.
|
||||
|
@ -178,7 +184,7 @@ export default class AddThreepid {
|
|||
* with a "message" property which contains a human-readable message detailing why
|
||||
* the request failed.
|
||||
*/
|
||||
public async checkEmailLinkClicked(): Promise<any[]> {
|
||||
public async checkEmailLinkClicked(): Promise<[boolean, IAuthData | Error | null]> {
|
||||
try {
|
||||
if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) {
|
||||
if (this.bind) {
|
||||
|
@ -220,7 +226,9 @@ export default class AddThreepid {
|
|||
continueKind: "primary",
|
||||
},
|
||||
};
|
||||
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
|
||||
const { finished } = Modal.createDialog<[boolean, IAuthData | Error | null]>(
|
||||
InteractiveAuthDialog,
|
||||
{
|
||||
title: _t("Add Email Address"),
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
authData: e.data,
|
||||
|
@ -229,7 +237,8 @@ export default class AddThreepid {
|
|||
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
return finished;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,10 +42,7 @@ interface IState {
|
|||
export default class AsyncWrapper extends React.Component<IProps, IState> {
|
||||
private unmounted = false;
|
||||
|
||||
public state = {
|
||||
component: null,
|
||||
error: null,
|
||||
};
|
||||
public state: IState = {};
|
||||
|
||||
public componentDidMount(): void {
|
||||
// XXX: temporary logging to try to diagnose
|
||||
|
@ -77,7 +74,7 @@ export default class AsyncWrapper extends React.Component<IProps, IState> {
|
|||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
if (this.state.component) {
|
||||
const Component = this.state.component;
|
||||
return <Component {...this.props} />;
|
||||
|
|
|
@ -138,7 +138,7 @@ export function getInitialLetter(name: string): string | undefined {
|
|||
}
|
||||
|
||||
export function avatarUrlForRoom(
|
||||
room: Room,
|
||||
room: Room | null,
|
||||
width: number,
|
||||
height: number,
|
||||
resizeMethod?: ResizeMethod,
|
||||
|
|
|
@ -197,7 +197,7 @@ export default abstract class BasePlatform {
|
|||
room: Room,
|
||||
ev?: MatrixEvent,
|
||||
): Notification {
|
||||
const notifBody = {
|
||||
const notifBody: NotificationOptions = {
|
||||
body: msg,
|
||||
silent: true, // we play our own sounds
|
||||
};
|
||||
|
|
|
@ -204,7 +204,7 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = {
|
|||
attribs.style += "height: 100%;";
|
||||
}
|
||||
|
||||
attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height);
|
||||
attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height)!;
|
||||
return { tagName, attribs };
|
||||
},
|
||||
"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
|
||||
// equivalents
|
||||
const customCSSMapper = {
|
||||
const customCSSMapper: Record<string, string> = {
|
||||
"data-mx-color": "color",
|
||||
"data-mx-bg-color": "background-color",
|
||||
// $customAttributeKey: $cssAttributeKey
|
||||
|
@ -352,7 +352,7 @@ const topicSanitizeHtmlParams: IExtendedSanitizeOptions = {
|
|||
};
|
||||
|
||||
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
|
||||
|
@ -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: IOptsReturnNode): ReactNode;
|
||||
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 isHtmlMessage = false;
|
||||
|
||||
|
@ -514,7 +514,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
|
|||
}
|
||||
|
||||
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 {
|
||||
// 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);
|
||||
strippedBody = opts.stripReplyFallback ? stripPlainReply(plainBody) : plainBody;
|
||||
bodyHasEmoji = mightContainEmoji(isFormattedBody ? formattedBody : plainBody);
|
||||
bodyHasEmoji = mightContainEmoji(isFormattedBody ? formattedBody! : plainBody);
|
||||
|
||||
const highlighter = safeHighlights?.length
|
||||
? 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
|
||||
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeParams structure.
|
||||
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, {
|
||||
// @ts-ignore: The `_useHtmlParser2` internal option is the
|
||||
// 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("");
|
||||
}
|
||||
} else if (highlighter) {
|
||||
safeBody = highlighter.applyHighlights(plainBody, safeHighlights).join("");
|
||||
safeBody = highlighter.applyHighlights(plainBody, safeHighlights!).join("");
|
||||
}
|
||||
} finally {
|
||||
delete sanitizeParams.textFilter;
|
||||
|
@ -597,9 +597,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
|
|||
|
||||
const match = BIGEMOJI_REGEX.exec(contentBodyTrimmed);
|
||||
emojiBody =
|
||||
match &&
|
||||
match[0] &&
|
||||
match[0].length === contentBodyTrimmed.length &&
|
||||
match?.[0]?.length === contentBodyTrimmed.length &&
|
||||
// Prevent user pills expanding for users with only emoji in
|
||||
// their username. Permalinks (links in pills) can be any URL
|
||||
// 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,
|
||||
});
|
||||
|
||||
let emojiBodyElements: JSX.Element[];
|
||||
let emojiBodyElements: JSX.Element[] | undefined;
|
||||
if (!safeBody && bodyHasEmoji) {
|
||||
emojiBodyElements = formatEmojis(strippedBody, false) as JSX.Element[];
|
||||
}
|
||||
|
@ -649,7 +647,7 @@ export function topicToHtml(
|
|||
allowExtendedHtml = false,
|
||||
): ReactNode {
|
||||
if (!SettingsStore.getValue("feature_html_topic")) {
|
||||
htmlTopic = null;
|
||||
htmlTopic = undefined;
|
||||
}
|
||||
|
||||
let isFormattedTopic = !!htmlTopic;
|
||||
|
@ -657,10 +655,10 @@ export function topicToHtml(
|
|||
let safeTopic = "";
|
||||
|
||||
try {
|
||||
topicHasEmoji = mightContainEmoji(isFormattedTopic ? htmlTopic : topic);
|
||||
topicHasEmoji = mightContainEmoji(isFormattedTopic ? htmlTopic! : topic);
|
||||
|
||||
if (isFormattedTopic) {
|
||||
safeTopic = sanitizeHtml(htmlTopic, allowExtendedHtml ? sanitizeHtmlParams : topicSanitizeHtmlParams);
|
||||
safeTopic = sanitizeHtml(htmlTopic!, allowExtendedHtml ? sanitizeHtmlParams : topicSanitizeHtmlParams);
|
||||
if (topicHasEmoji) {
|
||||
safeTopic = formatEmojis(safeTopic, true).join("");
|
||||
}
|
||||
|
@ -669,7 +667,7 @@ export function topicToHtml(
|
|||
isFormattedTopic = false; // Fall back to plain-text topic
|
||||
}
|
||||
|
||||
let emojiBodyElements: ReturnType<typeof formatEmojis>;
|
||||
let emojiBodyElements: ReturnType<typeof formatEmojis> | undefined;
|
||||
if (!isFormattedTopic && topicHasEmoji) {
|
||||
emojiBodyElements = formatEmojis(topic, false);
|
||||
}
|
||||
|
|
|
@ -169,10 +169,18 @@ export interface IConfigOptions {
|
|||
inline?: {
|
||||
left?: string;
|
||||
right?: string;
|
||||
pattern?: {
|
||||
tex?: string;
|
||||
latex?: string;
|
||||
};
|
||||
};
|
||||
display?: {
|
||||
left?: 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.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
|
||||
export const Key = {
|
||||
HOME: "Home",
|
||||
END: "End",
|
||||
|
@ -76,7 +78,7 @@ export const Key = {
|
|||
|
||||
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) {
|
||||
return ev.metaKey && !ev.altKey && !ev.ctrlKey && !ev.shiftKey;
|
||||
} else {
|
||||
|
|
|
@ -158,9 +158,9 @@ export default class LegacyCallHandler extends EventEmitter {
|
|||
private transferees = new Map<string, MatrixCall>(); // callId (target) -> call (transferee)
|
||||
private audioPromises = new Map<AudioID, Promise<void>>();
|
||||
private audioElementsWithListeners = new Map<HTMLMediaElement, boolean>();
|
||||
private supportsPstnProtocol = null;
|
||||
private pstnSupportPrefixed = 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 supportsPstnProtocol: boolean | null = null;
|
||||
private pstnSupportPrefixed: boolean | null = null; // True if the server only support the prefixed pstn protocol
|
||||
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.
|
||||
// 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,
|
||||
// this map will never be populated, but we check anyway for sanity
|
||||
if (this.shouldObeyAssertedfIdentity()) {
|
||||
const nativeUser = this.assertedIdentityNativeUsers[call.callId];
|
||||
const nativeUser = this.assertedIdentityNativeUsers.get(call.callId);
|
||||
if (nativeUser) {
|
||||
const room = findDMForUser(MatrixClientPeg.get(), nativeUser);
|
||||
if (room) return room.roomId;
|
||||
|
@ -466,8 +466,8 @@ export default class LegacyCallHandler extends EventEmitter {
|
|||
return this.getAllActiveCallsNotInRoom(roomId);
|
||||
}
|
||||
|
||||
public getTransfereeForCallId(callId: string): MatrixCall {
|
||||
return this.transferees[callId];
|
||||
public getTransfereeForCallId(callId: string): MatrixCall | undefined {
|
||||
return this.transferees.get(callId);
|
||||
}
|
||||
|
||||
public play(audioId: AudioID): void {
|
||||
|
@ -621,7 +621,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
|||
logger.log(`Asserted identity ${newAssertedIdentity} mapped to ${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 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;
|
||||
}
|
||||
if (transferee) {
|
||||
this.transferees[call.callId] = transferee;
|
||||
this.transferees.set(call.callId, transferee);
|
||||
}
|
||||
|
||||
this.setCallListeners(call);
|
||||
|
|
|
@ -91,12 +91,12 @@ export default class Login {
|
|||
}
|
||||
|
||||
public loginViaPassword(
|
||||
username: string,
|
||||
phoneCountry: string,
|
||||
phoneNumber: string,
|
||||
username: string | undefined,
|
||||
phoneCountry: string | undefined,
|
||||
phoneNumber: string | undefined,
|
||||
password: string,
|
||||
): Promise<IMatrixClientCreds> {
|
||||
const isEmail = username.indexOf("@") > 0;
|
||||
const isEmail = username?.indexOf("@") > 0;
|
||||
|
||||
let identifier;
|
||||
if (phoneCountry && phoneNumber) {
|
||||
|
|
|
@ -139,7 +139,7 @@ export default class Markdown {
|
|||
*/
|
||||
private repairLinks(parsed: commonmark.Node): commonmark.Node {
|
||||
const walker = parsed.walker();
|
||||
let event: commonmark.NodeWalkingStep = null;
|
||||
let event: commonmark.NodeWalkingStep | null = null;
|
||||
let text = "";
|
||||
let isInPara = false;
|
||||
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
|
||||
// in order to avoid deviation to commonmark and unexpected
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -37,7 +37,7 @@ export enum MediaDeviceHandlerEvent {
|
|||
}
|
||||
|
||||
export default class MediaDeviceHandler extends EventEmitter {
|
||||
private static internalInstance;
|
||||
private static internalInstance?: MediaDeviceHandler;
|
||||
|
||||
public static get instance(): MediaDeviceHandler {
|
||||
if (!MediaDeviceHandler.internalInstance) {
|
||||
|
@ -67,7 +67,7 @@ export default class MediaDeviceHandler extends EventEmitter {
|
|||
public static async getDevices(): Promise<IMediaDevices> {
|
||||
try {
|
||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||
const output = {
|
||||
const output: Record<MediaDeviceKindEnum, MediaDeviceInfo[]> = {
|
||||
[MediaDeviceKindEnum.AudioOutput]: [],
|
||||
[MediaDeviceKindEnum.AudioInput]: [],
|
||||
[MediaDeviceKindEnum.VideoInput]: [],
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { ReactInstance } from "react";
|
||||
import ReactDom from "react-dom";
|
||||
|
||||
interface IChildProps {
|
||||
|
@ -41,7 +41,7 @@ interface IProps {
|
|||
* automatic positional animation, look at react-shuffle or similar libraries.
|
||||
*/
|
||||
export default class NodeAnimator extends React.Component<IProps> {
|
||||
private nodes = {};
|
||||
private nodes: Record<string, ReactInstance> = {};
|
||||
private children: { [key: string]: React.DetailedReactHTMLElement<any, HTMLElement> };
|
||||
public static defaultProps: Partial<IProps> = {
|
||||
startStyles: [],
|
||||
|
@ -65,7 +65,7 @@ export default class NodeAnimator extends React.Component<IProps> {
|
|||
*/
|
||||
private applyStyles(node: HTMLElement, styles: React.CSSProperties): void {
|
||||
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;
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
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
|
||||
type of tile.
|
||||
*/
|
||||
const msgTypeHandlers = {
|
||||
const msgTypeHandlers: Record<string, (event: MatrixEvent) => string> = {
|
||||
[MsgType.KeyVerificationRequest]: (event: MatrixEvent) => {
|
||||
const name = (event.sender || {}).name;
|
||||
return _t("%(name)s is requesting verification", { name });
|
||||
|
@ -95,22 +95,26 @@ const msgTypeHandlers = {
|
|||
},
|
||||
};
|
||||
|
||||
export const Notifier = {
|
||||
notifsByRoom: {},
|
||||
class NotifierClass {
|
||||
private notifsByRoom: Record<string, Notification[]> = {};
|
||||
|
||||
// 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
|
||||
// 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)) {
|
||||
return msgTypeHandlers[ev.getContent().msgtype](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 cli = MatrixClientPeg.get();
|
||||
if (!plaf) {
|
||||
|
@ -165,9 +169,14 @@ export const Notifier = {
|
|||
if (this.notifsByRoom[ev.getRoomId()] === undefined) this.notifsByRoom[ev.getRoomId()] = [];
|
||||
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
|
||||
// and the browser will cache the sound.
|
||||
const content = SettingsStore.getValue("notificationSound", roomId);
|
||||
|
@ -193,9 +202,10 @@ export const Notifier = {
|
|||
type: content.type,
|
||||
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();
|
||||
if (localNotificationsAreSilenced(cli)) {
|
||||
return;
|
||||
|
@ -224,39 +234,32 @@ export const Notifier = {
|
|||
} catch (ex) {
|
||||
logger.warn("Caught error when trying to fetch room notification sound:", ex);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
start: function (this: typeof Notifier) {
|
||||
// do not re-bind in the case of repeated call
|
||||
this.boundOnEvent = this.boundOnEvent || this.onEvent.bind(this);
|
||||
this.boundOnSyncStateChange = this.boundOnSyncStateChange || this.onSyncStateChange.bind(this);
|
||||
this.boundOnRoomReceipt = this.boundOnRoomReceipt || this.onRoomReceipt.bind(this);
|
||||
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);
|
||||
public start(): void {
|
||||
MatrixClientPeg.get().on(RoomEvent.Timeline, this.onEvent);
|
||||
MatrixClientPeg.get().on(RoomEvent.Receipt, this.onRoomReceipt);
|
||||
MatrixClientPeg.get().on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
|
||||
MatrixClientPeg.get().on(ClientEvent.Sync, this.onSyncStateChange);
|
||||
this.toolbarHidden = false;
|
||||
this.isSyncing = false;
|
||||
},
|
||||
}
|
||||
|
||||
stop: function (this: typeof Notifier) {
|
||||
public stop(): void {
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener(RoomEvent.Timeline, this.boundOnEvent);
|
||||
MatrixClientPeg.get().removeListener(RoomEvent.Receipt, this.boundOnRoomReceipt);
|
||||
MatrixClientPeg.get().removeListener(MatrixEventEvent.Decrypted, this.boundOnEventDecrypted);
|
||||
MatrixClientPeg.get().removeListener(ClientEvent.Sync, this.boundOnSyncStateChange);
|
||||
MatrixClientPeg.get().removeListener(RoomEvent.Timeline, this.onEvent);
|
||||
MatrixClientPeg.get().removeListener(RoomEvent.Receipt, this.onRoomReceipt);
|
||||
MatrixClientPeg.get().removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted);
|
||||
MatrixClientPeg.get().removeListener(ClientEvent.Sync, this.onSyncStateChange);
|
||||
}
|
||||
this.isSyncing = false;
|
||||
},
|
||||
}
|
||||
|
||||
supportsDesktopNotifications: function () {
|
||||
const plaf = PlatformPeg.get();
|
||||
return plaf && plaf.supportsNotifications();
|
||||
},
|
||||
public supportsDesktopNotifications(): boolean {
|
||||
return PlatformPeg.get()?.supportsNotifications() ?? false;
|
||||
}
|
||||
|
||||
setEnabled: function (enable: boolean, callback?: () => void) {
|
||||
public setEnabled(enable: boolean, callback?: () => void): void {
|
||||
const plaf = PlatformPeg.get();
|
||||
if (!plaf) return;
|
||||
|
||||
|
@ -320,31 +323,30 @@ export const Notifier = {
|
|||
// set the notifications_hidden flag, as the user has knowingly interacted
|
||||
// with the setting we shouldn't nag them any further
|
||||
this.setPromptHidden(true);
|
||||
},
|
||||
}
|
||||
|
||||
isEnabled: function () {
|
||||
public isEnabled(): boolean {
|
||||
return this.isPossible() && SettingsStore.getValue("notificationsEnabled");
|
||||
},
|
||||
}
|
||||
|
||||
isPossible: function () {
|
||||
public isPossible(): boolean {
|
||||
const plaf = PlatformPeg.get();
|
||||
if (!plaf) return false;
|
||||
if (!plaf.supportsNotifications()) return false;
|
||||
if (!plaf?.supportsNotifications()) return false;
|
||||
if (!plaf.maySendNotifications()) return false;
|
||||
|
||||
return true; // possible, but not necessarily enabled
|
||||
},
|
||||
}
|
||||
|
||||
isBodyEnabled: function () {
|
||||
public isBodyEnabled(): boolean {
|
||||
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
|
||||
return SettingsStore.getValue("audioNotificationsEnabled");
|
||||
},
|
||||
}
|
||||
|
||||
setPromptHidden: function (this: typeof Notifier, hidden: boolean, persistent = true) {
|
||||
public setPromptHidden(hidden: boolean, persistent = true): void {
|
||||
this.toolbarHidden = hidden;
|
||||
|
||||
hideNotificationsToast();
|
||||
|
@ -353,9 +355,9 @@ export const Notifier = {
|
|||
if (persistent && global.localStorage) {
|
||||
global.localStorage.setItem("notifications_hidden", String(hidden));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
shouldShowPrompt: function () {
|
||||
public shouldShowPrompt(): boolean {
|
||||
const client = MatrixClientPeg.get();
|
||||
if (!client) {
|
||||
return false;
|
||||
|
@ -366,25 +368,21 @@ export const Notifier = {
|
|||
this.supportsDesktopNotifications() &&
|
||||
!isPushNotifyDisabled() &&
|
||||
!this.isEnabled() &&
|
||||
!this._isPromptHidden()
|
||||
!this.isPromptHidden()
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
_isPromptHidden: function (this: typeof Notifier) {
|
||||
private isPromptHidden(): boolean {
|
||||
// Check localStorage for any such meta data
|
||||
if (global.localStorage) {
|
||||
return global.localStorage.getItem("notifications_hidden") === "true";
|
||||
}
|
||||
|
||||
return this.toolbarHidden;
|
||||
},
|
||||
}
|
||||
|
||||
onSyncStateChange: function (
|
||||
this: typeof Notifier,
|
||||
state: SyncState,
|
||||
prevState?: SyncState,
|
||||
data?: ISyncStateData,
|
||||
) {
|
||||
// XXX: Exported for tests
|
||||
public onSyncStateChange = (state: SyncState, prevState?: SyncState, data?: ISyncStateData): void => {
|
||||
if (state === SyncState.Syncing) {
|
||||
this.isSyncing = true;
|
||||
} else if (state === SyncState.Stopped || state === SyncState.Error) {
|
||||
|
@ -395,16 +393,15 @@ export const Notifier = {
|
|||
if (![SyncState.Stopped, SyncState.Error].includes(state) && !data?.fromCache) {
|
||||
createLocalNotificationSettingsIfNeeded(MatrixClientPeg.get());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
onEvent: function (
|
||||
this: typeof Notifier,
|
||||
private onEvent = (
|
||||
ev: MatrixEvent,
|
||||
room: Room | undefined,
|
||||
toStartOfTimeline: boolean | undefined,
|
||||
removed: boolean,
|
||||
data: IRoomTimelineData,
|
||||
) {
|
||||
): void => {
|
||||
if (!data.liveEvent) return; // only notify for new things, not old.
|
||||
if (!this.isSyncing) return; // don't alert for any messages initially
|
||||
if (ev.getSender() === MatrixClientPeg.get().getUserId()) return;
|
||||
|
@ -422,10 +419,10 @@ export const Notifier = {
|
|||
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,
|
||||
// in which case it might decrypt soon if the keys arrive
|
||||
if (ev.isDecryptionFailure()) return;
|
||||
|
@ -434,10 +431,10 @@ export const Notifier = {
|
|||
if (idx === -1) return;
|
||||
|
||||
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) {
|
||||
// ideally we would clear each notification when it was read,
|
||||
// but we have no way, given a read receipt, to know whether
|
||||
|
@ -453,12 +450,12 @@ export const Notifier = {
|
|||
}
|
||||
delete this.notifsByRoom[room.roomId];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
_evaluateEvent: function (ev: MatrixEvent) {
|
||||
// XXX: exported for tests
|
||||
public evaluateEvent(ev: MatrixEvent): void {
|
||||
// Mute notifications for broadcast info events
|
||||
if (ev.getType() === VoiceBroadcastInfoEventType) return;
|
||||
|
||||
let roomId = ev.getRoomId();
|
||||
if (LegacyCallHandler.instance.getSupportsVirtualRooms()) {
|
||||
// Attempt to translate a virtual room to a native one
|
||||
|
@ -477,7 +474,7 @@ export const Notifier = {
|
|||
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
||||
|
||||
if (actions?.notify) {
|
||||
this._performCustomEventHandling(ev);
|
||||
this.performCustomEventHandling(ev);
|
||||
|
||||
const store = SdkContextClass.instance.roomViewStore;
|
||||
const isViewingRoom = store.getRoomId() === room.roomId;
|
||||
|
@ -492,19 +489,19 @@ export const Notifier = {
|
|||
}
|
||||
|
||||
if (this.isEnabled()) {
|
||||
this._displayPopupNotification(ev, room);
|
||||
this.displayPopupNotification(ev, room);
|
||||
}
|
||||
if (actions.tweaks.sound && this.isAudioEnabled()) {
|
||||
PlatformPeg.get().loudNotification(ev, room);
|
||||
this._playAudioNotification(ev, room);
|
||||
this.playAudioNotification(ev, room);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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")) {
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
key: getIncomingCallToastKey(ev.getStateKey()),
|
||||
|
@ -514,11 +511,12 @@ export const Notifier = {
|
|||
props: { callEvent: ev },
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!window.mxNotifier) {
|
||||
window.mxNotifier = Notifier;
|
||||
window.mxNotifier = new NotifierClass();
|
||||
}
|
||||
|
||||
export default window.mxNotifier;
|
||||
export const Notifier: NotifierClass = window.mxNotifier;
|
||||
|
|
|
@ -132,8 +132,8 @@ export class PosthogAnalytics {
|
|||
private anonymity = Anonymity.Disabled;
|
||||
// set true during the constructor if posthog config is present, otherwise false
|
||||
private readonly enabled: boolean = false;
|
||||
private static _instance = null;
|
||||
private platformSuperProperties = {};
|
||||
private static _instance: PosthogAnalytics | null = null;
|
||||
private platformSuperProperties: Properties = {};
|
||||
public static readonly ANALYTICS_EVENT_TYPE = "im.vector.analytics";
|
||||
private propertiesForNextEvent: Partial<Record<"$set" | "$set_once", UserProperties>> = {};
|
||||
private userPropertyCache: UserProperties = {};
|
||||
|
|
|
@ -120,7 +120,7 @@ export class PosthogScreenTracker extends PureComponent<{ screenName: 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,9 +33,9 @@ enum State {
|
|||
}
|
||||
|
||||
class Presence {
|
||||
private unavailableTimer: Timer = null;
|
||||
private dispatcherRef: string = null;
|
||||
private state: State = null;
|
||||
private unavailableTimer: Timer | null = null;
|
||||
private dispatcherRef: string | null = null;
|
||||
private state: State | null = null;
|
||||
|
||||
/**
|
||||
* Start listening the user activity to evaluate his presence state.
|
||||
|
@ -73,14 +73,14 @@ class Presence {
|
|||
* Get the current presence state.
|
||||
* @returns {string} the presence state (see PRESENCE enum)
|
||||
*/
|
||||
public getState(): State {
|
||||
public getState(): State | null {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
private onAction = (payload: ActionPayload): void => {
|
||||
if (payload.action === "user_activity") {
|
||||
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> {
|
||||
const room = MatrixClientPeg.get().getRoom(event.getRoomId());
|
||||
const room = MatrixClientPeg.get().getRoom(event.getRoomId())!;
|
||||
return MatrixClientPeg.get()
|
||||
.resendEvent(event, room)
|
||||
.then(
|
||||
|
|
|
@ -30,6 +30,6 @@ export function storeRoomAliasInCache(alias: string, id: string): void {
|
|||
aliasToIDMap.set(alias, id);
|
||||
}
|
||||
|
||||
export function getCachedRoomIDForAlias(alias: string): string {
|
||||
export function getCachedRoomIDForAlias(alias: string): string | undefined {
|
||||
return aliasToIDMap.get(alias);
|
||||
}
|
||||
|
|
|
@ -112,7 +112,7 @@ export function inviteUsersToRoom(
|
|||
): Promise<void> {
|
||||
return inviteMultipleToRoom(roomId, userIds, sendSharedHistoryKeys, progressCallback)
|
||||
.then((result) => {
|
||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||
const room = MatrixClientPeg.get().getRoom(roomId)!;
|
||||
showAnyInviteErrors(result.states, room, result.inviter);
|
||||
})
|
||||
.catch((err) => {
|
||||
|
@ -175,14 +175,14 @@ export function showAnyInviteErrors(
|
|||
<BaseAvatar
|
||||
url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24) : null}
|
||||
name={name}
|
||||
idName={user.userId}
|
||||
idName={user?.userId}
|
||||
width={36}
|
||||
height={36}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx_InviteDialog_tile_nameStack">
|
||||
<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 className="mx_InviteDialog_tile--inviterError_errorText">
|
||||
{inviter.getErrorText(addr)}
|
||||
|
|
|
@ -46,7 +46,7 @@ export function getRoomNotifsState(client: MatrixClient, roomId: string): RoomNo
|
|||
}
|
||||
|
||||
// for everything else, look at the room rule.
|
||||
let roomRule = null;
|
||||
let roomRule: IPushRule | undefined;
|
||||
try {
|
||||
roomRule = client.getRoomPushRule("global", roomId);
|
||||
} catch (err) {
|
||||
|
@ -106,7 +106,7 @@ export function getUnreadNotificationCount(room: Room, type: NotificationCountTy
|
|||
|
||||
function setRoomNotifsStateMuted(roomId: string): Promise<any> {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const promises = [];
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
// delete the room rule
|
||||
const roomRule = cli.getRoomPushRule("global", roomId);
|
||||
|
@ -137,7 +137,7 @@ function setRoomNotifsStateMuted(roomId: string): Promise<any> {
|
|||
|
||||
function setRoomNotifsStateUnmuted(roomId: string, newState: RoomNotifState): Promise<any> {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const promises = [];
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
const overrideMuteRule = findOverrideMuteRule(roomId);
|
||||
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
|
||||
* @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());
|
||||
}
|
||||
|
||||
// The various display alias getters should all feed through this one path so
|
||||
// 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) {
|
||||
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> {
|
||||
let newTarget;
|
||||
if (isDirect) {
|
||||
const guessedUserId = guessDMRoomTargetId(room, MatrixClientPeg.get().getUserId());
|
||||
const guessedUserId = guessDMRoomTargetId(room, MatrixClientPeg.get().getUserId()!);
|
||||
newTarget = guessedUserId;
|
||||
} else {
|
||||
newTarget = null;
|
||||
|
@ -118,7 +118,7 @@ function guessDMRoomTargetId(room: Room, myUserId: string): string {
|
|||
|
||||
if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) {
|
||||
oldestUser = user;
|
||||
oldestTs = user.events.member.getTs();
|
||||
oldestTs = user.events.member?.getTs();
|
||||
}
|
||||
}
|
||||
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)) {
|
||||
oldestUser = user;
|
||||
oldestTs = user.events.member.getTs();
|
||||
oldestTs = user.events.member?.getTs();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -182,7 +182,7 @@ async function getSecretStorageKey({
|
|||
|
||||
export async function getDehydrationKey(
|
||||
keyInfo: ISecretStorageKeyInfo,
|
||||
checkFunc: (Uint8Array) => void,
|
||||
checkFunc: (data: Uint8Array) => void,
|
||||
): Promise<Uint8Array> {
|
||||
const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.();
|
||||
if (keyFromCustomisations) {
|
||||
|
@ -196,7 +196,7 @@ export async function getDehydrationKey(
|
|||
/* props= */
|
||||
{
|
||||
keyInfo,
|
||||
checkPrivateKey: async (input): Promise<boolean> => {
|
||||
checkPrivateKey: async (input: KeyParams): Promise<boolean> => {
|
||||
const key = await inputToKey(input);
|
||||
try {
|
||||
checkFunc(key);
|
||||
|
@ -290,7 +290,7 @@ export async function promptForBackupPassphrase(): Promise<Uint8Array> {
|
|||
RestoreKeyBackupDialog,
|
||||
{
|
||||
showSummary: false,
|
||||
keyCallback: (k) => (key = k),
|
||||
keyCallback: (k: Uint8Array) => (key = k),
|
||||
},
|
||||
null,
|
||||
/* priority = */ false,
|
||||
|
|
|
@ -697,11 +697,8 @@ export const Commands = [
|
|||
}
|
||||
|
||||
if (viaServers) {
|
||||
// For the join
|
||||
dispatch["opts"] = {
|
||||
// These are passed down to the js-sdk's /join call
|
||||
viaServers: viaServers,
|
||||
};
|
||||
// For the join, these are passed down to the js-sdk's /join call
|
||||
dispatch["opts"] = { viaServers };
|
||||
|
||||
// For if the join fails (rejoin button)
|
||||
dispatch["via_servers"] = viaServers;
|
||||
|
@ -1042,7 +1039,7 @@ export const Commands = [
|
|||
throw newTranslatableError("Session already verified!");
|
||||
} else {
|
||||
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;
|
||||
};
|
||||
|
||||
export type TermsInteractionCallback = (
|
||||
policiesAndServicePairs: {
|
||||
service: Service;
|
||||
export type ServicePolicyPair = {
|
||||
policies: Policies;
|
||||
}[],
|
||||
service: Service;
|
||||
};
|
||||
|
||||
export type TermsInteractionCallback = (
|
||||
policiesAndServicePairs: ServicePolicyPair[],
|
||||
agreedUrls: string[],
|
||||
extraClassNames?: 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 that is not a thing the API supports, so probably best to just show
|
||||
// things they've not agreed to yet.
|
||||
const unagreedPoliciesAndServicePairs = [];
|
||||
const unagreedPoliciesAndServicePairs: ServicePolicyPair[] = [];
|
||||
for (const { service, policies } of policiesAndServicePairs) {
|
||||
const unagreedPolicies = {};
|
||||
const unagreedPolicies: Policies = {};
|
||||
for (const [policyName, policy] of Object.entries(policies)) {
|
||||
let policyAgreed = false;
|
||||
for (const lang of Object.keys(policy)) {
|
||||
|
|
|
@ -33,7 +33,7 @@ import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases";
|
|||
import defaultDispatcher from "./dispatcher/dispatcher";
|
||||
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||
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 { highlightEvent, isLocationEvent } from "./utils/EventUtils";
|
||||
import { ElementCall } from "./models/Call";
|
||||
|
@ -308,7 +308,7 @@ function textForServerACLEvent(ev: MatrixEvent): () => string | null {
|
|||
allow_ip_literals: prevContent.allow_ip_literals !== false,
|
||||
};
|
||||
|
||||
let getText = null;
|
||||
let getText: () => string = null;
|
||||
if (prev.deny.length === 0 && prev.allow.length === 0) {
|
||||
getText = () => _t("%(senderDisplayName)s set the server ACLs for this room.", { senderDisplayName });
|
||||
} else {
|
||||
|
@ -360,8 +360,8 @@ function textForCanonicalAliasEvent(ev: MatrixEvent): () => string | null {
|
|||
const oldAltAliases = ev.getPrevContent().alt_aliases || [];
|
||||
const newAlias = ev.getContent().alias;
|
||||
const newAltAliases = ev.getContent().alt_aliases || [];
|
||||
const removedAltAliases = oldAltAliases.filter((alias) => !newAltAliases.includes(alias));
|
||||
const addedAltAliases = newAltAliases.filter((alias) => !oldAltAliases.includes(alias));
|
||||
const removedAltAliases = oldAltAliases.filter((alias: string) => !newAltAliases.includes(alias));
|
||||
const addedAltAliases = newAltAliases.filter((alias: string) => !oldAltAliases.includes(alias));
|
||||
|
||||
if (!removedAltAliases.length && !addedAltAliases.length) {
|
||||
if (newAlias) {
|
||||
|
@ -533,8 +533,8 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
|
|||
const senderName = getSenderName(event);
|
||||
const roomId = event.getRoomId();
|
||||
|
||||
const pinned = event.getContent().pinned ?? [];
|
||||
const previouslyPinned = event.getPrevContent().pinned ?? [];
|
||||
const pinned = event.getContent<{ pinned: string[] }>().pinned ?? [];
|
||||
const previouslyPinned: string[] = event.getPrevContent().pinned ?? [];
|
||||
const newlyPinned = pinned.filter((item) => previouslyPinned.indexOf(item) < 0);
|
||||
const newlyUnpinned = previouslyPinned.filter((item) => pinned.indexOf(item) < 0);
|
||||
|
||||
|
@ -550,7 +550,10 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
|
|||
{ senderName },
|
||||
{
|
||||
a: (sub) => (
|
||||
<AccessibleButton kind="link_inline" onClick={(e) => highlightEvent(roomId, messageId)}>
|
||||
<AccessibleButton
|
||||
kind="link_inline"
|
||||
onClick={(e: ButtonEvent) => highlightEvent(roomId, messageId)}
|
||||
>
|
||||
{sub}
|
||||
</AccessibleButton>
|
||||
),
|
||||
|
@ -580,7 +583,10 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
|
|||
{ senderName },
|
||||
{
|
||||
a: (sub) => (
|
||||
<AccessibleButton kind="link_inline" onClick={(e) => highlightEvent(roomId, messageId)}>
|
||||
<AccessibleButton
|
||||
kind="link_inline"
|
||||
onClick={(e: ButtonEvent) => highlightEvent(roomId, messageId)}
|
||||
>
|
||||
{sub}
|
||||
</AccessibleButton>
|
||||
),
|
||||
|
|
|
@ -168,7 +168,7 @@ export default class UserActivity {
|
|||
return this.activeRecentlyTimeout.isRunning();
|
||||
}
|
||||
|
||||
private onPageVisibilityChanged = (e): void => {
|
||||
private onPageVisibilityChanged = (e: Event): void => {
|
||||
if (this.document.visibilityState === "hidden") {
|
||||
this.activeNowTimeout.abort();
|
||||
this.activeRecentlyTimeout.abort();
|
||||
|
@ -182,11 +182,12 @@ export default class UserActivity {
|
|||
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
|
||||
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) {
|
||||
// mouse hasn't actually moved
|
||||
return;
|
||||
|
@ -223,4 +224,8 @@ export default class UserActivity {
|
|||
}
|
||||
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;
|
||||
}
|
||||
|
||||
private async userToVirtualUser(userId: string): Promise<string> {
|
||||
private async userToVirtualUser(userId: string): Promise<string | null> {
|
||||
const results = await LegacyCallHandler.instance.sipVirtualLookup(userId);
|
||||
if (results.length === 0 || !results[0].fields.lookup_success) return null;
|
||||
return results[0].userid;
|
||||
|
@ -59,11 +59,11 @@ export default class VoipUserMapper {
|
|||
if (!virtualUser) return null;
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
this.virtualToNativeRoomIdCache.set(virtualRoomId, roomId);
|
||||
this.virtualToNativeRoomIdCache.set(virtualRoomId!, roomId);
|
||||
|
||||
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
|
||||
* virtual room
|
||||
*/
|
||||
public async getVirtualRoomForRoom(roomId: string): Promise<Room | null> {
|
||||
public async getVirtualRoomForRoom(roomId: string): Promise<Room | undefined> {
|
||||
const virtualUser = await this.getVirtualUserForRoom(roomId);
|
||||
if (!virtualUser) return null;
|
||||
if (!virtualUser) return undefined;
|
||||
|
||||
return findDMForUser(MatrixClientPeg.get(), virtualUser);
|
||||
}
|
||||
|
@ -121,8 +121,12 @@ export default class VoipUserMapper {
|
|||
if (!LegacyCallHandler.instance.getSupportsVirtualRooms()) return;
|
||||
|
||||
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}`);
|
||||
const result = await LegacyCallHandler.instance.sipNativeLookup(inviterId);
|
||||
const result = await LegacyCallHandler.instance.sipNativeLookup(inviterId!);
|
||||
if (result.length === 0) {
|
||||
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
|
||||
// to make sure we joined virtual rooms on joining a native one)
|
||||
MatrixClientPeg.get().joinRoom(invitedRoom.roomId);
|
||||
}
|
||||
|
||||
// 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
|
||||
this.virtualToNativeRoomIdCache.set(invitedRoom.roomId, nativeRoom.roomId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,11 +21,11 @@ import { MatrixClientPeg } from "./MatrixClientPeg";
|
|||
import { _t } from "./languageHandler";
|
||||
|
||||
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[] {
|
||||
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.
|
||||
*/
|
||||
export function usersTyping(room: Room, exclude: string[] = []): RoomMember[] {
|
||||
const whoIsTyping = [];
|
||||
const whoIsTyping: RoomMember[] = [];
|
||||
|
||||
const memberKeys = Object.keys(room.currentState.members);
|
||||
for (const userId of memberKeys) {
|
||||
|
|
|
@ -27,6 +27,7 @@ import {
|
|||
KEYBOARD_SHORTCUTS,
|
||||
MAC_ONLY_SHORTCUTS,
|
||||
} from "./KeyboardShortcuts";
|
||||
import { IBaseSetting } from "../settings/Settings";
|
||||
|
||||
/**
|
||||
* This function gets the keyboard shortcuts that should be presented in the UI
|
||||
|
@ -103,7 +104,7 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
|
|||
return true;
|
||||
})
|
||||
.reduce((o, key) => {
|
||||
o[key] = KEYBOARD_SHORTCUTS[key];
|
||||
o[key as KeyBindingAction] = KEYBOARD_SHORTCUTS[key as KeyBindingAction];
|
||||
return o;
|
||||
}, {} as IKeyboardShortcuts);
|
||||
};
|
||||
|
@ -112,7 +113,10 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
|
|||
* Gets keyboard shortcuts that should be presented to the user in the UI.
|
||||
*/
|
||||
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]) => {
|
||||
acc[key] = value;
|
||||
|
@ -120,11 +124,11 @@ export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => {
|
|||
}, {} as IKeyboardShortcuts);
|
||||
};
|
||||
|
||||
export const getKeyboardShortcutValue = (name: string): KeyCombo | undefined => {
|
||||
export const getKeyboardShortcutValue = (name: KeyBindingAction): KeyCombo | undefined => {
|
||||
return getKeyboardShortcutsForUI()[name]?.default;
|
||||
};
|
||||
|
||||
export const getKeyboardShortcutDisplayName = (name: string): string | undefined => {
|
||||
export const getKeyboardShortcutDisplayName = (name: KeyBindingAction): string | undefined => {
|
||||
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>;
|
||||
|
||||
export type IKeyboardShortcuts = {
|
||||
// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
|
||||
[k in KeyBindingAction]?: KeyboardShortcutSetting;
|
||||
};
|
||||
// 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>>;
|
||||
|
||||
export interface ICategory {
|
||||
categoryLabel?: string;
|
||||
|
|
|
@ -25,6 +25,7 @@ import React, {
|
|||
Reducer,
|
||||
Dispatch,
|
||||
RefObject,
|
||||
ReactNode,
|
||||
} from "react";
|
||||
|
||||
import { getKeyBindingsManager } from "../KeyBindingsManager";
|
||||
|
@ -158,8 +159,8 @@ interface IProps {
|
|||
handleHomeEnd?: boolean;
|
||||
handleUpDown?: boolean;
|
||||
handleLeftRight?: boolean;
|
||||
children(renderProps: { onKeyDownHandler(ev: React.KeyboardEvent) });
|
||||
onKeyDown?(ev: React.KeyboardEvent, state: IState);
|
||||
children(renderProps: { onKeyDownHandler(ev: React.KeyboardEvent): void }): ReactNode;
|
||||
onKeyDown?(ev: React.KeyboardEvent, state: IState): void;
|
||||
}
|
||||
|
||||
export const findSiblingElement = (
|
||||
|
|
|
@ -25,7 +25,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
|||
|
||||
interface IProps extends React.ComponentProps<typeof StyledCheckbox> {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
|||
|
||||
interface IProps extends React.ComponentProps<typeof StyledRadioButton> {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ export const RovingAccessibleButton: React.FC<IProps> = ({ inputRef, onFocus, ..
|
|||
return (
|
||||
<AccessibleButton
|
||||
{...props}
|
||||
onFocus={(event) => {
|
||||
onFocus={(event: React.FocusEvent) => {
|
||||
onFocusInternal();
|
||||
onFocus?.(event);
|
||||
}}
|
||||
|
|
|
@ -31,7 +31,7 @@ export const RovingAccessibleTooltipButton: React.FC<IProps> = ({ inputRef, onFo
|
|||
return (
|
||||
<AccessibleTooltipButton
|
||||
{...props}
|
||||
onFocus={(event) => {
|
||||
onFocus={(event: React.FocusEvent) => {
|
||||
onFocusInternal();
|
||||
onFocus?.(event);
|
||||
}}
|
||||
|
|
|
@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { ReactElement } from "react";
|
||||
|
||||
import { useRovingTabIndex } from "../RovingTabIndex";
|
||||
import { FocusHandler, Ref } from "./types";
|
||||
|
||||
interface IProps {
|
||||
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.
|
||||
|
|
|
@ -54,7 +54,7 @@ export default class RoomListActions {
|
|||
oldIndex: number | null,
|
||||
newIndex: number | null,
|
||||
): AsyncActionPayload {
|
||||
let metaData = null;
|
||||
let metaData: Parameters<MatrixClient["setRoomTag"]>[2] | null = null;
|
||||
|
||||
// Is the tag ordered manually?
|
||||
const store = RoomListStore.instance;
|
||||
|
@ -81,7 +81,7 @@ export default class RoomListActions {
|
|||
return asyncAction(
|
||||
"RoomListActions.tagRoom",
|
||||
() => {
|
||||
const promises = [];
|
||||
const promises: Promise<any>[] = [];
|
||||
const roomId = room.roomId;
|
||||
|
||||
// Evil hack to get DMs behaving
|
||||
|
@ -120,7 +120,7 @@ export default class RoomListActions {
|
|||
if (newTag && newTag !== DefaultTagID.DM && (hasChangedSubLists || metaData)) {
|
||||
// metaData is the body of the PUT to set the tag, so it must
|
||||
// at least be an empty object.
|
||||
metaData = metaData || {};
|
||||
metaData = metaData || ({} as typeof metaData);
|
||||
|
||||
const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function (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.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { ChangeEvent } from "react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import SdkConfig from "../../../../SdkConfig";
|
||||
|
@ -27,6 +28,7 @@ import Field from "../../../../components/views/elements/Field";
|
|||
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
||||
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
||||
import { IIndexStats } from "../../../../indexing/BaseEventIndexManager";
|
||||
|
||||
interface IProps extends IDialogProps {}
|
||||
|
||||
|
@ -43,7 +45,7 @@ interface IState {
|
|||
* Allows the user to introspect the event index state and disable it.
|
||||
*/
|
||||
export default class ManageEventIndexDialog extends React.Component<IProps, IState> {
|
||||
public constructor(props) {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
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();
|
||||
let stats;
|
||||
let stats: IIndexStats;
|
||||
|
||||
try {
|
||||
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);
|
||||
};
|
||||
|
||||
private onCrawlerSleepTimeChange = (e): void => {
|
||||
this.setState({ crawlerSleepTime: e.target.value });
|
||||
private onCrawlerSleepTimeChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||
this.setState({ crawlerSleepTime: parseInt(e.target.value, 10) });
|
||||
SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const brand = SdkConfig.get().brand;
|
||||
|
||||
let crawlerState;
|
||||
|
|
|
@ -239,7 +239,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent<IProps, I
|
|||
<form onSubmit={this.onPassPhraseNextClick}>
|
||||
<p>
|
||||
{_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> },
|
||||
)}
|
||||
|
@ -459,7 +459,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent<IProps, I
|
|||
}
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
let content;
|
||||
if (this.state.error) {
|
||||
content = (
|
||||
|
|
|
@ -20,7 +20,7 @@ import FileSaver from "file-saver";
|
|||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
||||
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 { 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!");
|
||||
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";
|
||||
});
|
||||
this.setState({
|
||||
|
@ -842,7 +842,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
|||
}
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
let content;
|
||||
if (this.state.error) {
|
||||
content = (
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import FileSaver from "file-saver";
|
||||
import React from "react";
|
||||
import React, { ChangeEvent } from "react";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
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>);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const disableForm = this.state.phase === Phase.Exporting;
|
||||
|
||||
return (
|
||||
|
@ -163,7 +163,9 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
|
|||
<Field
|
||||
label={_t("Enter passphrase")}
|
||||
value={this.state.passphrase1}
|
||||
onChange={(e) => this.onPassphraseChange(e, "passphrase1")}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
this.onPassphraseChange(e, "passphrase1")
|
||||
}
|
||||
autoFocus={true}
|
||||
size={64}
|
||||
type="password"
|
||||
|
@ -174,7 +176,9 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
|
|||
<Field
|
||||
label={_t("Confirm passphrase")}
|
||||
value={this.state.passphrase2}
|
||||
onChange={(e) => this.onPassphraseChange(e, "passphrase2")}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
this.onPassphraseChange(e, "passphrase2")
|
||||
}
|
||||
size={64}
|
||||
type="password"
|
||||
disabled={disableForm}
|
||||
|
|
|
@ -73,9 +73,9 @@ export default class ImportE2eKeysDialog extends React.Component<IProps, IState>
|
|||
}
|
||||
|
||||
private onFormChange = (): void => {
|
||||
const files = this.file.current.files || [];
|
||||
const files = this.file.current.files;
|
||||
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;
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const disableForm = this.state.phase !== Phase.Edit;
|
||||
|
||||
return (
|
||||
|
|
|
@ -48,13 +48,13 @@ export default class NewRecoveryMethodDialog extends React.PureComponent<IProps>
|
|||
{
|
||||
onFinished: this.props.onFinished,
|
||||
},
|
||||
null,
|
||||
undefined,
|
||||
/* priority = */ false,
|
||||
/* static = */ true,
|
||||
);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
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>;
|
||||
|
|
|
@ -37,14 +37,14 @@ export default class RecoveryMethodRemovedDialog extends React.PureComponent<IPr
|
|||
this.props.onFinished();
|
||||
Modal.createDialogAsync(
|
||||
import("./CreateKeyBackupDialog") as unknown as Promise<ComponentType<{}>>,
|
||||
null,
|
||||
undefined,
|
||||
null,
|
||||
/* priority = */ false,
|
||||
/* static = */ true,
|
||||
);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const title = <span className="mx_KeyBackupFailedDialog_title">{_t("Recovery Method Removed")}</span>;
|
||||
|
||||
return (
|
||||
|
|
|
@ -91,7 +91,7 @@ export class PlaybackQueue {
|
|||
|
||||
public unsortedEnqueue(mxEvent: MatrixEvent, playback: Playback): void {
|
||||
// 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.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 {
|
||||
// Remember where the user got to in playback
|
||||
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
|
||||
playback.skipTo(this.clockStates.get(mxEvent.getId())!);
|
||||
playback.skipTo(this.clockStates.get(mxEvent.getId()!)!);
|
||||
} else if (newState === PlaybackState.Stopped) {
|
||||
// Remove the now-useless clock for some space savings
|
||||
this.clockStates.delete(mxEvent.getId());
|
||||
this.clockStates.delete(mxEvent.getId()!);
|
||||
|
||||
if (wasLastPlaying) {
|
||||
this.recentFullPlays.add(this.currentPlaybackId);
|
||||
|
@ -133,7 +133,7 @@ export class PlaybackQueue {
|
|||
// timeline is already most recent last, so we can iterate down that.
|
||||
const timeline = arrayFastClone(this.room.getLiveTimeline().getEvents());
|
||||
let scanForVoiceMessage = false;
|
||||
let nextEv: MatrixEvent;
|
||||
let nextEv: MatrixEvent | undefined;
|
||||
for (const event of timeline) {
|
||||
if (event.getId() === mxEvent.getId()) {
|
||||
scanForVoiceMessage = true;
|
||||
|
@ -149,8 +149,8 @@ export class PlaybackQueue {
|
|||
break; // Stop automatic playback: next useful event is not a voice message
|
||||
}
|
||||
|
||||
const havePlayback = this.playbacks.has(event.getId());
|
||||
const isRecentlyCompleted = this.recentFullPlays.has(event.getId());
|
||||
const havePlayback = this.playbacks.has(event.getId()!);
|
||||
const isRecentlyCompleted = this.recentFullPlays.has(event.getId()!);
|
||||
if (havePlayback && !isRecentlyCompleted) {
|
||||
nextEv = event;
|
||||
break;
|
||||
|
@ -164,7 +164,7 @@ export class PlaybackQueue {
|
|||
} else {
|
||||
this.playbackIdOrder = orderClone;
|
||||
|
||||
const instance = this.playbacks.get(nextEv.getId());
|
||||
const instance = this.playbacks.get(nextEv.getId()!);
|
||||
PlaybackManager.instance.pauseAllExcept(instance);
|
||||
|
||||
// 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) {
|
||||
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.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 amplitudeIndex = 0;
|
||||
|
||||
public process(inputs, outputs, parameters): boolean {
|
||||
public process(
|
||||
inputs: Float32Array[][],
|
||||
outputs: Float32Array[][],
|
||||
parameters: Record<string, Float32Array>,
|
||||
): boolean {
|
||||
const currentSecond = roundTimeToTargetFreq(currentTime);
|
||||
// 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
|
||||
|
|
|
@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import Recorder from "opus-recorder/dist/recorder.min.js";
|
||||
import encoderPath from "opus-recorder/dist/encoderWorker.min.js";
|
||||
import { SimpleObservable } from "matrix-widget-api";
|
||||
|
@ -78,7 +77,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
|
|||
private targetMaxLength: number | null = TARGET_MAX_LENGTH;
|
||||
public amplitudes: number[] = []; // at each second mark, generated
|
||||
private liveWaveform = new FixedRollingArray(RECORDING_PLAYBACK_SAMPLES, 0);
|
||||
public onDataAvailable: (data: ArrayBuffer) => void;
|
||||
public onDataAvailable?: (data: ArrayBuffer) => void;
|
||||
|
||||
public get contentType(): string {
|
||||
return "audio/ogg";
|
||||
|
@ -182,7 +181,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
|
|||
});
|
||||
|
||||
// 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) {
|
||||
logger.error("Error starting recording: ", e);
|
||||
if (e instanceof DOMException) {
|
||||
|
|
|
@ -69,7 +69,7 @@ export function decodeOgg(audioBuffer: ArrayBuffer): Promise<ArrayBuffer> {
|
|||
command: "encode",
|
||||
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
|
||||
* @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;
|
||||
|
||||
if (force && this.shouldForceComplete()) {
|
||||
|
@ -78,7 +78,7 @@ export default abstract class AutocompleteProvider {
|
|||
}
|
||||
|
||||
if (!commandRegex) {
|
||||
return null;
|
||||
return {};
|
||||
}
|
||||
|
||||
commandRegex.lastIndex = 0;
|
||||
|
|
|
@ -95,7 +95,7 @@ export default class CommandProvider extends AutocompleteProvider {
|
|||
description={_t(result.description)}
|
||||
/>
|
||||
),
|
||||
range,
|
||||
range: range!,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { uniq, sortBy } from "lodash";
|
||||
import { uniq, sortBy, ListIteratee } from "lodash";
|
||||
import EMOTICON_REGEX from "emojibase-regex/emoticon";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
|
@ -55,7 +55,11 @@ const SORTED_EMOJI: ISortedEmoji[] = EMOJI.sort((a, b) => {
|
|||
_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);
|
||||
if (index === -1) {
|
||||
return Infinity;
|
||||
|
@ -90,7 +94,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
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(
|
||||
|
@ -113,7 +117,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
// Do second match with shouldMatchWordsOnly in order to match against 'name'
|
||||
completions = completions.concat(this.nameMatcher.match(matchedString));
|
||||
|
||||
let sorters = [];
|
||||
let sorters: ListIteratee<ISortedEmoji>[] = [];
|
||||
// make sure that emoticons come first
|
||||
sorters.push((c) => score(matchedString, c.emoji.emoticon || ""));
|
||||
|
||||
|
@ -148,7 +152,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
<span>{c.emoji.unicode}</span>
|
||||
</PillCompletion>
|
||||
),
|
||||
range,
|
||||
range: range!,
|
||||
}));
|
||||
}
|
||||
return [];
|
||||
|
|
|
@ -40,12 +40,13 @@ export default class NotifProvider extends AutocompleteProvider {
|
|||
): Promise<ICompletion[]> {
|
||||
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);
|
||||
if (
|
||||
command?.[0].length > 1 &&
|
||||
["@room", "@channel", "@everyone", "@here"].some((c) => c.startsWith(command[0]))
|
||||
command?.[0] &&
|
||||
command[0].length > 1 &&
|
||||
["@room", "@channel", "@everyone", "@here"].some((c) => c.startsWith(command![0]))
|
||||
) {
|
||||
return [
|
||||
{
|
||||
|
@ -58,7 +59,7 @@ export default class NotifProvider extends AutocompleteProvider {
|
|||
<RoomAvatar width={24} height={24} room={this.room} />
|
||||
</PillCompletion>
|
||||
),
|
||||
range,
|
||||
range: range!,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ export default class QueryMatcher<T extends {}> {
|
|||
if (!this._items.has(key)) {
|
||||
this._items.set(key, []);
|
||||
}
|
||||
this._items.get(key).push({
|
||||
this._items.get(key)!.push({
|
||||
keyWeight: Number(index),
|
||||
object,
|
||||
});
|
||||
|
@ -104,7 +104,11 @@ export default class QueryMatcher<T extends {}> {
|
|||
if (query.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const matches = [];
|
||||
const matches: {
|
||||
index: number;
|
||||
object: T;
|
||||
keyWeight: number;
|
||||
}[] = [];
|
||||
// Iterate through the map & check each key.
|
||||
// ES6 Map iteration order is defined to be insertion order, so results
|
||||
// here will come out in the order they were put in.
|
||||
|
|
|
@ -39,12 +39,12 @@ function canonicalScore(displayedAlias: string, room: Room): number {
|
|||
|
||||
function matcherObject(
|
||||
room: Room,
|
||||
displayedAlias: string | null,
|
||||
displayedAlias: string,
|
||||
matchName = "",
|
||||
): {
|
||||
room: Room;
|
||||
matchName: string;
|
||||
displayedAlias: string | null;
|
||||
displayedAlias: string;
|
||||
} {
|
||||
return {
|
||||
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
|
||||
let matcherObjects = this.getRooms().reduce<ReturnType<typeof matcherObject>[]>((aliases, room) => {
|
||||
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) {
|
||||
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} />
|
||||
</PillCompletion>
|
||||
),
|
||||
range,
|
||||
range: range!,
|
||||
}),
|
||||
)
|
||||
.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 {
|
||||
public matcher: QueryMatcher<RoomMember>;
|
||||
public users: RoomMember[];
|
||||
public users: RoomMember[] | null;
|
||||
public room: Room;
|
||||
|
||||
public constructor(room: Room, renderingType?: TimelineRenderingType) {
|
||||
|
@ -54,7 +54,7 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
renderingType,
|
||||
});
|
||||
this.room = room;
|
||||
this.matcher = new QueryMatcher([], {
|
||||
this.matcher = new QueryMatcher<RoomMember>([], {
|
||||
keys: ["name"],
|
||||
funcs: [(obj) => obj.userId.slice(1)], // index by user id minus the leading '@'
|
||||
shouldMatchWordsOnly: false,
|
||||
|
@ -73,7 +73,7 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
|
||||
private onRoomTimeline = (
|
||||
ev: MatrixEvent,
|
||||
room: Room | null,
|
||||
room: Room | undefined,
|
||||
toStartOfTimeline: boolean,
|
||||
removed: boolean,
|
||||
data: IRoomTimelineData,
|
||||
|
@ -110,18 +110,15 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
// lazy-load user list into matcher
|
||||
if (!this.users) this.makeUsers();
|
||||
|
||||
let completions = [];
|
||||
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 "@"
|
||||
if (fullMatch && fullMatch !== "@") {
|
||||
// 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;
|
||||
completions = this.matcher.match(query, limit).map((user) => {
|
||||
const description = UserIdentifierCustomisations.getDisplayUserIdentifier(user.userId, {
|
||||
return this.matcher.match(query, limit).map((user) => {
|
||||
const description = UserIdentifierCustomisations.getDisplayUserIdentifier?.(user.userId, {
|
||||
roomId: this.room.roomId,
|
||||
withDisplayName: true,
|
||||
});
|
||||
|
@ -132,18 +129,18 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
completion: user.rawDisplayName,
|
||||
completionId: user.userId,
|
||||
type: "user",
|
||||
suffix: selection.beginning && range.start === 0 ? ": " : " ",
|
||||
suffix: selection.beginning && range!.start === 0 ? ": " : " ",
|
||||
href: makeUserPermalink(user.userId),
|
||||
component: (
|
||||
<PillCompletion title={displayName} description={description}>
|
||||
<MemberAvatar member={user} width={24} height={24} />
|
||||
</PillCompletion>
|
||||
),
|
||||
range,
|
||||
range: range!,
|
||||
};
|
||||
});
|
||||
}
|
||||
return completions;
|
||||
return [];
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
|
@ -152,10 +149,10 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
|
||||
private makeUsers(): void {
|
||||
const events = this.room.getLiveTimeline().getEvents();
|
||||
const lastSpoken = {};
|
||||
const lastSpoken: Record<string, number> = {};
|
||||
|
||||
for (const event of events) {
|
||||
lastSpoken[event.getSender()] = event.getTs();
|
||||
lastSpoken[event.getSender()!] = event.getTs();
|
||||
}
|
||||
|
||||
const currentUserId = MatrixClientPeg.get().credentials.userId;
|
||||
|
@ -167,7 +164,7 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
this.matcher.setObjects(this.users);
|
||||
}
|
||||
|
||||
public onUserSpoke(user: RoomMember): void {
|
||||
public onUserSpoke(user: RoomMember | null): void {
|
||||
if (!this.users) return;
|
||||
if (!user) 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
|
||||
const { element, className, onScroll, tabIndex, wrappedRef, children, ...otherProps } = this.props;
|
||||
|
||||
|
|
|
@ -99,9 +99,9 @@ export interface IProps extends MenuProps {
|
|||
closeOnInteraction?: boolean;
|
||||
|
||||
// Function to be called on menu close
|
||||
onFinished();
|
||||
onFinished(): void;
|
||||
// on resize callback
|
||||
windowResize?();
|
||||
windowResize?(): void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -119,8 +119,8 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
|
|||
managed: true,
|
||||
};
|
||||
|
||||
public constructor(props, context) {
|
||||
super(props, context);
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
contextMenuElem: null,
|
||||
|
@ -387,13 +387,13 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
|
|||
menuStyle["paddingRight"] = menuPaddingRight;
|
||||
}
|
||||
|
||||
const wrapperStyle = {};
|
||||
const wrapperStyle: CSSProperties = {};
|
||||
if (!isNaN(Number(zIndex))) {
|
||||
menuStyle["zIndex"] = zIndex + 1;
|
||||
wrapperStyle["zIndex"] = zIndex;
|
||||
}
|
||||
|
||||
let background;
|
||||
let background: JSX.Element;
|
||||
if (hasBackground) {
|
||||
background = (
|
||||
<div
|
||||
|
@ -624,7 +624,7 @@ export function createMenu(
|
|||
ElementClass: typeof React.Component,
|
||||
props: Record<string, any>,
|
||||
): { close: (...args: any[]) => void } {
|
||||
const onFinished = function (...args): void {
|
||||
const onFinished = function (...args: any[]): void {
|
||||
ReactDOM.unmountComponentAtNode(getOrCreateContainer());
|
||||
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.
|
||||
const client = this.context || MatrixClientPeg.get();
|
||||
const isGuest = client ? client.isGuest() : true;
|
||||
|
|
|
@ -43,7 +43,7 @@ interface IProps {
|
|||
}
|
||||
|
||||
interface IState {
|
||||
timelineSet: EventTimelineSet;
|
||||
timelineSet: EventTimelineSet | null;
|
||||
narrow: boolean;
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ class FilePanel extends React.Component<IProps, IState> {
|
|||
public noRoom: boolean;
|
||||
private card = createRef<HTMLDivElement>();
|
||||
|
||||
public state = {
|
||||
public state: IState = {
|
||||
timelineSet: null,
|
||||
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()) {
|
||||
return (
|
||||
<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> {
|
||||
return "options" in item;
|
||||
}
|
||||
|
@ -123,19 +129,19 @@ export function GenericDropdownMenu<T>({
|
|||
.flatMap((it) => (isGenericDropdownMenuGroup(it) ? [it, ...it.options] : [it]))
|
||||
.find((option) => (toKey ? toKey(option.key) === toKey(value) : option.key === value));
|
||||
let contextMenuOptions: JSX.Element;
|
||||
if (options && isGenericDropdownMenuGroup(options[0])) {
|
||||
if (options && isGenericDropdownMenuGroupArray(options)) {
|
||||
contextMenuOptions = (
|
||||
<>
|
||||
{options.map((group) => (
|
||||
<GenericDropdownMenuGroup
|
||||
key={toKey?.(group.key) ?? group.key}
|
||||
key={toKey?.(group.key) ?? (group.key as Key)}
|
||||
label={group.label}
|
||||
description={group.description}
|
||||
adornment={group.adornment}
|
||||
>
|
||||
{group.options.map((option) => (
|
||||
<GenericDropdownMenuOption
|
||||
key={toKey?.(option.key) ?? option.key}
|
||||
key={toKey?.(option.key) ?? (option.key as Key)}
|
||||
label={option.label}
|
||||
description={option.description}
|
||||
onClick={(ev: ButtonEvent) => {
|
||||
|
@ -156,7 +162,7 @@ export function GenericDropdownMenu<T>({
|
|||
<>
|
||||
{options.map((option) => (
|
||||
<GenericDropdownMenuOption
|
||||
key={toKey?.(option.key) ?? option.key}
|
||||
key={toKey?.(option.key) ?? (option.key as Key)}
|
||||
label={option.label}
|
||||
description={option.description}
|
||||
onClick={(ev: ButtonEvent) => {
|
||||
|
|
|
@ -22,7 +22,7 @@ interface IProps {
|
|||
}
|
||||
|
||||
export default class GenericErrorPage extends React.PureComponent<IProps> {
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<div className="mx_GenericErrorPage">
|
||||
<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
|
||||
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
|
||||
// argument is the stage, second is the phase. Some stages do not have
|
||||
// phases and will be counted as 0 (numeric).
|
||||
onStagePhaseChange?(stage: string, phase: string | number): void;
|
||||
onStagePhaseChange?(stage: AuthType, phase: number): void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -99,7 +99,7 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
|
|||
|
||||
private unmounted = false;
|
||||
|
||||
public constructor(props) {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
@ -249,7 +249,7 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
|
|||
this.authLogic.setEmailSid(sid);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const stage = this.state.authStage;
|
||||
if (!stage) {
|
||||
if (this.state.busy) {
|
||||
|
|
|
@ -68,7 +68,7 @@ interface IState {
|
|||
export default class LeftPanel extends React.Component<IProps, IState> {
|
||||
private listContainerRef = createRef<HTMLDivElement>();
|
||||
private roomListRef = createRef<RoomList>();
|
||||
private focusedElement = null;
|
||||
private focusedElement: Element = null;
|
||||
private isDoingStickyHeaders = false;
|
||||
|
||||
public constructor(props: IProps) {
|
||||
|
|
|
@ -136,8 +136,8 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
protected backgroundImageWatcherRef: string;
|
||||
protected resizer: Resizer;
|
||||
|
||||
public constructor(props, context) {
|
||||
super(props, context);
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
syncErrorData: undefined,
|
||||
|
@ -229,8 +229,8 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private createResizer(): Resizer {
|
||||
let panelSize;
|
||||
let panelCollapsed;
|
||||
let panelSize: number;
|
||||
let panelCollapsed: boolean;
|
||||
const collapseConfig: ICollapseConfig = {
|
||||
// TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel
|
||||
toggleSize: 206 - 50,
|
||||
|
@ -341,7 +341,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
|
||||
if (!serverNoticeList) return;
|
||||
|
||||
const events = [];
|
||||
const events: MatrixEvent[] = [];
|
||||
let pinnedEventTs = 0;
|
||||
for (const room of serverNoticeList) {
|
||||
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"
|
||||
);
|
||||
});
|
||||
const usageLimitEventContent = usageLimitEvent && usageLimitEvent.getContent();
|
||||
const usageLimitEventContent = usageLimitEvent?.getContent<IUsageLimit>();
|
||||
this.calculateServerLimitToast(this.state.syncErrorData, usageLimitEventContent);
|
||||
this.setState({
|
||||
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.
|
||||
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
|
||||
// of this component, so something must be focused.
|
||||
this.onKeyDown(ev);
|
||||
};
|
||||
|
||||
private onNativeKeyDown = (ev): void => {
|
||||
private onNativeKeyDown = (ev: KeyboardEvent): void => {
|
||||
// only pass this if there is no focused element.
|
||||
// if there is, onKeyDown will be called by the
|
||||
// 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;
|
||||
|
||||
const roomAction = getKeyBindingsManager().getRoomAction(ev);
|
||||
|
@ -571,7 +571,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
) {
|
||||
dis.dispatch<SwitchSpacePayload>({
|
||||
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;
|
||||
}
|
||||
|
@ -615,13 +615,11 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
* dispatch a page-up/page-down/etc to the appropriate component
|
||||
* @param {Object} ev The key event
|
||||
*/
|
||||
private onScrollKeyPressed = (ev): void => {
|
||||
if (this._roomView.current) {
|
||||
this._roomView.current.handleScrollKey(ev);
|
||||
}
|
||||
private onScrollKeyPressed = (ev: React.KeyboardEvent | KeyboardEvent): void => {
|
||||
this._roomView.current?.handleScrollKey(ev);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
let pageElement;
|
||||
|
||||
switch (this.props.page_type) {
|
||||
|
|
|
@ -47,7 +47,7 @@ export default class MainSplit extends React.Component<IProps> {
|
|||
};
|
||||
|
||||
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)) {
|
||||
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 panelView = this.props.panel;
|
||||
|
||||
|
|
|
@ -419,7 +419,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
window.addEventListener("resize", this.onWindowResized);
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps, prevState): void {
|
||||
public componentDidUpdate(prevProps: IProps, prevState: IState): void {
|
||||
if (this.shouldTrackPageChange(prevState, this.state)) {
|
||||
const durationMs = this.stopPageChangeTimer();
|
||||
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) {
|
||||
throw new Error("setStateForNewView with no view!");
|
||||
}
|
||||
const newState = {
|
||||
currentUserId: null,
|
||||
this.setState({
|
||||
currentUserId: undefined,
|
||||
justRegistered: false,
|
||||
};
|
||||
Object.assign(newState, state);
|
||||
this.setState(newState);
|
||||
...state,
|
||||
} as IState);
|
||||
}
|
||||
|
||||
private onAction = (payload: ActionPayload): void => {
|
||||
|
@ -2022,7 +2021,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
return fragmentAfterLogin;
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const fragmentAfterLogin = this.getFragmentAfterLogin();
|
||||
let view = null;
|
||||
|
||||
|
|
|
@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
|
|||
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 classNames from "classnames";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
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 { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||
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 { Layout } from "../../settings/enums/Layout";
|
||||
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 IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
|
||||
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.
|
||||
public grouperKeyMap = new WeakMap<MatrixEvent, string>();
|
||||
|
||||
public constructor(props, context) {
|
||||
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
|
@ -308,7 +308,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
SettingsStore.unwatchSetting(this.showTypingNotificationsWatcherRef);
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps, prevState): void {
|
||||
public componentDidUpdate(prevProps: IProps, prevState: IState): void {
|
||||
if (prevProps.layout !== this.props.layout) {
|
||||
this.calculateRoomMembersCount();
|
||||
}
|
||||
|
@ -410,17 +410,13 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
/* jump to the top of the content.
|
||||
*/
|
||||
public scrollToTop(): void {
|
||||
if (this.scrollPanel.current) {
|
||||
this.scrollPanel.current.scrollToTop();
|
||||
}
|
||||
this.scrollPanel.current?.scrollToTop();
|
||||
}
|
||||
|
||||
/* jump to the bottom of the content.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public handleScrollKey(ev: KeyboardEvent): void {
|
||||
if (this.scrollPanel.current) {
|
||||
this.scrollPanel.current.handleScrollKey(ev);
|
||||
}
|
||||
public handleScrollKey(ev: React.KeyboardEvent | KeyboardEvent): void {
|
||||
this.scrollPanel.current?.handleScrollKey(ev);
|
||||
}
|
||||
|
||||
/* jump to the given event id.
|
||||
|
@ -752,7 +746,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
const readReceipts = this.readReceiptsByEvent[eventId];
|
||||
|
||||
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 hasNextEvent = nextEvent && this.shouldShowEvent(nextEvent);
|
||||
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,
|
||||
// they are folded into the receipts of the last shown event.
|
||||
private getReadReceiptsByShownEvent(): Record<string, IReadReceiptProps[]> {
|
||||
const receiptsByEvent = {};
|
||||
const receiptsByUserId = {};
|
||||
const receiptsByEvent: Record<string, IReadReceiptProps[]> = {};
|
||||
const receiptsByUserId: Record<
|
||||
string,
|
||||
{
|
||||
lastShownEventId: string;
|
||||
receipt: IReadReceiptProps;
|
||||
}
|
||||
> = {};
|
||||
|
||||
let lastShownEventId;
|
||||
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 bottomSpinner;
|
||||
if (this.props.backPaginating) {
|
||||
|
|
|
@ -27,8 +27,8 @@ interface IState {
|
|||
}
|
||||
|
||||
export default class NonUrgentToastContainer extends React.PureComponent<IProps, IState> {
|
||||
public constructor(props, context) {
|
||||
super(props, context);
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
toasts: NonUrgentToastStore.instance.components,
|
||||
|
@ -45,7 +45,7 @@ export default class NonUrgentToastContainer extends React.PureComponent<IProps,
|
|||
this.setState({ toasts: NonUrgentToastStore.instance.components });
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const toasts = this.state.toasts.map((t, i) => {
|
||||
return (
|
||||
<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>();
|
||||
|
||||
public constructor(props) {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
@ -55,7 +55,7 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
|
|||
this.setState({ narrow });
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const emptyState = (
|
||||
<div className="mx_RightPanel_empty mx_NotificationPanel_empty">
|
||||
<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 = {
|
||||
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.
|
||||
*/
|
||||
|
||||
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 { logger } from "matrix-js-sdk/src/logger";
|
||||
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;
|
||||
let pipContent: Array<CreatePipChildren> = [];
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
|||
public static contextType = MatrixClientContext;
|
||||
public context!: React.ContextType<typeof MatrixClientContext>;
|
||||
|
||||
public constructor(props, context) {
|
||||
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
|
@ -149,7 +149,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
|||
this.setState({ searchQuery });
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
let card = <div />;
|
||||
const roomId = this.props.room?.roomId;
|
||||
const phase = this.props.overwriteCard?.phase ?? this.state.phase;
|
||||
|
|
|
@ -111,11 +111,11 @@ export const RoomSearchView = forwardRef<ScrollPanel, Props>(
|
|||
);
|
||||
if (!bundledRelationship || event.getThread()) continue;
|
||||
const room = client.getRoom(event.getRoomId());
|
||||
const thread = room.findThreadForEvent(event);
|
||||
const thread = room?.findThreadForEvent(event);
|
||||
if (thread) {
|
||||
event.setThread(thread);
|
||||
} 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();
|
||||
};
|
||||
|
||||
let lastRoomId: string;
|
||||
let lastRoomId: string | undefined;
|
||||
let mergedTimeline: MatrixEvent[] = [];
|
||||
let ourEventsIndexes: number[] = [];
|
||||
|
||||
|
|
|
@ -14,10 +14,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { SyncState, ISyncStateData } from "matrix-js-sdk/src/sync";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { MatrixError } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { _t, _td } from "../../languageHandler";
|
||||
import Resend from "../../Resend";
|
||||
|
@ -192,10 +193,10 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
|
|||
private getUnsentMessageContent(): JSX.Element {
|
||||
const unsentMessages = this.state.unsentMessages;
|
||||
|
||||
let title;
|
||||
let title: ReactNode;
|
||||
|
||||
let consentError = null;
|
||||
let resourceLimitError = null;
|
||||
let consentError: MatrixError | null = null;
|
||||
let resourceLimitError: MatrixError | null = null;
|
||||
for (const m of unsentMessages) {
|
||||
if (m.error && m.error.errcode === "M_CONSENT_NOT_GIVEN") {
|
||||
consentError = m.error;
|
||||
|
@ -212,7 +213,7 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
|
|||
{},
|
||||
{
|
||||
consentLink: (sub) => (
|
||||
<a href={consentError.data && consentError.data.consent_uri} target="_blank">
|
||||
<a href={consentError!.data?.consent_uri} target="_blank">
|
||||
{sub}
|
||||
</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()) {
|
||||
return (
|
||||
<div className="mx_RoomStatusBar">
|
||||
|
|
|
@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactElement } from "react";
|
||||
import React, { ReactElement, ReactNode } from "react";
|
||||
|
||||
import { StaticNotificationState } from "../../stores/notifications/StaticNotificationState";
|
||||
import NotificationBadge from "../views/rooms/NotificationBadge";
|
||||
|
||||
interface RoomStatusBarUnsentMessagesProps {
|
||||
title: string;
|
||||
title: ReactNode;
|
||||
description?: string;
|
||||
notificationState: StaticNotificationState;
|
||||
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 { HistoryVisibility } from "matrix-js-sdk/src/@types/partials";
|
||||
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 { _t } from "../../languageHandler";
|
||||
|
@ -49,7 +50,7 @@ import RoomScrollStateStore, { ScrollState } from "../../stores/RoomScrollStateS
|
|||
import WidgetEchoStore from "../../stores/WidgetEchoStore";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
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 { E2EStatus, shieldStatusForRoom } from "../../utils/ShieldUtils";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
|
@ -223,6 +224,7 @@ export interface IRoomState {
|
|||
narrow: boolean;
|
||||
// List of undecryptable events currently visible on-screen
|
||||
visibleDecryptionFailures?: MatrixEvent[];
|
||||
msc3946ProcessDynamicPredecessor: boolean;
|
||||
}
|
||||
|
||||
interface LocalRoomViewProps {
|
||||
|
@ -416,6 +418,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
liveTimeline: undefined,
|
||||
narrow: false,
|
||||
visibleDecryptionFailures: [],
|
||||
msc3946ProcessDynamicPredecessor: SettingsStore.getValue("feature_dynamic_room_predecessors"),
|
||||
};
|
||||
|
||||
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_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);
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps, nextState): boolean {
|
||||
public shouldComponentUpdate(nextProps: IRoomProps, nextState: IRoomState): boolean {
|
||||
const hasPropsDiff = objectHasDiff(this.props, nextProps);
|
||||
|
||||
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) {
|
||||
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") {
|
||||
|
@ -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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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:
|
||||
// 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:
|
||||
// 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
|
||||
private forgetReadMarker = (ev): void => {
|
||||
private forgetReadMarker = (ev: ButtonEvent): void => {
|
||||
ev.stopPropagation();
|
||||
this.messagePanel.forgetReadMarker();
|
||||
};
|
||||
|
@ -1770,7 +1782,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
*
|
||||
* We pass it down to the scroll panel.
|
||||
*/
|
||||
public handleScrollKey = (ev): void => {
|
||||
public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => {
|
||||
let panel: ScrollPanel | TimelinePanel;
|
||||
if (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,
|
||||
// otherwise react calls it with null on each update.
|
||||
private gatherTimelinePanelRef = (r): void => {
|
||||
private gatherTimelinePanelRef = (r?: TimelinePanel): void => {
|
||||
this.messagePanel = r;
|
||||
};
|
||||
|
||||
private getOldRoom(): Room | null {
|
||||
const createEvent = this.state.room.currentState.getStateEvents(EventType.RoomCreate, "");
|
||||
if (!createEvent || !createEvent.getContent()["predecessor"]) return null;
|
||||
|
||||
return this.context.client.getRoom(createEvent.getContent()["predecessor"]["room_id"]);
|
||||
const { roomId } = this.state.room?.findPredecessor(this.state.msc3946ProcessDynamicPredecessor) || {};
|
||||
return this.context.client?.getRoom(roomId) || null;
|
||||
}
|
||||
|
||||
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.state === LocalRoomState.CREATING) {
|
||||
return this.renderLocalRoomCreateLoader();
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
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 SettingsStore from "../../settings/SettingsStore";
|
||||
|
@ -195,8 +195,8 @@ export default class ScrollPanel extends React.Component<IProps> {
|
|||
private heightUpdateInProgress: boolean;
|
||||
private divScroll: HTMLDivElement;
|
||||
|
||||
public constructor(props, context) {
|
||||
super(props, context);
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.props.resizeNotifier?.on("middlePanelResizedNoisy", this.onResize);
|
||||
|
||||
|
@ -440,9 +440,9 @@ export default class ScrollPanel extends React.Component<IProps> {
|
|||
// pagination.
|
||||
//
|
||||
// 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++) {
|
||||
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
|
||||
excessHeight -= tile.clientHeight;
|
||||
//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
|
||||
* @param {object} ev the keyboard event
|
||||
*/
|
||||
public handleScrollKey = (ev: KeyboardEvent): void => {
|
||||
public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => {
|
||||
const roomAction = getKeyBindingsManager().getRoomAction(ev);
|
||||
switch (roomAction) {
|
||||
case KeyBindingAction.ScrollUp:
|
||||
|
|
|
@ -24,7 +24,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
|||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
|
||||
interface IProps extends HTMLProps<HTMLInputElement> {
|
||||
onSearch?: (query: string) => void;
|
||||
onSearch: (query: string) => void;
|
||||
onCleared?: (source?: string) => void;
|
||||
onKeyDown?: (ev: React.KeyboardEvent) => void;
|
||||
onFocus?: (ev: React.FocusEvent) => void;
|
||||
|
@ -62,7 +62,7 @@ export default class SearchBox extends React.Component<IProps, IState> {
|
|||
|
||||
private onSearch = throttle(
|
||||
(): void => {
|
||||
this.props.onSearch(this.search.current.value);
|
||||
this.props.onSearch(this.search.current?.value);
|
||||
},
|
||||
200,
|
||||
{ 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 }] */
|
||||
const {
|
||||
onSearch,
|
||||
|
|
|
@ -280,7 +280,7 @@ const Tile: React.FC<ITileProps> = ({
|
|||
);
|
||||
|
||||
if (showChildren) {
|
||||
const onChildrenKeyDown = (e): void => {
|
||||
const onChildrenKeyDown = (e: React.KeyboardEvent): void => {
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
switch (action) {
|
||||
case KeyBindingAction.ArrowLeft:
|
||||
|
|
|
@ -318,7 +318,7 @@ const SpaceSetupFirstRooms: React.FC<{
|
|||
label={_t("Room name")}
|
||||
placeholder={placeholders[i]}
|
||||
value={roomNames[i]}
|
||||
onChange={(ev) => setRoomName(i, ev.target.value)}
|
||||
onChange={(ev: React.ChangeEvent<HTMLInputElement>) => setRoomName(i, ev.target.value)}
|
||||
autoFocus={i === 2}
|
||||
disabled={busy}
|
||||
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 =
|
||||
this.state.showRightPanel && this.state.phase === Phase.Landing ? (
|
||||
<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) {
|
||||
this.setupThread(this.props.mxEvent);
|
||||
}
|
||||
|
@ -316,7 +316,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private get threadRelation(): IEventRelation {
|
||||
const relation = {
|
||||
const relation: IEventRelation = {
|
||||
rel_type: THREAD_RELATION_TYPE.name,
|
||||
event_id: this.state.thread?.id,
|
||||
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 threadRelation = this.threadRelation;
|
||||
|
|
|
@ -1316,7 +1316,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
*
|
||||
* 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;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
// just show a spinner while the timeline loads.
|
||||
//
|
||||
// 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
|
||||
*/
|
||||
function serializeEventIdsFromTimelineSets(timelineSets): { [key: string]: string[] }[] {
|
||||
function serializeEventIdsFromTimelineSets(timelineSets: EventTimelineSet[]): { [key: string]: string[] }[] {
|
||||
const serializedEventIdsInTimelineSet = timelineSets.map((timelineSet) => {
|
||||
const timelineMap = {};
|
||||
const timelineMap: Record<string, string[]> = {};
|
||||
|
||||
const timelines = timelineSet.getTimelines();
|
||||
const liveTimeline = timelineSet.getLiveTimeline();
|
||||
|
|
|
@ -25,8 +25,8 @@ interface IState {
|
|||
}
|
||||
|
||||
export default class ToastContainer extends React.Component<{}, IState> {
|
||||
public constructor(props, context) {
|
||||
super(props, context);
|
||||
public constructor(props: {}) {
|
||||
super(props);
|
||||
this.state = {
|
||||
toasts: ToastStore.sharedInstance().getToasts(),
|
||||
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 isStacked = totalCount > 1;
|
||||
let toast;
|
||||
|
|
|
@ -57,7 +57,7 @@ export default class UploadBar extends React.PureComponent<IProps, IState> {
|
|||
private dispatcherRef: Optional<string>;
|
||||
private mounted = false;
|
||||
|
||||
public constructor(props) {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
// 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!);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
if (!this.state.currentFile) {
|
||||
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 userId = MatrixClientPeg.get().getUserId();
|
||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import Modal from "../../Modal";
|
||||
|
@ -31,7 +32,7 @@ import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases
|
|||
import { UserOnboardingPage } from "../views/user-onboarding/UserOnboardingPage";
|
||||
|
||||
interface IProps {
|
||||
userId?: string;
|
||||
userId: string;
|
||||
resizeNotifier: ResizeNotifier;
|
||||
}
|
||||
|
||||
|
@ -66,7 +67,7 @@ export default class UserView extends React.Component<IProps, IState> {
|
|||
private async loadProfileInfo(): Promise<void> {
|
||||
const cli = MatrixClientPeg.get();
|
||||
this.setState({ loading: true });
|
||||
let profileInfo;
|
||||
let profileInfo: Awaited<ReturnType<MatrixClient["getProfileInfo"]>>;
|
||||
try {
|
||||
profileInfo = await cli.getProfileInfo(this.props.userId);
|
||||
} catch (err) {
|
||||
|
@ -83,7 +84,7 @@ export default class UserView extends React.Component<IProps, IState> {
|
|||
this.setState({ member, loading: false });
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
if (this.state.loading) {
|
||||
return <Spinner />;
|
||||
} 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);
|
||||
}
|
||||
|
||||
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 isEditing = this.state.isEditing;
|
||||
|
|
|
@ -57,7 +57,7 @@ export default class CompleteSecurity extends React.Component<IProps, IState> {
|
|||
store.stop();
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const { phase, lostKeys } = this.state;
|
||||
let icon;
|
||||
let title;
|
||||
|
|
|
@ -27,7 +27,7 @@ interface IProps {
|
|||
}
|
||||
|
||||
export default class E2eSetup extends React.Component<IProps> {
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<AuthPage>
|
||||
<CompleteSecurityBody>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue