Merge branch 'develop' into johannes/find-myself

This commit is contained in:
Johannes Marbach 2023-02-13 20:16:04 +01:00 committed by GitHub
commit d0e9331f07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
612 changed files with 3608 additions and 2769 deletions

View file

@ -54,12 +54,12 @@ describe("Polls", () => {
}; };
const getPollOption = (pollId: string, optionText: string): Chainable<JQuery> => { const getPollOption = (pollId: string, optionText: string): Chainable<JQuery> => {
return getPollTile(pollId).contains(".mx_MPollBody_option .mx_StyledRadioButton", optionText); return getPollTile(pollId).contains(".mx_PollOption .mx_StyledRadioButton", optionText);
}; };
const expectPollOptionVoteCount = (pollId: string, optionText: string, votes: number): void => { const expectPollOptionVoteCount = (pollId: string, optionText: string, votes: number): void => {
getPollOption(pollId, optionText).within(() => { getPollOption(pollId, optionText).within(() => {
cy.get(".mx_MPollBody_optionVoteCount").should("contain", `${votes} vote`); cy.get(".mx_PollOption_optionVoteCount").should("contain", `${votes} vote`);
}); });
}; };

View file

@ -154,11 +154,13 @@
"@types/flux": "^3.1.9", "@types/flux": "^3.1.9",
"@types/fs-extra": "^11.0.0", "@types/fs-extra": "^11.0.0",
"@types/geojson": "^7946.0.8", "@types/geojson": "^7946.0.8",
"@types/glob-to-regexp": "^0.4.1",
"@types/jest": "^29.2.1", "@types/jest": "^29.2.1",
"@types/katex": "^0.14.0", "@types/katex": "^0.14.0",
"@types/lodash": "^4.14.168", "@types/lodash": "^4.14.168",
"@types/modernizr": "^3.5.3", "@types/modernizr": "^3.5.3",
"@types/node": "^16", "@types/node": "^16",
"@types/node-fetch": "^2.6.2",
"@types/pako": "^2.0.0", "@types/pako": "^2.0.0",
"@types/parse5": "^6.0.0", "@types/parse5": "^6.0.0",
"@types/qrcode": "^1.3.5", "@types/qrcode": "^1.3.5",
@ -168,6 +170,7 @@
"@types/react-test-renderer": "^17.0.1", "@types/react-test-renderer": "^17.0.1",
"@types/react-transition-group": "^4.4.0", "@types/react-transition-group": "^4.4.0",
"@types/sanitize-html": "^2.3.1", "@types/sanitize-html": "^2.3.1",
"@types/tar-js": "^0.3.2",
"@types/ua-parser-js": "^0.7.36", "@types/ua-parser-js": "^0.7.36",
"@types/zxcvbn": "^4.4.0", "@types/zxcvbn": "^4.4.0",
"@typescript-eslint/eslint-plugin": "^5.35.1", "@typescript-eslint/eslint-plugin": "^5.35.1",

View file

@ -19,6 +19,7 @@
@import "./components/views/context_menus/_KebabContextMenu.pcss"; @import "./components/views/context_menus/_KebabContextMenu.pcss";
@import "./components/views/dialogs/polls/_PollListItem.pcss"; @import "./components/views/dialogs/polls/_PollListItem.pcss";
@import "./components/views/elements/_FilterDropdown.pcss"; @import "./components/views/elements/_FilterDropdown.pcss";
@import "./components/views/elements/_FilterTabGroup.pcss";
@import "./components/views/elements/_LearnMore.pcss"; @import "./components/views/elements/_LearnMore.pcss";
@import "./components/views/location/_EnableLiveShare.pcss"; @import "./components/views/location/_EnableLiveShare.pcss";
@import "./components/views/location/_LiveDurationDropdown.pcss"; @import "./components/views/location/_LiveDurationDropdown.pcss";
@ -32,6 +33,7 @@
@import "./components/views/messages/_MBeaconBody.pcss"; @import "./components/views/messages/_MBeaconBody.pcss";
@import "./components/views/messages/shared/_MediaProcessingError.pcss"; @import "./components/views/messages/shared/_MediaProcessingError.pcss";
@import "./components/views/pips/_WidgetPip.pcss"; @import "./components/views/pips/_WidgetPip.pcss";
@import "./components/views/polls/_PollOption.pcss";
@import "./components/views/settings/devices/_CurrentDeviceSection.pcss"; @import "./components/views/settings/devices/_CurrentDeviceSection.pcss";
@import "./components/views/settings/devices/_DeviceDetailHeading.pcss"; @import "./components/views/settings/devices/_DeviceDetailHeading.pcss";
@import "./components/views/settings/devices/_DeviceDetails.pcss"; @import "./components/views/settings/devices/_DeviceDetails.pcss";

View 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;
}
}
}

View 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;
}
}

View file

@ -115,6 +115,7 @@ limitations under the License.
padding-left: 30px; /* 18px for the icon, 2px margin to text, 10px regular padding */ padding-left: 30px; /* 18px for the icon, 2px margin to text, 10px regular padding */
display: inline-block; display: inline-block;
position: relative; position: relative;
user-select: none;
&:nth-child(2) { &:nth-child(2) {
border-left: 1px solid $resend-button-divider-color; border-left: 1px solid $resend-button-divider-color;

View file

@ -47,108 +47,6 @@ limitations under the License.
mask-image: url("$(res)/img/element-icons/room/composer/poll.svg"); mask-image: url("$(res)/img/element-icons/room/composer/poll.svg");
} }
.mx_MPollBody_option {
border: 1px solid $quinary-content;
border-radius: 8px;
margin-bottom: 16px;
padding: 6px 12px;
max-width: 550px;
background-color: $background;
.mx_StyledRadioButton,
.mx_MPollBody_endedOption {
margin-bottom: 8px;
}
.mx_StyledRadioButton_content,
.mx_MPollBody_endedOption {
padding-top: 2px;
margin-right: 0px;
}
.mx_StyledRadioButton_spacer {
display: none;
}
.mx_MPollBody_optionDescription {
display: flex;
justify-content: space-between;
.mx_MPollBody_optionVoteCount {
color: $secondary-content;
font-size: $font-12px;
white-space: nowrap;
}
}
.mx_MPollBody_popularityBackground {
width: 100%;
height: 8px;
margin-right: 12px;
border-radius: 8px;
background-color: $system;
.mx_MPollBody_popularityAmount {
width: 0%;
height: 8px;
border-radius: 8px;
background-color: $quaternary-content;
}
}
}
.mx_MPollBody_option:last-child {
margin-bottom: 8px;
}
.mx_MPollBody_option_checked {
border-color: $accent;
.mx_MPollBody_popularityBackground {
.mx_MPollBody_popularityAmount {
background-color: $accent;
}
}
}
/* options not actionable in these states */
.mx_MPollBody_option_checked,
.mx_MPollBody_option_ended {
pointer-events: none;
}
.mx_StyledRadioButton_checked,
.mx_MPollBody_endedOptionWinner {
input[type="radio"] + div {
border-width: 2px;
border-color: $accent;
background-color: $accent;
background-image: url("$(res)/img/element-icons/check-white.svg");
background-size: 12px;
background-repeat: no-repeat;
background-position: center;
div {
visibility: hidden;
}
}
}
.mx_MPollBody_endedOptionWinner .mx_MPollBody_optionDescription .mx_MPollBody_optionVoteCount::before {
content: "";
position: relative;
display: inline-block;
margin-right: 4px;
top: 2px;
height: 12px;
width: 12px;
background-color: $accent;
mask-repeat: no-repeat;
mask-size: contain;
mask-position: center;
mask-image: url("$(res)/img/element-icons/trophy.svg");
}
.mx_MPollBody_totalVotes { .mx_MPollBody_totalVotes {
display: flex; display: flex;
flex-direction: inline; flex-direction: inline;
@ -168,9 +66,8 @@ limitations under the License.
pointer-events: none; pointer-events: none;
} }
.mx_MPollBody_option, .mx_MPollBody_allOptions {
/* label has cursor: default in user-agent stylesheet */ display: grid;
/* override */ grid-gap: $spacing-16;
.mx_MPollBody_live-option { margin-bottom: $spacing-8;
cursor: pointer;
} }

View file

@ -1,3 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.6667 1.33333H9.33333V0.666667C9.33333 0.3 9.03333 0 8.66667 0H3.33333C2.96667 0 2.66667 0.3 2.66667 0.666667V1.33333H1.33333C0.6 1.33333 0 1.93333 0 2.66667V3.33333C0 5.03333 1.28 6.42 2.92667 6.62667C3.34667 7.62667 4.24667 8.38 5.33333 8.6V10.6667H3.33333C2.96667 10.6667 2.66667 10.9667 2.66667 11.3333C2.66667 11.7 2.96667 12 3.33333 12H8.66667C9.03333 12 9.33333 11.7 9.33333 11.3333C9.33333 10.9667 9.03333 10.6667 8.66667 10.6667H6.66667V8.6C7.75333 8.38 8.65333 7.62667 9.07333 6.62667C10.72 6.42 12 5.03333 12 3.33333V2.66667C12 1.93333 11.4 1.33333 10.6667 1.33333ZM1.33333 3.33333V2.66667H2.66667V5.21333C1.89333 4.93333 1.33333 4.2 1.33333 3.33333ZM10.6667 3.33333C10.6667 4.2 10.1067 4.93333 9.33333 5.21333V2.66667H10.6667V3.33333Z" fill="#0DBD8B"/> <path d="M10.6667 1.33333H9.33333V0.666667C9.33333 0.3 9.03333 0 8.66667 0H3.33333C2.96667 0 2.66667 0.3 2.66667 0.666667V1.33333H1.33333C0.6 1.33333 0 1.93333 0 2.66667V3.33333C0 5.03333 1.28 6.42 2.92667 6.62667C3.34667 7.62667 4.24667 8.38 5.33333 8.6V10.6667H3.33333C2.96667 10.6667 2.66667 10.9667 2.66667 11.3333C2.66667 11.7 2.96667 12 3.33333 12H8.66667C9.03333 12 9.33333 11.7 9.33333 11.3333C9.33333 10.9667 9.03333 10.6667 8.66667 10.6667H6.66667V8.6C7.75333 8.38 8.65333 7.62667 9.07333 6.62667C10.72 6.42 12 5.03333 12 3.33333V2.66667C12 1.93333 11.4 1.33333 10.6667 1.33333ZM1.33333 3.33333V2.66667H2.66667V5.21333C1.89333 4.93333 1.33333 4.2 1.33333 3.33333ZM10.6667 3.33333C10.6667 4.2 10.1067 4.93333 9.33333 5.21333V2.66667H10.6667V3.33333Z" fill="currentColor"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 880 B

After

Width:  |  Height:  |  Size: 885 B

View file

@ -218,7 +218,7 @@ declare global {
processorCtor: (new (options?: AudioWorkletNodeOptions) => AudioWorkletProcessor) & { processorCtor: (new (options?: AudioWorkletNodeOptions) => AudioWorkletProcessor) & {
parameterDescriptors?: AudioParamDescriptor[]; parameterDescriptors?: AudioParamDescriptor[];
}, },
); ): void;
// eslint-disable-next-line no-var // eslint-disable-next-line no-var
var grecaptcha: var grecaptcha:

65
src/@types/opus-recorder.d.ts vendored Normal file
View 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;
}

View file

@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix"; import { IAuthData, IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "./MatrixClientPeg"; import { MatrixClientPeg } from "./MatrixClientPeg";
import Modal from "./Modal"; import Modal from "./Modal";
@ -29,6 +29,12 @@ function getIdServerDomain(): string {
return MatrixClientPeg.get().idBaseUrl.split("://")[1]; return MatrixClientPeg.get().idBaseUrl.split("://")[1];
} }
export type Binding = {
bind: boolean;
label: string;
errorTitle: string;
};
/** /**
* Allows a user to add a third party identifier to their homeserver and, * Allows a user to add a third party identifier to their homeserver and,
* optionally, the identity servers. * optionally, the identity servers.
@ -178,7 +184,7 @@ export default class AddThreepid {
* with a "message" property which contains a human-readable message detailing why * with a "message" property which contains a human-readable message detailing why
* the request failed. * the request failed.
*/ */
public async checkEmailLinkClicked(): Promise<any[]> { public async checkEmailLinkClicked(): Promise<[boolean, IAuthData | Error | null]> {
try { try {
if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) { if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) {
if (this.bind) { if (this.bind) {
@ -220,7 +226,9 @@ export default class AddThreepid {
continueKind: "primary", continueKind: "primary",
}, },
}; };
const { finished } = Modal.createDialog(InteractiveAuthDialog, { const { finished } = Modal.createDialog<[boolean, IAuthData | Error | null]>(
InteractiveAuthDialog,
{
title: _t("Add Email Address"), title: _t("Add Email Address"),
matrixClient: MatrixClientPeg.get(), matrixClient: MatrixClientPeg.get(),
authData: e.data, authData: e.data,
@ -229,7 +237,8 @@ export default class AddThreepid {
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
}, },
}); },
);
return finished; return finished;
} }
} }

View file

@ -42,10 +42,7 @@ interface IState {
export default class AsyncWrapper extends React.Component<IProps, IState> { export default class AsyncWrapper extends React.Component<IProps, IState> {
private unmounted = false; private unmounted = false;
public state = { public state: IState = {};
component: null,
error: null,
};
public componentDidMount(): void { public componentDidMount(): void {
// XXX: temporary logging to try to diagnose // XXX: temporary logging to try to diagnose
@ -77,7 +74,7 @@ export default class AsyncWrapper extends React.Component<IProps, IState> {
this.props.onFinished(false); this.props.onFinished(false);
}; };
public render(): JSX.Element { public render(): React.ReactNode {
if (this.state.component) { if (this.state.component) {
const Component = this.state.component; const Component = this.state.component;
return <Component {...this.props} />; return <Component {...this.props} />;

View file

@ -138,7 +138,7 @@ export function getInitialLetter(name: string): string | undefined {
} }
export function avatarUrlForRoom( export function avatarUrlForRoom(
room: Room, room: Room | null,
width: number, width: number,
height: number, height: number,
resizeMethod?: ResizeMethod, resizeMethod?: ResizeMethod,

View file

@ -197,7 +197,7 @@ export default abstract class BasePlatform {
room: Room, room: Room,
ev?: MatrixEvent, ev?: MatrixEvent,
): Notification { ): Notification {
const notifBody = { const notifBody: NotificationOptions = {
body: msg, body: msg,
silent: true, // we play our own sounds silent: true, // we play our own sounds
}; };

View file

@ -204,7 +204,7 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = {
attribs.style += "height: 100%;"; attribs.style += "height: 100%;";
} }
attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height); attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height)!;
return { tagName, attribs }; return { tagName, attribs };
}, },
"code": function (tagName: string, attribs: sanitizeHtml.Attributes) { "code": function (tagName: string, attribs: sanitizeHtml.Attributes) {
@ -228,7 +228,7 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = {
// Sanitise and transform data-mx-color and data-mx-bg-color to their CSS // Sanitise and transform data-mx-color and data-mx-bg-color to their CSS
// equivalents // equivalents
const customCSSMapper = { const customCSSMapper: Record<string, string> = {
"data-mx-color": "color", "data-mx-color": "color",
"data-mx-bg-color": "background-color", "data-mx-bg-color": "background-color",
// $customAttributeKey: $cssAttributeKey // $customAttributeKey: $cssAttributeKey
@ -352,7 +352,7 @@ const topicSanitizeHtmlParams: IExtendedSanitizeOptions = {
}; };
abstract class BaseHighlighter<T extends React.ReactNode> { abstract class BaseHighlighter<T extends React.ReactNode> {
public constructor(public highlightClass: string, public highlightLink: string) {} public constructor(public highlightClass: string, public highlightLink?: string) {}
/** /**
* apply the highlights to a section of text * apply the highlights to a section of text
@ -504,7 +504,7 @@ function formatEmojis(message: string, isHtmlMessage: boolean): (JSX.Element | s
export function bodyToHtml(content: IContent, highlights: Optional<string[]>, opts: IOptsReturnString): string; export function bodyToHtml(content: IContent, highlights: Optional<string[]>, opts: IOptsReturnString): string;
export function bodyToHtml(content: IContent, highlights: Optional<string[]>, opts: IOptsReturnNode): ReactNode; export function bodyToHtml(content: IContent, highlights: Optional<string[]>, opts: IOptsReturnNode): ReactNode;
export function bodyToHtml(content: IContent, highlights: Optional<string[]>, opts: IOpts = {}): ReactNode | string { export function bodyToHtml(content: IContent, highlights: Optional<string[]>, opts: IOpts = {}): ReactNode | string {
const isFormattedBody = content.format === "org.matrix.custom.html" && !!content.formatted_body; const isFormattedBody = content.format === "org.matrix.custom.html" && typeof content.formatted_body === "string";
let bodyHasEmoji = false; let bodyHasEmoji = false;
let isHtmlMessage = false; let isHtmlMessage = false;
@ -514,7 +514,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
} }
let strippedBody: string; let strippedBody: string;
let safeBody: string; // safe, sanitised HTML, preferred over `strippedBody` which is fully plaintext let safeBody: string | undefined; // safe, sanitised HTML, preferred over `strippedBody` which is fully plaintext
try { try {
// sanitizeHtml can hang if an unclosed HTML tag is thrown at it // sanitizeHtml can hang if an unclosed HTML tag is thrown at it
@ -529,7 +529,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
if (opts.stripReplyFallback && formattedBody) formattedBody = stripHTMLReply(formattedBody); if (opts.stripReplyFallback && formattedBody) formattedBody = stripHTMLReply(formattedBody);
strippedBody = opts.stripReplyFallback ? stripPlainReply(plainBody) : plainBody; strippedBody = opts.stripReplyFallback ? stripPlainReply(plainBody) : plainBody;
bodyHasEmoji = mightContainEmoji(isFormattedBody ? formattedBody : plainBody); bodyHasEmoji = mightContainEmoji(isFormattedBody ? formattedBody! : plainBody);
const highlighter = safeHighlights?.length const highlighter = safeHighlights?.length
? new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink) ? new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink)
@ -543,11 +543,11 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
// by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either // by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeParams structure. // XXX: hacky bodge to temporarily apply a textFilter to the sanitizeParams structure.
sanitizeParams.textFilter = function (safeText) { sanitizeParams.textFilter = function (safeText) {
return highlighter.applyHighlights(safeText, safeHighlights).join(""); return highlighter.applyHighlights(safeText, safeHighlights!).join("");
}; };
} }
safeBody = sanitizeHtml(formattedBody, sanitizeParams); safeBody = sanitizeHtml(formattedBody!, sanitizeParams);
const phtml = cheerio.load(safeBody, { const phtml = cheerio.load(safeBody, {
// @ts-ignore: The `_useHtmlParser2` internal option is the // @ts-ignore: The `_useHtmlParser2` internal option is the
// simplest way to both parse and render using `htmlparser2`. // simplest way to both parse and render using `htmlparser2`.
@ -574,7 +574,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
safeBody = formatEmojis(safeBody, true).join(""); safeBody = formatEmojis(safeBody, true).join("");
} }
} else if (highlighter) { } else if (highlighter) {
safeBody = highlighter.applyHighlights(plainBody, safeHighlights).join(""); safeBody = highlighter.applyHighlights(plainBody, safeHighlights!).join("");
} }
} finally { } finally {
delete sanitizeParams.textFilter; delete sanitizeParams.textFilter;
@ -597,9 +597,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
const match = BIGEMOJI_REGEX.exec(contentBodyTrimmed); const match = BIGEMOJI_REGEX.exec(contentBodyTrimmed);
emojiBody = emojiBody =
match && match?.[0]?.length === contentBodyTrimmed.length &&
match[0] &&
match[0].length === contentBodyTrimmed.length &&
// Prevent user pills expanding for users with only emoji in // Prevent user pills expanding for users with only emoji in
// their username. Permalinks (links in pills) can be any URL // their username. Permalinks (links in pills) can be any URL
// now, so we just check for an HTTP-looking thing. // now, so we just check for an HTTP-looking thing.
@ -614,7 +612,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
"markdown-body": isHtmlMessage && !emojiBody, "markdown-body": isHtmlMessage && !emojiBody,
}); });
let emojiBodyElements: JSX.Element[]; let emojiBodyElements: JSX.Element[] | undefined;
if (!safeBody && bodyHasEmoji) { if (!safeBody && bodyHasEmoji) {
emojiBodyElements = formatEmojis(strippedBody, false) as JSX.Element[]; emojiBodyElements = formatEmojis(strippedBody, false) as JSX.Element[];
} }
@ -649,7 +647,7 @@ export function topicToHtml(
allowExtendedHtml = false, allowExtendedHtml = false,
): ReactNode { ): ReactNode {
if (!SettingsStore.getValue("feature_html_topic")) { if (!SettingsStore.getValue("feature_html_topic")) {
htmlTopic = null; htmlTopic = undefined;
} }
let isFormattedTopic = !!htmlTopic; let isFormattedTopic = !!htmlTopic;
@ -657,10 +655,10 @@ export function topicToHtml(
let safeTopic = ""; let safeTopic = "";
try { try {
topicHasEmoji = mightContainEmoji(isFormattedTopic ? htmlTopic : topic); topicHasEmoji = mightContainEmoji(isFormattedTopic ? htmlTopic! : topic);
if (isFormattedTopic) { if (isFormattedTopic) {
safeTopic = sanitizeHtml(htmlTopic, allowExtendedHtml ? sanitizeHtmlParams : topicSanitizeHtmlParams); safeTopic = sanitizeHtml(htmlTopic!, allowExtendedHtml ? sanitizeHtmlParams : topicSanitizeHtmlParams);
if (topicHasEmoji) { if (topicHasEmoji) {
safeTopic = formatEmojis(safeTopic, true).join(""); safeTopic = formatEmojis(safeTopic, true).join("");
} }
@ -669,7 +667,7 @@ export function topicToHtml(
isFormattedTopic = false; // Fall back to plain-text topic isFormattedTopic = false; // Fall back to plain-text topic
} }
let emojiBodyElements: ReturnType<typeof formatEmojis>; let emojiBodyElements: ReturnType<typeof formatEmojis> | undefined;
if (!isFormattedTopic && topicHasEmoji) { if (!isFormattedTopic && topicHasEmoji) {
emojiBodyElements = formatEmojis(topic, false); emojiBodyElements = formatEmojis(topic, false);
} }

View file

@ -169,10 +169,18 @@ export interface IConfigOptions {
inline?: { inline?: {
left?: string; left?: string;
right?: string; right?: string;
pattern?: {
tex?: string;
latex?: string;
};
}; };
display?: { display?: {
left?: string; left?: string;
right?: string; right?: string;
pattern?: {
tex?: string;
latex?: string;
};
}; };
}; };

View file

@ -16,6 +16,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react";
export const Key = { export const Key = {
HOME: "Home", HOME: "Home",
END: "End", END: "End",
@ -76,7 +78,7 @@ export const Key = {
export const IS_MAC = navigator.platform.toUpperCase().includes("MAC"); export const IS_MAC = navigator.platform.toUpperCase().includes("MAC");
export function isOnlyCtrlOrCmdKeyEvent(ev: KeyboardEvent): boolean { export function isOnlyCtrlOrCmdKeyEvent(ev: React.KeyboardEvent | KeyboardEvent): boolean {
if (IS_MAC) { if (IS_MAC) {
return ev.metaKey && !ev.altKey && !ev.ctrlKey && !ev.shiftKey; return ev.metaKey && !ev.altKey && !ev.ctrlKey && !ev.shiftKey;
} else { } else {

View file

@ -158,9 +158,9 @@ export default class LegacyCallHandler extends EventEmitter {
private transferees = new Map<string, MatrixCall>(); // callId (target) -> call (transferee) private transferees = new Map<string, MatrixCall>(); // callId (target) -> call (transferee)
private audioPromises = new Map<AudioID, Promise<void>>(); private audioPromises = new Map<AudioID, Promise<void>>();
private audioElementsWithListeners = new Map<HTMLMediaElement, boolean>(); private audioElementsWithListeners = new Map<HTMLMediaElement, boolean>();
private supportsPstnProtocol = null; private supportsPstnProtocol: boolean | null = null;
private pstnSupportPrefixed = null; // True if the server only support the prefixed pstn protocol private pstnSupportPrefixed: boolean | null = null; // True if the server only support the prefixed pstn protocol
private supportsSipNativeVirtual = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native private supportsSipNativeVirtual: boolean | null = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native
// Map of the asserted identity users after we've looked them up using the API. // Map of the asserted identity users after we've looked them up using the API.
// We need to be be able to determine the mapped room synchronously, so we // We need to be be able to determine the mapped room synchronously, so we
@ -187,7 +187,7 @@ export default class LegacyCallHandler extends EventEmitter {
// check asserted identity: if we're not obeying asserted identity, // check asserted identity: if we're not obeying asserted identity,
// this map will never be populated, but we check anyway for sanity // this map will never be populated, but we check anyway for sanity
if (this.shouldObeyAssertedfIdentity()) { if (this.shouldObeyAssertedfIdentity()) {
const nativeUser = this.assertedIdentityNativeUsers[call.callId]; const nativeUser = this.assertedIdentityNativeUsers.get(call.callId);
if (nativeUser) { if (nativeUser) {
const room = findDMForUser(MatrixClientPeg.get(), nativeUser); const room = findDMForUser(MatrixClientPeg.get(), nativeUser);
if (room) return room.roomId; if (room) return room.roomId;
@ -466,8 +466,8 @@ export default class LegacyCallHandler extends EventEmitter {
return this.getAllActiveCallsNotInRoom(roomId); return this.getAllActiveCallsNotInRoom(roomId);
} }
public getTransfereeForCallId(callId: string): MatrixCall { public getTransfereeForCallId(callId: string): MatrixCall | undefined {
return this.transferees[callId]; return this.transferees.get(callId);
} }
public play(audioId: AudioID): void { public play(audioId: AudioID): void {
@ -621,7 +621,7 @@ export default class LegacyCallHandler extends EventEmitter {
logger.log(`Asserted identity ${newAssertedIdentity} mapped to ${newNativeAssertedIdentity}`); logger.log(`Asserted identity ${newAssertedIdentity} mapped to ${newNativeAssertedIdentity}`);
if (newNativeAssertedIdentity) { if (newNativeAssertedIdentity) {
this.assertedIdentityNativeUsers[call.callId] = newNativeAssertedIdentity; this.assertedIdentityNativeUsers.set(call.callId, newNativeAssertedIdentity);
// If we don't already have a room with this user, make one. This will be slightly odd // If we don't already have a room with this user, make one. This will be slightly odd
// if they called us because we'll be inviting them, but there's not much we can do about // if they called us because we'll be inviting them, but there's not much we can do about
@ -917,7 +917,7 @@ export default class LegacyCallHandler extends EventEmitter {
return; return;
} }
if (transferee) { if (transferee) {
this.transferees[call.callId] = transferee; this.transferees.set(call.callId, transferee);
} }
this.setCallListeners(call); this.setCallListeners(call);

View file

@ -91,12 +91,12 @@ export default class Login {
} }
public loginViaPassword( public loginViaPassword(
username: string, username: string | undefined,
phoneCountry: string, phoneCountry: string | undefined,
phoneNumber: string, phoneNumber: string | undefined,
password: string, password: string,
): Promise<IMatrixClientCreds> { ): Promise<IMatrixClientCreds> {
const isEmail = username.indexOf("@") > 0; const isEmail = username?.indexOf("@") > 0;
let identifier; let identifier;
if (phoneCountry && phoneNumber) { if (phoneCountry && phoneNumber) {

View file

@ -139,7 +139,7 @@ export default class Markdown {
*/ */
private repairLinks(parsed: commonmark.Node): commonmark.Node { private repairLinks(parsed: commonmark.Node): commonmark.Node {
const walker = parsed.walker(); const walker = parsed.walker();
let event: commonmark.NodeWalkingStep = null; let event: commonmark.NodeWalkingStep | null = null;
let text = ""; let text = "";
let isInPara = false; let isInPara = false;
let previousNode: commonmark.Node | null = null; let previousNode: commonmark.Node | null = null;
@ -287,7 +287,7 @@ export default class Markdown {
// However, if it's a blockquote, adds a p tag anyway // However, if it's a blockquote, adds a p tag anyway
// in order to avoid deviation to commonmark and unexpected // in order to avoid deviation to commonmark and unexpected
// results when parsing the formatted HTML. // results when parsing the formatted HTML.
if (node.parent.type === "block_quote" || isMultiLine(node)) { if (node.parent?.type === "block_quote" || isMultiLine(node)) {
realParagraph.call(this, node, entering); realParagraph.call(this, node, entering);
} }
}; };

View file

@ -37,7 +37,7 @@ export enum MediaDeviceHandlerEvent {
} }
export default class MediaDeviceHandler extends EventEmitter { export default class MediaDeviceHandler extends EventEmitter {
private static internalInstance; private static internalInstance?: MediaDeviceHandler;
public static get instance(): MediaDeviceHandler { public static get instance(): MediaDeviceHandler {
if (!MediaDeviceHandler.internalInstance) { if (!MediaDeviceHandler.internalInstance) {
@ -67,7 +67,7 @@ export default class MediaDeviceHandler extends EventEmitter {
public static async getDevices(): Promise<IMediaDevices> { public static async getDevices(): Promise<IMediaDevices> {
try { try {
const devices = await navigator.mediaDevices.enumerateDevices(); const devices = await navigator.mediaDevices.enumerateDevices();
const output = { const output: Record<MediaDeviceKindEnum, MediaDeviceInfo[]> = {
[MediaDeviceKindEnum.AudioOutput]: [], [MediaDeviceKindEnum.AudioOutput]: [],
[MediaDeviceKindEnum.AudioInput]: [], [MediaDeviceKindEnum.AudioInput]: [],
[MediaDeviceKindEnum.VideoInput]: [], [MediaDeviceKindEnum.VideoInput]: [],

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { ReactInstance } from "react";
import ReactDom from "react-dom"; import ReactDom from "react-dom";
interface IChildProps { interface IChildProps {
@ -41,7 +41,7 @@ interface IProps {
* automatic positional animation, look at react-shuffle or similar libraries. * automatic positional animation, look at react-shuffle or similar libraries.
*/ */
export default class NodeAnimator extends React.Component<IProps> { export default class NodeAnimator extends React.Component<IProps> {
private nodes = {}; private nodes: Record<string, ReactInstance> = {};
private children: { [key: string]: React.DetailedReactHTMLElement<any, HTMLElement> }; private children: { [key: string]: React.DetailedReactHTMLElement<any, HTMLElement> };
public static defaultProps: Partial<IProps> = { public static defaultProps: Partial<IProps> = {
startStyles: [], startStyles: [],
@ -65,7 +65,7 @@ export default class NodeAnimator extends React.Component<IProps> {
*/ */
private applyStyles(node: HTMLElement, styles: React.CSSProperties): void { private applyStyles(node: HTMLElement, styles: React.CSSProperties): void {
Object.entries(styles).forEach(([property, value]) => { Object.entries(styles).forEach(([property, value]) => {
node.style[property] = value; node.style[property as keyof Omit<CSSStyleDeclaration, "length" | "parentRule">] = value;
}); });
} }
@ -120,7 +120,7 @@ export default class NodeAnimator extends React.Component<IProps> {
this.nodes[k] = node; this.nodes[k] = node;
} }
public render(): JSX.Element { public render(): React.ReactNode {
return <>{Object.values(this.children)}</>; return <>{Object.values(this.children)}</>;
} }
} }

View file

@ -68,7 +68,7 @@ Override both the content body and the TextForEvent handler for specific msgtype
This is useful when the content body contains fallback text that would explain that the client can't handle a particular This is useful when the content body contains fallback text that would explain that the client can't handle a particular
type of tile. type of tile.
*/ */
const msgTypeHandlers = { const msgTypeHandlers: Record<string, (event: MatrixEvent) => string> = {
[MsgType.KeyVerificationRequest]: (event: MatrixEvent) => { [MsgType.KeyVerificationRequest]: (event: MatrixEvent) => {
const name = (event.sender || {}).name; const name = (event.sender || {}).name;
return _t("%(name)s is requesting verification", { name }); return _t("%(name)s is requesting verification", { name });
@ -95,22 +95,26 @@ const msgTypeHandlers = {
}, },
}; };
export const Notifier = { class NotifierClass {
notifsByRoom: {}, private notifsByRoom: Record<string, Notification[]> = {};
// A list of event IDs that we've received but need to wait until // A list of event IDs that we've received but need to wait until
// they're decrypted until we decide whether to notify for them // they're decrypted until we decide whether to notify for them
// or not // or not
pendingEncryptedEventIds: [], private pendingEncryptedEventIds: string[] = [];
notificationMessageForEvent: function (ev: MatrixEvent): string { private toolbarHidden?: boolean;
private isSyncing?: boolean;
public notificationMessageForEvent(ev: MatrixEvent): string {
if (msgTypeHandlers.hasOwnProperty(ev.getContent().msgtype)) { if (msgTypeHandlers.hasOwnProperty(ev.getContent().msgtype)) {
return msgTypeHandlers[ev.getContent().msgtype](ev); return msgTypeHandlers[ev.getContent().msgtype](ev);
} }
return TextForEvent.textForEvent(ev); return TextForEvent.textForEvent(ev);
}, }
_displayPopupNotification: function (ev: MatrixEvent, room: Room): void { // XXX: exported for tests
public displayPopupNotification(ev: MatrixEvent, room: Room): void {
const plaf = PlatformPeg.get(); const plaf = PlatformPeg.get();
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
if (!plaf) { if (!plaf) {
@ -165,9 +169,14 @@ export const Notifier = {
if (this.notifsByRoom[ev.getRoomId()] === undefined) this.notifsByRoom[ev.getRoomId()] = []; if (this.notifsByRoom[ev.getRoomId()] === undefined) this.notifsByRoom[ev.getRoomId()] = [];
this.notifsByRoom[ev.getRoomId()].push(notif); this.notifsByRoom[ev.getRoomId()].push(notif);
} }
}, }
getSoundForRoom: function (roomId: string) { public getSoundForRoom(roomId: string): {
url: string;
name: string;
type: string;
size: string;
} | null {
// We do no caching here because the SDK caches setting // We do no caching here because the SDK caches setting
// and the browser will cache the sound. // and the browser will cache the sound.
const content = SettingsStore.getValue("notificationSound", roomId); const content = SettingsStore.getValue("notificationSound", roomId);
@ -193,9 +202,10 @@ export const Notifier = {
type: content.type, type: content.type,
size: content.size, size: content.size,
}; };
}, }
_playAudioNotification: async function (ev: MatrixEvent, room: Room): Promise<void> { // XXX: Exported for tests
public async playAudioNotification(ev: MatrixEvent, room: Room): Promise<void> {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
if (localNotificationsAreSilenced(cli)) { if (localNotificationsAreSilenced(cli)) {
return; return;
@ -224,39 +234,32 @@ export const Notifier = {
} catch (ex) { } catch (ex) {
logger.warn("Caught error when trying to fetch room notification sound:", ex); logger.warn("Caught error when trying to fetch room notification sound:", ex);
} }
}, }
start: function (this: typeof Notifier) { public start(): void {
// do not re-bind in the case of repeated call MatrixClientPeg.get().on(RoomEvent.Timeline, this.onEvent);
this.boundOnEvent = this.boundOnEvent || this.onEvent.bind(this); MatrixClientPeg.get().on(RoomEvent.Receipt, this.onRoomReceipt);
this.boundOnSyncStateChange = this.boundOnSyncStateChange || this.onSyncStateChange.bind(this); MatrixClientPeg.get().on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
this.boundOnRoomReceipt = this.boundOnRoomReceipt || this.onRoomReceipt.bind(this); MatrixClientPeg.get().on(ClientEvent.Sync, this.onSyncStateChange);
this.boundOnEventDecrypted = this.boundOnEventDecrypted || this.onEventDecrypted.bind(this);
MatrixClientPeg.get().on(RoomEvent.Timeline, this.boundOnEvent);
MatrixClientPeg.get().on(RoomEvent.Receipt, this.boundOnRoomReceipt);
MatrixClientPeg.get().on(MatrixEventEvent.Decrypted, this.boundOnEventDecrypted);
MatrixClientPeg.get().on(ClientEvent.Sync, this.boundOnSyncStateChange);
this.toolbarHidden = false; this.toolbarHidden = false;
this.isSyncing = false; this.isSyncing = false;
}, }
stop: function (this: typeof Notifier) { public stop(): void {
if (MatrixClientPeg.get()) { if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener(RoomEvent.Timeline, this.boundOnEvent); MatrixClientPeg.get().removeListener(RoomEvent.Timeline, this.onEvent);
MatrixClientPeg.get().removeListener(RoomEvent.Receipt, this.boundOnRoomReceipt); MatrixClientPeg.get().removeListener(RoomEvent.Receipt, this.onRoomReceipt);
MatrixClientPeg.get().removeListener(MatrixEventEvent.Decrypted, this.boundOnEventDecrypted); MatrixClientPeg.get().removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted);
MatrixClientPeg.get().removeListener(ClientEvent.Sync, this.boundOnSyncStateChange); MatrixClientPeg.get().removeListener(ClientEvent.Sync, this.onSyncStateChange);
} }
this.isSyncing = false; this.isSyncing = false;
}, }
supportsDesktopNotifications: function () { public supportsDesktopNotifications(): boolean {
const plaf = PlatformPeg.get(); return PlatformPeg.get()?.supportsNotifications() ?? false;
return plaf && plaf.supportsNotifications(); }
},
setEnabled: function (enable: boolean, callback?: () => void) { public setEnabled(enable: boolean, callback?: () => void): void {
const plaf = PlatformPeg.get(); const plaf = PlatformPeg.get();
if (!plaf) return; if (!plaf) return;
@ -320,31 +323,30 @@ export const Notifier = {
// set the notifications_hidden flag, as the user has knowingly interacted // set the notifications_hidden flag, as the user has knowingly interacted
// with the setting we shouldn't nag them any further // with the setting we shouldn't nag them any further
this.setPromptHidden(true); this.setPromptHidden(true);
}, }
isEnabled: function () { public isEnabled(): boolean {
return this.isPossible() && SettingsStore.getValue("notificationsEnabled"); return this.isPossible() && SettingsStore.getValue("notificationsEnabled");
}, }
isPossible: function () { public isPossible(): boolean {
const plaf = PlatformPeg.get(); const plaf = PlatformPeg.get();
if (!plaf) return false; if (!plaf?.supportsNotifications()) return false;
if (!plaf.supportsNotifications()) return false;
if (!plaf.maySendNotifications()) return false; if (!plaf.maySendNotifications()) return false;
return true; // possible, but not necessarily enabled return true; // possible, but not necessarily enabled
}, }
isBodyEnabled: function () { public isBodyEnabled(): boolean {
return this.isEnabled() && SettingsStore.getValue("notificationBodyEnabled"); return this.isEnabled() && SettingsStore.getValue("notificationBodyEnabled");
}, }
isAudioEnabled: function () { public isAudioEnabled(): boolean {
// We don't route Audio via the HTML Notifications API so it is possible regardless of other things // We don't route Audio via the HTML Notifications API so it is possible regardless of other things
return SettingsStore.getValue("audioNotificationsEnabled"); return SettingsStore.getValue("audioNotificationsEnabled");
}, }
setPromptHidden: function (this: typeof Notifier, hidden: boolean, persistent = true) { public setPromptHidden(hidden: boolean, persistent = true): void {
this.toolbarHidden = hidden; this.toolbarHidden = hidden;
hideNotificationsToast(); hideNotificationsToast();
@ -353,9 +355,9 @@ export const Notifier = {
if (persistent && global.localStorage) { if (persistent && global.localStorage) {
global.localStorage.setItem("notifications_hidden", String(hidden)); global.localStorage.setItem("notifications_hidden", String(hidden));
} }
}, }
shouldShowPrompt: function () { public shouldShowPrompt(): boolean {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
return false; return false;
@ -366,25 +368,21 @@ export const Notifier = {
this.supportsDesktopNotifications() && this.supportsDesktopNotifications() &&
!isPushNotifyDisabled() && !isPushNotifyDisabled() &&
!this.isEnabled() && !this.isEnabled() &&
!this._isPromptHidden() !this.isPromptHidden()
); );
}, }
_isPromptHidden: function (this: typeof Notifier) { private isPromptHidden(): boolean {
// Check localStorage for any such meta data // Check localStorage for any such meta data
if (global.localStorage) { if (global.localStorage) {
return global.localStorage.getItem("notifications_hidden") === "true"; return global.localStorage.getItem("notifications_hidden") === "true";
} }
return this.toolbarHidden; return this.toolbarHidden;
}, }
onSyncStateChange: function ( // XXX: Exported for tests
this: typeof Notifier, public onSyncStateChange = (state: SyncState, prevState?: SyncState, data?: ISyncStateData): void => {
state: SyncState,
prevState?: SyncState,
data?: ISyncStateData,
) {
if (state === SyncState.Syncing) { if (state === SyncState.Syncing) {
this.isSyncing = true; this.isSyncing = true;
} else if (state === SyncState.Stopped || state === SyncState.Error) { } else if (state === SyncState.Stopped || state === SyncState.Error) {
@ -395,16 +393,15 @@ export const Notifier = {
if (![SyncState.Stopped, SyncState.Error].includes(state) && !data?.fromCache) { if (![SyncState.Stopped, SyncState.Error].includes(state) && !data?.fromCache) {
createLocalNotificationSettingsIfNeeded(MatrixClientPeg.get()); createLocalNotificationSettingsIfNeeded(MatrixClientPeg.get());
} }
}, };
onEvent: function ( private onEvent = (
this: typeof Notifier,
ev: MatrixEvent, ev: MatrixEvent,
room: Room | undefined, room: Room | undefined,
toStartOfTimeline: boolean | undefined, toStartOfTimeline: boolean | undefined,
removed: boolean, removed: boolean,
data: IRoomTimelineData, data: IRoomTimelineData,
) { ): void => {
if (!data.liveEvent) return; // only notify for new things, not old. if (!data.liveEvent) return; // only notify for new things, not old.
if (!this.isSyncing) return; // don't alert for any messages initially if (!this.isSyncing) return; // don't alert for any messages initially
if (ev.getSender() === MatrixClientPeg.get().getUserId()) return; if (ev.getSender() === MatrixClientPeg.get().getUserId()) return;
@ -422,10 +419,10 @@ export const Notifier = {
return; return;
} }
this._evaluateEvent(ev); this.evaluateEvent(ev);
}, };
onEventDecrypted: function (ev: MatrixEvent) { private onEventDecrypted = (ev: MatrixEvent): void => {
// 'decrypted' means the decryption process has finished: it may have failed, // 'decrypted' means the decryption process has finished: it may have failed,
// in which case it might decrypt soon if the keys arrive // in which case it might decrypt soon if the keys arrive
if (ev.isDecryptionFailure()) return; if (ev.isDecryptionFailure()) return;
@ -434,10 +431,10 @@ export const Notifier = {
if (idx === -1) return; if (idx === -1) return;
this.pendingEncryptedEventIds.splice(idx, 1); this.pendingEncryptedEventIds.splice(idx, 1);
this._evaluateEvent(ev); this.evaluateEvent(ev);
}, };
onRoomReceipt: function (ev: MatrixEvent, room: Room) { private onRoomReceipt = (ev: MatrixEvent, room: Room): void => {
if (room.getUnreadNotificationCount() === 0) { if (room.getUnreadNotificationCount() === 0) {
// ideally we would clear each notification when it was read, // ideally we would clear each notification when it was read,
// but we have no way, given a read receipt, to know whether // but we have no way, given a read receipt, to know whether
@ -453,12 +450,12 @@ export const Notifier = {
} }
delete this.notifsByRoom[room.roomId]; delete this.notifsByRoom[room.roomId];
} }
}, };
_evaluateEvent: function (ev: MatrixEvent) { // XXX: exported for tests
public evaluateEvent(ev: MatrixEvent): void {
// Mute notifications for broadcast info events // Mute notifications for broadcast info events
if (ev.getType() === VoiceBroadcastInfoEventType) return; if (ev.getType() === VoiceBroadcastInfoEventType) return;
let roomId = ev.getRoomId(); let roomId = ev.getRoomId();
if (LegacyCallHandler.instance.getSupportsVirtualRooms()) { if (LegacyCallHandler.instance.getSupportsVirtualRooms()) {
// Attempt to translate a virtual room to a native one // Attempt to translate a virtual room to a native one
@ -477,7 +474,7 @@ export const Notifier = {
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev); const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
if (actions?.notify) { if (actions?.notify) {
this._performCustomEventHandling(ev); this.performCustomEventHandling(ev);
const store = SdkContextClass.instance.roomViewStore; const store = SdkContextClass.instance.roomViewStore;
const isViewingRoom = store.getRoomId() === room.roomId; const isViewingRoom = store.getRoomId() === room.roomId;
@ -492,19 +489,19 @@ export const Notifier = {
} }
if (this.isEnabled()) { if (this.isEnabled()) {
this._displayPopupNotification(ev, room); this.displayPopupNotification(ev, room);
} }
if (actions.tweaks.sound && this.isAudioEnabled()) { if (actions.tweaks.sound && this.isAudioEnabled()) {
PlatformPeg.get().loudNotification(ev, room); PlatformPeg.get().loudNotification(ev, room);
this._playAudioNotification(ev, room); this.playAudioNotification(ev, room);
}
} }
} }
},
/** /**
* Some events require special handling such as showing in-app toasts * Some events require special handling such as showing in-app toasts
*/ */
_performCustomEventHandling: function (ev: MatrixEvent) { private performCustomEventHandling(ev: MatrixEvent): void {
if (ElementCall.CALL_EVENT_TYPE.names.includes(ev.getType()) && SettingsStore.getValue("feature_group_calls")) { if (ElementCall.CALL_EVENT_TYPE.names.includes(ev.getType()) && SettingsStore.getValue("feature_group_calls")) {
ToastStore.sharedInstance().addOrReplaceToast({ ToastStore.sharedInstance().addOrReplaceToast({
key: getIncomingCallToastKey(ev.getStateKey()), key: getIncomingCallToastKey(ev.getStateKey()),
@ -514,11 +511,12 @@ export const Notifier = {
props: { callEvent: ev }, props: { callEvent: ev },
}); });
} }
}, }
}; }
if (!window.mxNotifier) { if (!window.mxNotifier) {
window.mxNotifier = Notifier; window.mxNotifier = new NotifierClass();
} }
export default window.mxNotifier; export default window.mxNotifier;
export const Notifier: NotifierClass = window.mxNotifier;

View file

@ -132,8 +132,8 @@ export class PosthogAnalytics {
private anonymity = Anonymity.Disabled; private anonymity = Anonymity.Disabled;
// set true during the constructor if posthog config is present, otherwise false // set true during the constructor if posthog config is present, otherwise false
private readonly enabled: boolean = false; private readonly enabled: boolean = false;
private static _instance = null; private static _instance: PosthogAnalytics | null = null;
private platformSuperProperties = {}; private platformSuperProperties: Properties = {};
public static readonly ANALYTICS_EVENT_TYPE = "im.vector.analytics"; public static readonly ANALYTICS_EVENT_TYPE = "im.vector.analytics";
private propertiesForNextEvent: Partial<Record<"$set" | "$set_once", UserProperties>> = {}; private propertiesForNextEvent: Partial<Record<"$set" | "$set_once", UserProperties>> = {};
private userPropertyCache: UserProperties = {}; private userPropertyCache: UserProperties = {};

View file

@ -120,7 +120,7 @@ export class PosthogScreenTracker extends PureComponent<{ screenName: ScreenName
PosthogTrackers.instance.clearOverride(this.props.screenName); PosthogTrackers.instance.clearOverride(this.props.screenName);
} }
public render(): JSX.Element { public render(): React.ReactNode {
return null; // no need to render anything, we just need to hook into the React lifecycle return null; // no need to render anything, we just need to hook into the React lifecycle
} }
} }

View file

@ -33,9 +33,9 @@ enum State {
} }
class Presence { class Presence {
private unavailableTimer: Timer = null; private unavailableTimer: Timer | null = null;
private dispatcherRef: string = null; private dispatcherRef: string | null = null;
private state: State = null; private state: State | null = null;
/** /**
* Start listening the user activity to evaluate his presence state. * Start listening the user activity to evaluate his presence state.
@ -73,14 +73,14 @@ class Presence {
* Get the current presence state. * Get the current presence state.
* @returns {string} the presence state (see PRESENCE enum) * @returns {string} the presence state (see PRESENCE enum)
*/ */
public getState(): State { public getState(): State | null {
return this.state; return this.state;
} }
private onAction = (payload: ActionPayload): void => { private onAction = (payload: ActionPayload): void => {
if (payload.action === "user_activity") { if (payload.action === "user_activity") {
this.setState(State.Online); this.setState(State.Online);
this.unavailableTimer.restart(); this.unavailableTimer?.restart();
} }
}; };

View file

@ -46,7 +46,7 @@ export default class Resend {
} }
public static resend(event: MatrixEvent): Promise<void> { public static resend(event: MatrixEvent): Promise<void> {
const room = MatrixClientPeg.get().getRoom(event.getRoomId()); const room = MatrixClientPeg.get().getRoom(event.getRoomId())!;
return MatrixClientPeg.get() return MatrixClientPeg.get()
.resendEvent(event, room) .resendEvent(event, room)
.then( .then(

View file

@ -30,6 +30,6 @@ export function storeRoomAliasInCache(alias: string, id: string): void {
aliasToIDMap.set(alias, id); aliasToIDMap.set(alias, id);
} }
export function getCachedRoomIDForAlias(alias: string): string { export function getCachedRoomIDForAlias(alias: string): string | undefined {
return aliasToIDMap.get(alias); return aliasToIDMap.get(alias);
} }

View file

@ -112,7 +112,7 @@ export function inviteUsersToRoom(
): Promise<void> { ): Promise<void> {
return inviteMultipleToRoom(roomId, userIds, sendSharedHistoryKeys, progressCallback) return inviteMultipleToRoom(roomId, userIds, sendSharedHistoryKeys, progressCallback)
.then((result) => { .then((result) => {
const room = MatrixClientPeg.get().getRoom(roomId); const room = MatrixClientPeg.get().getRoom(roomId)!;
showAnyInviteErrors(result.states, room, result.inviter); showAnyInviteErrors(result.states, room, result.inviter);
}) })
.catch((err) => { .catch((err) => {
@ -175,14 +175,14 @@ export function showAnyInviteErrors(
<BaseAvatar <BaseAvatar
url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24) : null} url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24) : null}
name={name} name={name}
idName={user.userId} idName={user?.userId}
width={36} width={36}
height={36} height={36}
/> />
</div> </div>
<div className="mx_InviteDialog_tile_nameStack"> <div className="mx_InviteDialog_tile_nameStack">
<span className="mx_InviteDialog_tile_nameStack_name">{name}</span> <span className="mx_InviteDialog_tile_nameStack_name">{name}</span>
<span className="mx_InviteDialog_tile_nameStack_userId">{user.userId}</span> <span className="mx_InviteDialog_tile_nameStack_userId">{user?.userId}</span>
</div> </div>
<div className="mx_InviteDialog_tile--inviterError_errorText"> <div className="mx_InviteDialog_tile--inviterError_errorText">
{inviter.getErrorText(addr)} {inviter.getErrorText(addr)}

View file

@ -46,7 +46,7 @@ export function getRoomNotifsState(client: MatrixClient, roomId: string): RoomNo
} }
// for everything else, look at the room rule. // for everything else, look at the room rule.
let roomRule = null; let roomRule: IPushRule | undefined;
try { try {
roomRule = client.getRoomPushRule("global", roomId); roomRule = client.getRoomPushRule("global", roomId);
} catch (err) { } catch (err) {
@ -106,7 +106,7 @@ export function getUnreadNotificationCount(room: Room, type: NotificationCountTy
function setRoomNotifsStateMuted(roomId: string): Promise<any> { function setRoomNotifsStateMuted(roomId: string): Promise<any> {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const promises = []; const promises: Promise<unknown>[] = [];
// delete the room rule // delete the room rule
const roomRule = cli.getRoomPushRule("global", roomId); const roomRule = cli.getRoomPushRule("global", roomId);
@ -137,7 +137,7 @@ function setRoomNotifsStateMuted(roomId: string): Promise<any> {
function setRoomNotifsStateUnmuted(roomId: string, newState: RoomNotifState): Promise<any> { function setRoomNotifsStateUnmuted(roomId: string, newState: RoomNotifState): Promise<any> {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const promises = []; const promises: Promise<unknown>[] = [];
const overrideMuteRule = findOverrideMuteRule(roomId); const overrideMuteRule = findOverrideMuteRule(roomId);
if (overrideMuteRule) { if (overrideMuteRule) {

View file

@ -29,13 +29,13 @@ import AliasCustomisations from "./customisations/Alias";
* @param {Object} room The room object * @param {Object} room The room object
* @returns {string} A display alias for the given room * @returns {string} A display alias for the given room
*/ */
export function getDisplayAliasForRoom(room: Room): string | undefined { export function getDisplayAliasForRoom(room: Room): string | null {
return getDisplayAliasForAliasSet(room.getCanonicalAlias(), room.getAltAliases()); return getDisplayAliasForAliasSet(room.getCanonicalAlias(), room.getAltAliases());
} }
// The various display alias getters should all feed through this one path so // The various display alias getters should all feed through this one path so
// there's a single place to change the logic. // there's a single place to change the logic.
export function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: string[]): string { export function getDisplayAliasForAliasSet(canonicalAlias: string | null, altAliases: string[]): string | null {
if (AliasCustomisations.getDisplayAliasForAliasSet) { if (AliasCustomisations.getDisplayAliasForAliasSet) {
return AliasCustomisations.getDisplayAliasForAliasSet(canonicalAlias, altAliases); return AliasCustomisations.getDisplayAliasForAliasSet(canonicalAlias, altAliases);
} }
@ -45,7 +45,7 @@ export function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: s
export function guessAndSetDMRoom(room: Room, isDirect: boolean): Promise<void> { export function guessAndSetDMRoom(room: Room, isDirect: boolean): Promise<void> {
let newTarget; let newTarget;
if (isDirect) { if (isDirect) {
const guessedUserId = guessDMRoomTargetId(room, MatrixClientPeg.get().getUserId()); const guessedUserId = guessDMRoomTargetId(room, MatrixClientPeg.get().getUserId()!);
newTarget = guessedUserId; newTarget = guessedUserId;
} else { } else {
newTarget = null; newTarget = null;
@ -118,7 +118,7 @@ function guessDMRoomTargetId(room: Room, myUserId: string): string {
if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) { if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) {
oldestUser = user; oldestUser = user;
oldestTs = user.events.member.getTs(); oldestTs = user.events.member?.getTs();
} }
} }
if (oldestUser) return oldestUser.userId; if (oldestUser) return oldestUser.userId;
@ -129,7 +129,7 @@ function guessDMRoomTargetId(room: Room, myUserId: string): string {
if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) { if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) {
oldestUser = user; oldestUser = user;
oldestTs = user.events.member.getTs(); oldestTs = user.events.member?.getTs();
} }
} }

View file

@ -182,7 +182,7 @@ async function getSecretStorageKey({
export async function getDehydrationKey( export async function getDehydrationKey(
keyInfo: ISecretStorageKeyInfo, keyInfo: ISecretStorageKeyInfo,
checkFunc: (Uint8Array) => void, checkFunc: (data: Uint8Array) => void,
): Promise<Uint8Array> { ): Promise<Uint8Array> {
const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.(); const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.();
if (keyFromCustomisations) { if (keyFromCustomisations) {
@ -196,7 +196,7 @@ export async function getDehydrationKey(
/* props= */ /* props= */
{ {
keyInfo, keyInfo,
checkPrivateKey: async (input): Promise<boolean> => { checkPrivateKey: async (input: KeyParams): Promise<boolean> => {
const key = await inputToKey(input); const key = await inputToKey(input);
try { try {
checkFunc(key); checkFunc(key);
@ -290,7 +290,7 @@ export async function promptForBackupPassphrase(): Promise<Uint8Array> {
RestoreKeyBackupDialog, RestoreKeyBackupDialog,
{ {
showSummary: false, showSummary: false,
keyCallback: (k) => (key = k), keyCallback: (k: Uint8Array) => (key = k),
}, },
null, null,
/* priority = */ false, /* priority = */ false,

View file

@ -697,11 +697,8 @@ export const Commands = [
} }
if (viaServers) { if (viaServers) {
// For the join // For the join, these are passed down to the js-sdk's /join call
dispatch["opts"] = { dispatch["opts"] = { viaServers };
// These are passed down to the js-sdk's /join call
viaServers: viaServers,
};
// For if the join fails (rejoin button) // For if the join fails (rejoin button)
dispatch["via_servers"] = viaServers; dispatch["via_servers"] = viaServers;
@ -1042,7 +1039,7 @@ export const Commands = [
throw newTranslatableError("Session already verified!"); throw newTranslatableError("Session already verified!");
} else { } else {
throw newTranslatableError( throw newTranslatableError(
"WARNING: Session already verified, but keys do NOT MATCH!", "WARNING: session already verified, but keys do NOT MATCH!",
); );
} }
} }

View file

@ -52,11 +52,13 @@ export type Policies = {
[policy: string]: Policy; [policy: string]: Policy;
}; };
export type TermsInteractionCallback = ( export type ServicePolicyPair = {
policiesAndServicePairs: {
service: Service;
policies: Policies; policies: Policies;
}[], service: Service;
};
export type TermsInteractionCallback = (
policiesAndServicePairs: ServicePolicyPair[],
agreedUrls: string[], agreedUrls: string[],
extraClassNames?: string, extraClassNames?: string,
) => Promise<string[]>; ) => Promise<string[]>;
@ -117,9 +119,9 @@ export async function startTermsFlow(
// but then they'd assume they can un-check the boxes to un-agree to a policy, // but then they'd assume they can un-check the boxes to un-agree to a policy,
// but that is not a thing the API supports, so probably best to just show // but that is not a thing the API supports, so probably best to just show
// things they've not agreed to yet. // things they've not agreed to yet.
const unagreedPoliciesAndServicePairs = []; const unagreedPoliciesAndServicePairs: ServicePolicyPair[] = [];
for (const { service, policies } of policiesAndServicePairs) { for (const { service, policies } of policiesAndServicePairs) {
const unagreedPolicies = {}; const unagreedPolicies: Policies = {};
for (const [policyName, policy] of Object.entries(policies)) { for (const [policyName, policy] of Object.entries(policies)) {
let policyAgreed = false; let policyAgreed = false;
for (const lang of Object.keys(policy)) { for (const lang of Object.keys(policy)) {

View file

@ -33,7 +33,7 @@ import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases";
import defaultDispatcher from "./dispatcher/dispatcher"; import defaultDispatcher from "./dispatcher/dispatcher";
import { MatrixClientPeg } from "./MatrixClientPeg"; import { MatrixClientPeg } from "./MatrixClientPeg";
import { ROOM_SECURITY_TAB } from "./components/views/dialogs/RoomSettingsDialog"; import { ROOM_SECURITY_TAB } from "./components/views/dialogs/RoomSettingsDialog";
import AccessibleButton from "./components/views/elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "./components/views/elements/AccessibleButton";
import RightPanelStore from "./stores/right-panel/RightPanelStore"; import RightPanelStore from "./stores/right-panel/RightPanelStore";
import { highlightEvent, isLocationEvent } from "./utils/EventUtils"; import { highlightEvent, isLocationEvent } from "./utils/EventUtils";
import { ElementCall } from "./models/Call"; import { ElementCall } from "./models/Call";
@ -308,7 +308,7 @@ function textForServerACLEvent(ev: MatrixEvent): () => string | null {
allow_ip_literals: prevContent.allow_ip_literals !== false, allow_ip_literals: prevContent.allow_ip_literals !== false,
}; };
let getText = null; let getText: () => string = null;
if (prev.deny.length === 0 && prev.allow.length === 0) { if (prev.deny.length === 0 && prev.allow.length === 0) {
getText = () => _t("%(senderDisplayName)s set the server ACLs for this room.", { senderDisplayName }); getText = () => _t("%(senderDisplayName)s set the server ACLs for this room.", { senderDisplayName });
} else { } else {
@ -360,8 +360,8 @@ function textForCanonicalAliasEvent(ev: MatrixEvent): () => string | null {
const oldAltAliases = ev.getPrevContent().alt_aliases || []; const oldAltAliases = ev.getPrevContent().alt_aliases || [];
const newAlias = ev.getContent().alias; const newAlias = ev.getContent().alias;
const newAltAliases = ev.getContent().alt_aliases || []; const newAltAliases = ev.getContent().alt_aliases || [];
const removedAltAliases = oldAltAliases.filter((alias) => !newAltAliases.includes(alias)); const removedAltAliases = oldAltAliases.filter((alias: string) => !newAltAliases.includes(alias));
const addedAltAliases = newAltAliases.filter((alias) => !oldAltAliases.includes(alias)); const addedAltAliases = newAltAliases.filter((alias: string) => !oldAltAliases.includes(alias));
if (!removedAltAliases.length && !addedAltAliases.length) { if (!removedAltAliases.length && !addedAltAliases.length) {
if (newAlias) { if (newAlias) {
@ -533,8 +533,8 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
const senderName = getSenderName(event); const senderName = getSenderName(event);
const roomId = event.getRoomId(); const roomId = event.getRoomId();
const pinned = event.getContent().pinned ?? []; const pinned = event.getContent<{ pinned: string[] }>().pinned ?? [];
const previouslyPinned = event.getPrevContent().pinned ?? []; const previouslyPinned: string[] = event.getPrevContent().pinned ?? [];
const newlyPinned = pinned.filter((item) => previouslyPinned.indexOf(item) < 0); const newlyPinned = pinned.filter((item) => previouslyPinned.indexOf(item) < 0);
const newlyUnpinned = previouslyPinned.filter((item) => pinned.indexOf(item) < 0); const newlyUnpinned = previouslyPinned.filter((item) => pinned.indexOf(item) < 0);
@ -550,7 +550,10 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
{ senderName }, { senderName },
{ {
a: (sub) => ( a: (sub) => (
<AccessibleButton kind="link_inline" onClick={(e) => highlightEvent(roomId, messageId)}> <AccessibleButton
kind="link_inline"
onClick={(e: ButtonEvent) => highlightEvent(roomId, messageId)}
>
{sub} {sub}
</AccessibleButton> </AccessibleButton>
), ),
@ -580,7 +583,10 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
{ senderName }, { senderName },
{ {
a: (sub) => ( a: (sub) => (
<AccessibleButton kind="link_inline" onClick={(e) => highlightEvent(roomId, messageId)}> <AccessibleButton
kind="link_inline"
onClick={(e: ButtonEvent) => highlightEvent(roomId, messageId)}
>
{sub} {sub}
</AccessibleButton> </AccessibleButton>
), ),

View file

@ -168,7 +168,7 @@ export default class UserActivity {
return this.activeRecentlyTimeout.isRunning(); return this.activeRecentlyTimeout.isRunning();
} }
private onPageVisibilityChanged = (e): void => { private onPageVisibilityChanged = (e: Event): void => {
if (this.document.visibilityState === "hidden") { if (this.document.visibilityState === "hidden") {
this.activeNowTimeout.abort(); this.activeNowTimeout.abort();
this.activeRecentlyTimeout.abort(); this.activeRecentlyTimeout.abort();
@ -182,11 +182,12 @@ export default class UserActivity {
this.activeRecentlyTimeout.abort(); this.activeRecentlyTimeout.abort();
}; };
private onUserActivity = (event: MouseEvent): void => { // XXX: exported for tests
public onUserActivity = (event: Event): void => {
// ignore anything if the window isn't focused // ignore anything if the window isn't focused
if (!this.document.hasFocus()) return; if (!this.document.hasFocus()) return;
if (event.screenX && event.type === "mousemove") { if (event.type === "mousemove" && this.isMouseEvent(event)) {
if (event.screenX === this.lastScreenX && event.screenY === this.lastScreenY) { if (event.screenX === this.lastScreenX && event.screenY === this.lastScreenY) {
// mouse hasn't actually moved // mouse hasn't actually moved
return; return;
@ -223,4 +224,8 @@ export default class UserActivity {
} }
attachedTimers.forEach((t) => t.abort()); attachedTimers.forEach((t) => t.abort());
} }
private isMouseEvent(event: Event): event is MouseEvent {
return event.type.startsWith("mouse");
}
} }

View file

@ -38,7 +38,7 @@ export default class VoipUserMapper {
return window.mxVoipUserMapper; return window.mxVoipUserMapper;
} }
private async userToVirtualUser(userId: string): Promise<string> { private async userToVirtualUser(userId: string): Promise<string | null> {
const results = await LegacyCallHandler.instance.sipVirtualLookup(userId); const results = await LegacyCallHandler.instance.sipVirtualLookup(userId);
if (results.length === 0 || !results[0].fields.lookup_success) return null; if (results.length === 0 || !results[0].fields.lookup_success) return null;
return results[0].userid; return results[0].userid;
@ -59,11 +59,11 @@ export default class VoipUserMapper {
if (!virtualUser) return null; if (!virtualUser) return null;
const virtualRoomId = await ensureVirtualRoomExists(MatrixClientPeg.get(), virtualUser, roomId); const virtualRoomId = await ensureVirtualRoomExists(MatrixClientPeg.get(), virtualUser, roomId);
MatrixClientPeg.get().setRoomAccountData(virtualRoomId, VIRTUAL_ROOM_EVENT_TYPE, { MatrixClientPeg.get().setRoomAccountData(virtualRoomId!, VIRTUAL_ROOM_EVENT_TYPE, {
native_room: roomId, native_room: roomId,
}); });
this.virtualToNativeRoomIdCache.set(virtualRoomId, roomId); this.virtualToNativeRoomIdCache.set(virtualRoomId!, roomId);
return virtualRoomId; return virtualRoomId;
} }
@ -72,9 +72,9 @@ export default class VoipUserMapper {
* Gets the ID of the virtual room for a room, or null if the room has no * Gets the ID of the virtual room for a room, or null if the room has no
* virtual room * virtual room
*/ */
public async getVirtualRoomForRoom(roomId: string): Promise<Room | null> { public async getVirtualRoomForRoom(roomId: string): Promise<Room | undefined> {
const virtualUser = await this.getVirtualUserForRoom(roomId); const virtualUser = await this.getVirtualUserForRoom(roomId);
if (!virtualUser) return null; if (!virtualUser) return undefined;
return findDMForUser(MatrixClientPeg.get(), virtualUser); return findDMForUser(MatrixClientPeg.get(), virtualUser);
} }
@ -121,8 +121,12 @@ export default class VoipUserMapper {
if (!LegacyCallHandler.instance.getSupportsVirtualRooms()) return; if (!LegacyCallHandler.instance.getSupportsVirtualRooms()) return;
const inviterId = invitedRoom.getDMInviter(); const inviterId = invitedRoom.getDMInviter();
if (!inviterId) {
logger.error("Could not find DM inviter for room id: " + invitedRoom.roomId);
}
logger.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`); logger.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
const result = await LegacyCallHandler.instance.sipNativeLookup(inviterId); const result = await LegacyCallHandler.instance.sipNativeLookup(inviterId!);
if (result.length === 0) { if (result.length === 0) {
return; return;
} }
@ -141,7 +145,6 @@ export default class VoipUserMapper {
// (possibly we should only join if we've also joined the native room, then we'd also have // (possibly we should only join if we've also joined the native room, then we'd also have
// to make sure we joined virtual rooms on joining a native one) // to make sure we joined virtual rooms on joining a native one)
MatrixClientPeg.get().joinRoom(invitedRoom.roomId); MatrixClientPeg.get().joinRoom(invitedRoom.roomId);
}
// also put this room in the virtual room ID cache so isVirtualRoom return the right answer // also put this room in the virtual room ID cache so isVirtualRoom return the right answer
// in however long it takes for the echo of setAccountData to come down the sync // in however long it takes for the echo of setAccountData to come down the sync
@ -149,3 +152,4 @@ export default class VoipUserMapper {
} }
} }
} }
}

View file

@ -21,11 +21,11 @@ import { MatrixClientPeg } from "./MatrixClientPeg";
import { _t } from "./languageHandler"; import { _t } from "./languageHandler";
export function usersTypingApartFromMeAndIgnored(room: Room): RoomMember[] { export function usersTypingApartFromMeAndIgnored(room: Room): RoomMember[] {
return usersTyping(room, [MatrixClientPeg.get().getUserId()].concat(MatrixClientPeg.get().getIgnoredUsers())); return usersTyping(room, [MatrixClientPeg.get().getUserId()!].concat(MatrixClientPeg.get().getIgnoredUsers()));
} }
export function usersTypingApartFromMe(room: Room): RoomMember[] { export function usersTypingApartFromMe(room: Room): RoomMember[] {
return usersTyping(room, [MatrixClientPeg.get().getUserId()]); return usersTyping(room, [MatrixClientPeg.get().getUserId()!]);
} }
/** /**
@ -36,7 +36,7 @@ export function usersTypingApartFromMe(room: Room): RoomMember[] {
* @returns {RoomMember[]} list of user objects who are typing. * @returns {RoomMember[]} list of user objects who are typing.
*/ */
export function usersTyping(room: Room, exclude: string[] = []): RoomMember[] { export function usersTyping(room: Room, exclude: string[] = []): RoomMember[] {
const whoIsTyping = []; const whoIsTyping: RoomMember[] = [];
const memberKeys = Object.keys(room.currentState.members); const memberKeys = Object.keys(room.currentState.members);
for (const userId of memberKeys) { for (const userId of memberKeys) {

View file

@ -27,6 +27,7 @@ import {
KEYBOARD_SHORTCUTS, KEYBOARD_SHORTCUTS,
MAC_ONLY_SHORTCUTS, MAC_ONLY_SHORTCUTS,
} from "./KeyboardShortcuts"; } from "./KeyboardShortcuts";
import { IBaseSetting } from "../settings/Settings";
/** /**
* This function gets the keyboard shortcuts that should be presented in the UI * This function gets the keyboard shortcuts that should be presented in the UI
@ -103,7 +104,7 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
return true; return true;
}) })
.reduce((o, key) => { .reduce((o, key) => {
o[key] = KEYBOARD_SHORTCUTS[key]; o[key as KeyBindingAction] = KEYBOARD_SHORTCUTS[key as KeyBindingAction];
return o; return o;
}, {} as IKeyboardShortcuts); }, {} as IKeyboardShortcuts);
}; };
@ -112,7 +113,10 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
* Gets keyboard shortcuts that should be presented to the user in the UI. * Gets keyboard shortcuts that should be presented to the user in the UI.
*/ */
export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => { export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => {
const entries = [...Object.entries(getUIOnlyShortcuts()), ...Object.entries(getKeyboardShortcuts())]; const entries = [...Object.entries(getUIOnlyShortcuts()), ...Object.entries(getKeyboardShortcuts())] as [
KeyBindingAction,
IBaseSetting<KeyCombo>,
][];
return entries.reduce((acc, [key, value]) => { return entries.reduce((acc, [key, value]) => {
acc[key] = value; acc[key] = value;
@ -120,11 +124,11 @@ export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => {
}, {} as IKeyboardShortcuts); }, {} as IKeyboardShortcuts);
}; };
export const getKeyboardShortcutValue = (name: string): KeyCombo | undefined => { export const getKeyboardShortcutValue = (name: KeyBindingAction): KeyCombo | undefined => {
return getKeyboardShortcutsForUI()[name]?.default; return getKeyboardShortcutsForUI()[name]?.default;
}; };
export const getKeyboardShortcutDisplayName = (name: string): string | undefined => { export const getKeyboardShortcutDisplayName = (name: KeyBindingAction): string | undefined => {
const keyboardShortcutDisplayName = getKeyboardShortcutsForUI()[name]?.displayName; const keyboardShortcutDisplayName = getKeyboardShortcutsForUI()[name]?.displayName;
return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName); return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName as string);
}; };

View file

@ -156,10 +156,8 @@ export enum KeyBindingAction {
type KeyboardShortcutSetting = IBaseSetting<KeyCombo>; type KeyboardShortcutSetting = IBaseSetting<KeyCombo>;
export type IKeyboardShortcuts = {
// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager // TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
[k in KeyBindingAction]?: KeyboardShortcutSetting; export type IKeyboardShortcuts = Partial<Record<KeyBindingAction, KeyboardShortcutSetting>>;
};
export interface ICategory { export interface ICategory {
categoryLabel?: string; categoryLabel?: string;

View file

@ -25,6 +25,7 @@ import React, {
Reducer, Reducer,
Dispatch, Dispatch,
RefObject, RefObject,
ReactNode,
} from "react"; } from "react";
import { getKeyBindingsManager } from "../KeyBindingsManager"; import { getKeyBindingsManager } from "../KeyBindingsManager";
@ -158,8 +159,8 @@ interface IProps {
handleHomeEnd?: boolean; handleHomeEnd?: boolean;
handleUpDown?: boolean; handleUpDown?: boolean;
handleLeftRight?: boolean; handleLeftRight?: boolean;
children(renderProps: { onKeyDownHandler(ev: React.KeyboardEvent) }); children(renderProps: { onKeyDownHandler(ev: React.KeyboardEvent): void }): ReactNode;
onKeyDown?(ev: React.KeyboardEvent, state: IState); onKeyDown?(ev: React.KeyboardEvent, state: IState): void;
} }
export const findSiblingElement = ( export const findSiblingElement = (

View file

@ -25,7 +25,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
interface IProps extends React.ComponentProps<typeof StyledCheckbox> { interface IProps extends React.ComponentProps<typeof StyledCheckbox> {
label?: string; label?: string;
onChange(); // we handle keyup/down ourselves so lose the ChangeEvent onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent
onClose(): void; // gets called after onChange on KeyBindingAction.ActivateSelectedButton onClose(): void; // gets called after onChange on KeyBindingAction.ActivateSelectedButton
} }

View file

@ -25,7 +25,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
interface IProps extends React.ComponentProps<typeof StyledRadioButton> { interface IProps extends React.ComponentProps<typeof StyledRadioButton> {
label?: string; label?: string;
onChange(); // we handle keyup/down ourselves so lose the ChangeEvent onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent
onClose(): void; // gets called after onChange on KeyBindingAction.Enter onClose(): void; // gets called after onChange on KeyBindingAction.Enter
} }

View file

@ -30,7 +30,7 @@ export const RovingAccessibleButton: React.FC<IProps> = ({ inputRef, onFocus, ..
return ( return (
<AccessibleButton <AccessibleButton
{...props} {...props}
onFocus={(event) => { onFocus={(event: React.FocusEvent) => {
onFocusInternal(); onFocusInternal();
onFocus?.(event); onFocus?.(event);
}} }}

View file

@ -31,7 +31,7 @@ export const RovingAccessibleTooltipButton: React.FC<IProps> = ({ inputRef, onFo
return ( return (
<AccessibleTooltipButton <AccessibleTooltipButton
{...props} {...props}
onFocus={(event) => { onFocus={(event: React.FocusEvent) => {
onFocusInternal(); onFocusInternal();
onFocus?.(event); onFocus?.(event);
}} }}

View file

@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { ReactElement } from "react";
import { useRovingTabIndex } from "../RovingTabIndex"; import { useRovingTabIndex } from "../RovingTabIndex";
import { FocusHandler, Ref } from "./types"; import { FocusHandler, Ref } from "./types";
interface IProps { interface IProps {
inputRef?: Ref; inputRef?: Ref;
children(renderProps: { onFocus: FocusHandler; isActive: boolean; ref: Ref }); children(renderProps: { onFocus: FocusHandler; isActive: boolean; ref: Ref }): ReactElement<any, any>;
} }
// Wrapper to allow use of useRovingTabIndex outside of React Functional Components. // Wrapper to allow use of useRovingTabIndex outside of React Functional Components.

View file

@ -54,7 +54,7 @@ export default class RoomListActions {
oldIndex: number | null, oldIndex: number | null,
newIndex: number | null, newIndex: number | null,
): AsyncActionPayload { ): AsyncActionPayload {
let metaData = null; let metaData: Parameters<MatrixClient["setRoomTag"]>[2] | null = null;
// Is the tag ordered manually? // Is the tag ordered manually?
const store = RoomListStore.instance; const store = RoomListStore.instance;
@ -81,7 +81,7 @@ export default class RoomListActions {
return asyncAction( return asyncAction(
"RoomListActions.tagRoom", "RoomListActions.tagRoom",
() => { () => {
const promises = []; const promises: Promise<any>[] = [];
const roomId = room.roomId; const roomId = room.roomId;
// Evil hack to get DMs behaving // Evil hack to get DMs behaving
@ -120,7 +120,7 @@ export default class RoomListActions {
if (newTag && newTag !== DefaultTagID.DM && (hasChangedSubLists || metaData)) { if (newTag && newTag !== DefaultTagID.DM && (hasChangedSubLists || metaData)) {
// metaData is the body of the PUT to set the tag, so it must // metaData is the body of the PUT to set the tag, so it must
// at least be an empty object. // at least be an empty object.
metaData = metaData || {}; metaData = metaData || ({} as typeof metaData);
const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function (err) { const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function (err) {
logger.error("Failed to add tag " + newTag + " to room: " + err); logger.error("Failed to add tag " + newTag + " to room: " + err);

View file

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { ChangeEvent } from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import { _t } from "../../../../languageHandler"; import { _t } from "../../../../languageHandler";
import SdkConfig from "../../../../SdkConfig"; import SdkConfig from "../../../../SdkConfig";
@ -27,6 +28,7 @@ import Field from "../../../../components/views/elements/Field";
import BaseDialog from "../../../../components/views/dialogs/BaseDialog"; import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
import DialogButtons from "../../../../components/views/elements/DialogButtons"; import DialogButtons from "../../../../components/views/elements/DialogButtons";
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps"; import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
import { IIndexStats } from "../../../../indexing/BaseEventIndexManager";
interface IProps extends IDialogProps {} interface IProps extends IDialogProps {}
@ -43,7 +45,7 @@ interface IState {
* Allows the user to introspect the event index state and disable it. * Allows the user to introspect the event index state and disable it.
*/ */
export default class ManageEventIndexDialog extends React.Component<IProps, IState> { export default class ManageEventIndexDialog extends React.Component<IProps, IState> {
public constructor(props) { public constructor(props: IProps) {
super(props); super(props);
this.state = { this.state = {
@ -56,9 +58,9 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
}; };
} }
public updateCurrentRoom = async (room): Promise<void> => { public updateCurrentRoom = async (room: Room): Promise<void> => {
const eventIndex = EventIndexPeg.get(); const eventIndex = EventIndexPeg.get();
let stats; let stats: IIndexStats;
try { try {
stats = await eventIndex.getStats(); stats = await eventIndex.getStats();
@ -136,12 +138,12 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
Modal.createDialog(DisableEventIndexDialog, null, null, /* priority = */ false, /* static = */ true); Modal.createDialog(DisableEventIndexDialog, null, null, /* priority = */ false, /* static = */ true);
}; };
private onCrawlerSleepTimeChange = (e): void => { private onCrawlerSleepTimeChange = (e: ChangeEvent<HTMLInputElement>): void => {
this.setState({ crawlerSleepTime: e.target.value }); this.setState({ crawlerSleepTime: parseInt(e.target.value, 10) });
SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value); SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value);
}; };
public render(): JSX.Element { public render(): React.ReactNode {
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
let crawlerState; let crawlerState;

View file

@ -239,7 +239,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent<IProps, I
<form onSubmit={this.onPassPhraseNextClick}> <form onSubmit={this.onPassPhraseNextClick}>
<p> <p>
{_t( {_t(
"<b>Warning</b>: You should only set up key backup from a trusted computer.", "<b>Warning</b>: you should only set up key backup from a trusted computer.",
{}, {},
{ b: (sub) => <b>{sub}</b> }, { b: (sub) => <b>{sub}</b> },
)} )}
@ -459,7 +459,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent<IProps, I
} }
} }
public render(): JSX.Element { public render(): React.ReactNode {
let content; let content;
if (this.state.error) { if (this.state.error) {
content = ( content = (

View file

@ -20,7 +20,7 @@ import FileSaver from "file-saver";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup"; import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
import { TrustInfo } from "matrix-js-sdk/src/crypto/backup"; import { TrustInfo } from "matrix-js-sdk/src/crypto/backup";
import { CrossSigningKeys } from "matrix-js-sdk/src/matrix"; import { CrossSigningKeys, UIAFlow } from "matrix-js-sdk/src/matrix";
import { IRecoveryKey } from "matrix-js-sdk/src/crypto/api"; import { IRecoveryKey } from "matrix-js-sdk/src/crypto/api";
import { CryptoEvent } from "matrix-js-sdk/src/crypto"; import { CryptoEvent } from "matrix-js-sdk/src/crypto";
@ -206,7 +206,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
logger.log("uploadDeviceSigningKeys advertised no flows!"); logger.log("uploadDeviceSigningKeys advertised no flows!");
return; return;
} }
const canUploadKeysWithPasswordOnly = error.data.flows.some((f) => { const canUploadKeysWithPasswordOnly = error.data.flows.some((f: UIAFlow) => {
return f.stages.length === 1 && f.stages[0] === "m.login.password"; return f.stages.length === 1 && f.stages[0] === "m.login.password";
}); });
this.setState({ this.setState({
@ -842,7 +842,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
} }
} }
public render(): JSX.Element { public render(): React.ReactNode {
let content; let content;
if (this.state.error) { if (this.state.error) {
content = ( content = (

View file

@ -16,7 +16,7 @@ limitations under the License.
*/ */
import FileSaver from "file-saver"; import FileSaver from "file-saver";
import React from "react"; import React, { ChangeEvent } from "react";
import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
@ -127,7 +127,7 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
} as Pick<IState, AnyPassphrase>); } as Pick<IState, AnyPassphrase>);
}; };
public render(): JSX.Element { public render(): React.ReactNode {
const disableForm = this.state.phase === Phase.Exporting; const disableForm = this.state.phase === Phase.Exporting;
return ( return (
@ -163,7 +163,9 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
<Field <Field
label={_t("Enter passphrase")} label={_t("Enter passphrase")}
value={this.state.passphrase1} value={this.state.passphrase1}
onChange={(e) => this.onPassphraseChange(e, "passphrase1")} onChange={(e: ChangeEvent<HTMLInputElement>) =>
this.onPassphraseChange(e, "passphrase1")
}
autoFocus={true} autoFocus={true}
size={64} size={64}
type="password" type="password"
@ -174,7 +176,9 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
<Field <Field
label={_t("Confirm passphrase")} label={_t("Confirm passphrase")}
value={this.state.passphrase2} value={this.state.passphrase2}
onChange={(e) => this.onPassphraseChange(e, "passphrase2")} onChange={(e: ChangeEvent<HTMLInputElement>) =>
this.onPassphraseChange(e, "passphrase2")
}
size={64} size={64}
type="password" type="password"
disabled={disableForm} disabled={disableForm}

View file

@ -73,9 +73,9 @@ export default class ImportE2eKeysDialog extends React.Component<IProps, IState>
} }
private onFormChange = (): void => { private onFormChange = (): void => {
const files = this.file.current.files || []; const files = this.file.current.files;
this.setState({ this.setState({
enableSubmit: this.state.passphrase !== "" && files.length > 0, enableSubmit: this.state.passphrase !== "" && !!files?.length,
}); });
}; };
@ -127,7 +127,7 @@ export default class ImportE2eKeysDialog extends React.Component<IProps, IState>
return false; return false;
}; };
public render(): JSX.Element { public render(): React.ReactNode {
const disableForm = this.state.phase !== Phase.Edit; const disableForm = this.state.phase !== Phase.Edit;
return ( return (

View file

@ -48,13 +48,13 @@ export default class NewRecoveryMethodDialog extends React.PureComponent<IProps>
{ {
onFinished: this.props.onFinished, onFinished: this.props.onFinished,
}, },
null, undefined,
/* priority = */ false, /* priority = */ false,
/* static = */ true, /* static = */ true,
); );
}; };
public render(): JSX.Element { public render(): React.ReactNode {
const title = <span className="mx_KeyBackupFailedDialog_title">{_t("New Recovery Method")}</span>; const title = <span className="mx_KeyBackupFailedDialog_title">{_t("New Recovery Method")}</span>;
const newMethodDetected = <p>{_t("A new Security Phrase and key for Secure Messages have been detected.")}</p>; const newMethodDetected = <p>{_t("A new Security Phrase and key for Secure Messages have been detected.")}</p>;

View file

@ -37,14 +37,14 @@ export default class RecoveryMethodRemovedDialog extends React.PureComponent<IPr
this.props.onFinished(); this.props.onFinished();
Modal.createDialogAsync( Modal.createDialogAsync(
import("./CreateKeyBackupDialog") as unknown as Promise<ComponentType<{}>>, import("./CreateKeyBackupDialog") as unknown as Promise<ComponentType<{}>>,
null, undefined,
null, null,
/* priority = */ false, /* priority = */ false,
/* static = */ true, /* static = */ true,
); );
}; };
public render(): JSX.Element { public render(): React.ReactNode {
const title = <span className="mx_KeyBackupFailedDialog_title">{_t("Recovery Method Removed")}</span>; const title = <span className="mx_KeyBackupFailedDialog_title">{_t("Recovery Method Removed")}</span>;
return ( return (

View file

@ -91,7 +91,7 @@ export class PlaybackQueue {
public unsortedEnqueue(mxEvent: MatrixEvent, playback: Playback): void { public unsortedEnqueue(mxEvent: MatrixEvent, playback: Playback): void {
// We don't ever detach our listeners: we expect the Playback to clean up for us // We don't ever detach our listeners: we expect the Playback to clean up for us
this.playbacks.set(mxEvent.getId(), playback); this.playbacks.set(mxEvent.getId()!, playback);
playback.on(UPDATE_EVENT, (state) => this.onPlaybackStateChange(playback, mxEvent, state)); playback.on(UPDATE_EVENT, (state) => this.onPlaybackStateChange(playback, mxEvent, state));
playback.clockInfo.liveData.onUpdate((clock) => this.onPlaybackClock(playback, mxEvent, clock)); playback.clockInfo.liveData.onUpdate((clock) => this.onPlaybackClock(playback, mxEvent, clock));
} }
@ -99,12 +99,12 @@ export class PlaybackQueue {
private onPlaybackStateChange(playback: Playback, mxEvent: MatrixEvent, newState: PlaybackState): void { private onPlaybackStateChange(playback: Playback, mxEvent: MatrixEvent, newState: PlaybackState): void {
// Remember where the user got to in playback // Remember where the user got to in playback
const wasLastPlaying = this.currentPlaybackId === mxEvent.getId(); const wasLastPlaying = this.currentPlaybackId === mxEvent.getId();
if (newState === PlaybackState.Stopped && this.clockStates.has(mxEvent.getId()) && !wasLastPlaying) { if (newState === PlaybackState.Stopped && this.clockStates.has(mxEvent.getId()!) && !wasLastPlaying) {
// noinspection JSIgnoredPromiseFromCall // noinspection JSIgnoredPromiseFromCall
playback.skipTo(this.clockStates.get(mxEvent.getId())!); playback.skipTo(this.clockStates.get(mxEvent.getId()!)!);
} else if (newState === PlaybackState.Stopped) { } else if (newState === PlaybackState.Stopped) {
// Remove the now-useless clock for some space savings // Remove the now-useless clock for some space savings
this.clockStates.delete(mxEvent.getId()); this.clockStates.delete(mxEvent.getId()!);
if (wasLastPlaying) { if (wasLastPlaying) {
this.recentFullPlays.add(this.currentPlaybackId); this.recentFullPlays.add(this.currentPlaybackId);
@ -133,7 +133,7 @@ export class PlaybackQueue {
// timeline is already most recent last, so we can iterate down that. // timeline is already most recent last, so we can iterate down that.
const timeline = arrayFastClone(this.room.getLiveTimeline().getEvents()); const timeline = arrayFastClone(this.room.getLiveTimeline().getEvents());
let scanForVoiceMessage = false; let scanForVoiceMessage = false;
let nextEv: MatrixEvent; let nextEv: MatrixEvent | undefined;
for (const event of timeline) { for (const event of timeline) {
if (event.getId() === mxEvent.getId()) { if (event.getId() === mxEvent.getId()) {
scanForVoiceMessage = true; scanForVoiceMessage = true;
@ -149,8 +149,8 @@ export class PlaybackQueue {
break; // Stop automatic playback: next useful event is not a voice message break; // Stop automatic playback: next useful event is not a voice message
} }
const havePlayback = this.playbacks.has(event.getId()); const havePlayback = this.playbacks.has(event.getId()!);
const isRecentlyCompleted = this.recentFullPlays.has(event.getId()); const isRecentlyCompleted = this.recentFullPlays.has(event.getId()!);
if (havePlayback && !isRecentlyCompleted) { if (havePlayback && !isRecentlyCompleted) {
nextEv = event; nextEv = event;
break; break;
@ -164,7 +164,7 @@ export class PlaybackQueue {
} else { } else {
this.playbackIdOrder = orderClone; this.playbackIdOrder = orderClone;
const instance = this.playbacks.get(nextEv.getId()); const instance = this.playbacks.get(nextEv.getId()!);
PlaybackManager.instance.pauseAllExcept(instance); PlaybackManager.instance.pauseAllExcept(instance);
// This should cause a Play event, which will re-populate our playback order // This should cause a Play event, which will re-populate our playback order
@ -196,7 +196,7 @@ export class PlaybackQueue {
} }
} }
this.currentPlaybackId = mxEvent.getId(); this.currentPlaybackId = mxEvent.getId()!;
if (order.length === 0 || order[order.length - 1] !== this.currentPlaybackId) { if (order.length === 0 || order[order.length - 1] !== this.currentPlaybackId) {
order.push(this.currentPlaybackId); order.push(this.currentPlaybackId);
} }
@ -214,7 +214,7 @@ export class PlaybackQueue {
if (playback.currentState === PlaybackState.Decoding) return; // ignore pre-ready values if (playback.currentState === PlaybackState.Decoding) return; // ignore pre-ready values
if (playback.currentState !== PlaybackState.Stopped) { if (playback.currentState !== PlaybackState.Stopped) {
this.clockStates.set(mxEvent.getId(), clocks[0]); // [0] is the current seek position this.clockStates.set(mxEvent.getId()!, clocks[0]); // [0] is the current seek position
} }
} }
} }

View file

@ -43,7 +43,11 @@ class MxVoiceWorklet extends AudioWorkletProcessor {
private nextAmplitudeSecond = 0; private nextAmplitudeSecond = 0;
private amplitudeIndex = 0; private amplitudeIndex = 0;
public process(inputs, outputs, parameters): boolean { public process(
inputs: Float32Array[][],
outputs: Float32Array[][],
parameters: Record<string, Float32Array>,
): boolean {
const currentSecond = roundTimeToTargetFreq(currentTime); const currentSecond = roundTimeToTargetFreq(currentTime);
// We special case the first ping because there's a fairly good chance that we'll miss the zeroth // We special case the first ping because there's a fairly good chance that we'll miss the zeroth
// update. Firefox for instance takes 0.06 seconds (roughly) to call this function for the first // update. Firefox for instance takes 0.06 seconds (roughly) to call this function for the first

View file

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
// @ts-ignore
import Recorder from "opus-recorder/dist/recorder.min.js"; import Recorder from "opus-recorder/dist/recorder.min.js";
import encoderPath from "opus-recorder/dist/encoderWorker.min.js"; import encoderPath from "opus-recorder/dist/encoderWorker.min.js";
import { SimpleObservable } from "matrix-widget-api"; import { SimpleObservable } from "matrix-widget-api";
@ -78,7 +77,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
private targetMaxLength: number | null = TARGET_MAX_LENGTH; private targetMaxLength: number | null = TARGET_MAX_LENGTH;
public amplitudes: number[] = []; // at each second mark, generated public amplitudes: number[] = []; // at each second mark, generated
private liveWaveform = new FixedRollingArray(RECORDING_PLAYBACK_SAMPLES, 0); private liveWaveform = new FixedRollingArray(RECORDING_PLAYBACK_SAMPLES, 0);
public onDataAvailable: (data: ArrayBuffer) => void; public onDataAvailable?: (data: ArrayBuffer) => void;
public get contentType(): string { public get contentType(): string {
return "audio/ogg"; return "audio/ogg";
@ -182,7 +181,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
}); });
// not using EventEmitter here because it leads to detached bufferes // not using EventEmitter here because it leads to detached bufferes
this.recorder.ondataavailable = (data: ArrayBuffer) => this?.onDataAvailable(data); this.recorder.ondataavailable = (data: ArrayBuffer) => this.onDataAvailable?.(data);
} catch (e) { } catch (e) {
logger.error("Error starting recording: ", e); logger.error("Error starting recording: ", e);
if (e instanceof DOMException) { if (e instanceof DOMException) {

View file

@ -69,7 +69,7 @@ export function decodeOgg(audioBuffer: ArrayBuffer): Promise<ArrayBuffer> {
command: "encode", command: "encode",
buffers: ev.data, buffers: ev.data,
}, },
ev.data.map((b) => b.buffer), ev.data.map((b: Float32Array) => b.buffer),
); );
}; };

View file

@ -70,7 +70,7 @@ export default abstract class AutocompleteProvider {
* @param {boolean} force True if the user is forcing completion * @param {boolean} force True if the user is forcing completion
* @return {object} { command, range } where both objects fields are null if no match * @return {object} { command, range } where both objects fields are null if no match
*/ */
public getCurrentCommand(query: string, selection: ISelectionRange, force = false): ICommand | null { public getCurrentCommand(query: string, selection: ISelectionRange, force = false): Partial<ICommand> {
let commandRegex = this.commandRegex; let commandRegex = this.commandRegex;
if (force && this.shouldForceComplete()) { if (force && this.shouldForceComplete()) {
@ -78,7 +78,7 @@ export default abstract class AutocompleteProvider {
} }
if (!commandRegex) { if (!commandRegex) {
return null; return {};
} }
commandRegex.lastIndex = 0; commandRegex.lastIndex = 0;

View file

@ -95,7 +95,7 @@ export default class CommandProvider extends AutocompleteProvider {
description={_t(result.description)} description={_t(result.description)}
/> />
), ),
range, range: range!,
}; };
}); });
} }

View file

@ -19,7 +19,7 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import { uniq, sortBy } from "lodash"; import { uniq, sortBy, ListIteratee } from "lodash";
import EMOTICON_REGEX from "emojibase-regex/emoticon"; import EMOTICON_REGEX from "emojibase-regex/emoticon";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
@ -55,7 +55,11 @@ const SORTED_EMOJI: ISortedEmoji[] = EMOJI.sort((a, b) => {
_orderBy: index, _orderBy: index,
})); }));
function score(query: string, space: string): number { function score(query: string, space: string[] | string): number {
if (Array.isArray(space)) {
return Math.min(...space.map((s) => score(query, s)));
}
const index = space.indexOf(query); const index = space.indexOf(query);
if (index === -1) { if (index === -1) {
return Infinity; return Infinity;
@ -90,7 +94,7 @@ export default class EmojiProvider extends AutocompleteProvider {
shouldMatchWordsOnly: true, shouldMatchWordsOnly: true,
}); });
this.recentlyUsed = Array.from(new Set(recent.get().map(getEmojiFromUnicode).filter(Boolean))); this.recentlyUsed = Array.from(new Set(recent.get().map(getEmojiFromUnicode).filter(Boolean))) as IEmoji[];
} }
public async getCompletions( public async getCompletions(
@ -113,7 +117,7 @@ export default class EmojiProvider extends AutocompleteProvider {
// Do second match with shouldMatchWordsOnly in order to match against 'name' // Do second match with shouldMatchWordsOnly in order to match against 'name'
completions = completions.concat(this.nameMatcher.match(matchedString)); completions = completions.concat(this.nameMatcher.match(matchedString));
let sorters = []; let sorters: ListIteratee<ISortedEmoji>[] = [];
// make sure that emoticons come first // make sure that emoticons come first
sorters.push((c) => score(matchedString, c.emoji.emoticon || "")); sorters.push((c) => score(matchedString, c.emoji.emoticon || ""));
@ -148,7 +152,7 @@ export default class EmojiProvider extends AutocompleteProvider {
<span>{c.emoji.unicode}</span> <span>{c.emoji.unicode}</span>
</PillCompletion> </PillCompletion>
), ),
range, range: range!,
})); }));
} }
return []; return [];

View file

@ -40,12 +40,13 @@ export default class NotifProvider extends AutocompleteProvider {
): Promise<ICompletion[]> { ): Promise<ICompletion[]> {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!this.room.currentState.mayTriggerNotifOfType("room", client.credentials.userId)) return []; if (!this.room.currentState.mayTriggerNotifOfType("room", client.credentials.userId!)) return [];
const { command, range } = this.getCurrentCommand(query, selection, force); const { command, range } = this.getCurrentCommand(query, selection, force);
if ( if (
command?.[0].length > 1 && command?.[0] &&
["@room", "@channel", "@everyone", "@here"].some((c) => c.startsWith(command[0])) command[0].length > 1 &&
["@room", "@channel", "@everyone", "@here"].some((c) => c.startsWith(command![0]))
) { ) {
return [ return [
{ {
@ -58,7 +59,7 @@ export default class NotifProvider extends AutocompleteProvider {
<RoomAvatar width={24} height={24} room={this.room} /> <RoomAvatar width={24} height={24} room={this.room} />
</PillCompletion> </PillCompletion>
), ),
range, range: range!,
}, },
]; ];
} }

View file

@ -88,7 +88,7 @@ export default class QueryMatcher<T extends {}> {
if (!this._items.has(key)) { if (!this._items.has(key)) {
this._items.set(key, []); this._items.set(key, []);
} }
this._items.get(key).push({ this._items.get(key)!.push({
keyWeight: Number(index), keyWeight: Number(index),
object, object,
}); });
@ -104,7 +104,11 @@ export default class QueryMatcher<T extends {}> {
if (query.length === 0) { if (query.length === 0) {
return []; return [];
} }
const matches = []; const matches: {
index: number;
object: T;
keyWeight: number;
}[] = [];
// Iterate through the map & check each key. // Iterate through the map & check each key.
// ES6 Map iteration order is defined to be insertion order, so results // ES6 Map iteration order is defined to be insertion order, so results
// here will come out in the order they were put in. // here will come out in the order they were put in.

View file

@ -39,12 +39,12 @@ function canonicalScore(displayedAlias: string, room: Room): number {
function matcherObject( function matcherObject(
room: Room, room: Room,
displayedAlias: string | null, displayedAlias: string,
matchName = "", matchName = "",
): { ): {
room: Room; room: Room;
matchName: string; matchName: string;
displayedAlias: string | null; displayedAlias: string;
} { } {
return { return {
room, room,
@ -81,7 +81,7 @@ export default class RoomProvider extends AutocompleteProvider {
// the only reason we need to do this is because Fuse only matches on properties // the only reason we need to do this is because Fuse only matches on properties
let matcherObjects = this.getRooms().reduce<ReturnType<typeof matcherObject>[]>((aliases, room) => { let matcherObjects = this.getRooms().reduce<ReturnType<typeof matcherObject>[]>((aliases, room) => {
if (room.getCanonicalAlias()) { if (room.getCanonicalAlias()) {
aliases = aliases.concat(matcherObject(room, room.getCanonicalAlias(), room.name)); aliases = aliases.concat(matcherObject(room, room.getCanonicalAlias()!, room.name));
} }
if (room.getAltAliases().length) { if (room.getAltAliases().length) {
const altAliases = room.getAltAliases().map((alias) => matcherObject(room, alias)); const altAliases = room.getAltAliases().map((alias) => matcherObject(room, alias));
@ -122,7 +122,7 @@ export default class RoomProvider extends AutocompleteProvider {
<RoomAvatar width={24} height={24} room={room.room} /> <RoomAvatar width={24} height={24} room={room.room} />
</PillCompletion> </PillCompletion>
), ),
range, range: range!,
}), }),
) )
.filter((completion) => !!completion.completion && completion.completion.length > 0); .filter((completion) => !!completion.completion && completion.completion.length > 0);

View file

@ -44,7 +44,7 @@ const FORCED_USER_REGEX = /[^/,:; \t\n]\S*/g;
export default class UserProvider extends AutocompleteProvider { export default class UserProvider extends AutocompleteProvider {
public matcher: QueryMatcher<RoomMember>; public matcher: QueryMatcher<RoomMember>;
public users: RoomMember[]; public users: RoomMember[] | null;
public room: Room; public room: Room;
public constructor(room: Room, renderingType?: TimelineRenderingType) { public constructor(room: Room, renderingType?: TimelineRenderingType) {
@ -54,7 +54,7 @@ export default class UserProvider extends AutocompleteProvider {
renderingType, renderingType,
}); });
this.room = room; this.room = room;
this.matcher = new QueryMatcher([], { this.matcher = new QueryMatcher<RoomMember>([], {
keys: ["name"], keys: ["name"],
funcs: [(obj) => obj.userId.slice(1)], // index by user id minus the leading '@' funcs: [(obj) => obj.userId.slice(1)], // index by user id minus the leading '@'
shouldMatchWordsOnly: false, shouldMatchWordsOnly: false,
@ -73,7 +73,7 @@ export default class UserProvider extends AutocompleteProvider {
private onRoomTimeline = ( private onRoomTimeline = (
ev: MatrixEvent, ev: MatrixEvent,
room: Room | null, room: Room | undefined,
toStartOfTimeline: boolean, toStartOfTimeline: boolean,
removed: boolean, removed: boolean,
data: IRoomTimelineData, data: IRoomTimelineData,
@ -110,18 +110,15 @@ export default class UserProvider extends AutocompleteProvider {
// lazy-load user list into matcher // lazy-load user list into matcher
if (!this.users) this.makeUsers(); if (!this.users) this.makeUsers();
let completions = [];
const { command, range } = this.getCurrentCommand(rawQuery, selection, force); const { command, range } = this.getCurrentCommand(rawQuery, selection, force);
if (!command) return completions; const fullMatch = command?.[0];
const fullMatch = command[0];
// Don't search if the query is a single "@" // Don't search if the query is a single "@"
if (fullMatch && fullMatch !== "@") { if (fullMatch && fullMatch !== "@") {
// Don't include the '@' in our search query - it's only used as a way to trigger completion // Don't include the '@' in our search query - it's only used as a way to trigger completion
const query = fullMatch.startsWith("@") ? fullMatch.substring(1) : fullMatch; const query = fullMatch.startsWith("@") ? fullMatch.substring(1) : fullMatch;
completions = this.matcher.match(query, limit).map((user) => { return this.matcher.match(query, limit).map((user) => {
const description = UserIdentifierCustomisations.getDisplayUserIdentifier(user.userId, { const description = UserIdentifierCustomisations.getDisplayUserIdentifier?.(user.userId, {
roomId: this.room.roomId, roomId: this.room.roomId,
withDisplayName: true, withDisplayName: true,
}); });
@ -132,18 +129,18 @@ export default class UserProvider extends AutocompleteProvider {
completion: user.rawDisplayName, completion: user.rawDisplayName,
completionId: user.userId, completionId: user.userId,
type: "user", type: "user",
suffix: selection.beginning && range.start === 0 ? ": " : " ", suffix: selection.beginning && range!.start === 0 ? ": " : " ",
href: makeUserPermalink(user.userId), href: makeUserPermalink(user.userId),
component: ( component: (
<PillCompletion title={displayName} description={description}> <PillCompletion title={displayName} description={description}>
<MemberAvatar member={user} width={24} height={24} /> <MemberAvatar member={user} width={24} height={24} />
</PillCompletion> </PillCompletion>
), ),
range, range: range!,
}; };
}); });
} }
return completions; return [];
} }
public getName(): string { public getName(): string {
@ -152,10 +149,10 @@ export default class UserProvider extends AutocompleteProvider {
private makeUsers(): void { private makeUsers(): void {
const events = this.room.getLiveTimeline().getEvents(); const events = this.room.getLiveTimeline().getEvents();
const lastSpoken = {}; const lastSpoken: Record<string, number> = {};
for (const event of events) { for (const event of events) {
lastSpoken[event.getSender()] = event.getTs(); lastSpoken[event.getSender()!] = event.getTs();
} }
const currentUserId = MatrixClientPeg.get().credentials.userId; const currentUserId = MatrixClientPeg.get().credentials.userId;
@ -167,7 +164,7 @@ export default class UserProvider extends AutocompleteProvider {
this.matcher.setObjects(this.users); this.matcher.setObjects(this.users);
} }
public onUserSpoke(user: RoomMember): void { public onUserSpoke(user: RoomMember | null): void {
if (!this.users) return; if (!this.users) return;
if (!user) return; if (!user) return;
if (user.userId === MatrixClientPeg.get().credentials.userId) return; if (user.userId === MatrixClientPeg.get().credentials.userId) return;

View file

@ -55,7 +55,7 @@ export default class AutoHideScrollbar<T extends keyof JSX.IntrinsicElements> ex
} }
} }
public render(): JSX.Element { public render(): React.ReactNode {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const { element, className, onScroll, tabIndex, wrappedRef, children, ...otherProps } = this.props; const { element, className, onScroll, tabIndex, wrappedRef, children, ...otherProps } = this.props;

View file

@ -99,9 +99,9 @@ export interface IProps extends MenuProps {
closeOnInteraction?: boolean; closeOnInteraction?: boolean;
// Function to be called on menu close // Function to be called on menu close
onFinished(); onFinished(): void;
// on resize callback // on resize callback
windowResize?(); windowResize?(): void;
} }
interface IState { interface IState {
@ -119,8 +119,8 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
managed: true, managed: true,
}; };
public constructor(props, context) { public constructor(props: IProps) {
super(props, context); super(props);
this.state = { this.state = {
contextMenuElem: null, contextMenuElem: null,
@ -387,13 +387,13 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
menuStyle["paddingRight"] = menuPaddingRight; menuStyle["paddingRight"] = menuPaddingRight;
} }
const wrapperStyle = {}; const wrapperStyle: CSSProperties = {};
if (!isNaN(Number(zIndex))) { if (!isNaN(Number(zIndex))) {
menuStyle["zIndex"] = zIndex + 1; menuStyle["zIndex"] = zIndex + 1;
wrapperStyle["zIndex"] = zIndex; wrapperStyle["zIndex"] = zIndex;
} }
let background; let background: JSX.Element;
if (hasBackground) { if (hasBackground) {
background = ( background = (
<div <div
@ -624,7 +624,7 @@ export function createMenu(
ElementClass: typeof React.Component, ElementClass: typeof React.Component,
props: Record<string, any>, props: Record<string, any>,
): { close: (...args: any[]) => void } { ): { close: (...args: any[]) => void } {
const onFinished = function (...args): void { const onFinished = function (...args: any[]): void {
ReactDOM.unmountComponentAtNode(getOrCreateContainer()); ReactDOM.unmountComponentAtNode(getOrCreateContainer());
props?.onFinished?.apply(null, args); props?.onFinished?.apply(null, args);
}; };

View file

@ -118,7 +118,7 @@ export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
} }
}; };
public render(): JSX.Element { public render(): React.ReactNode {
// HACK: Workaround for the context's MatrixClient not updating. // HACK: Workaround for the context's MatrixClient not updating.
const client = this.context || MatrixClientPeg.get(); const client = this.context || MatrixClientPeg.get();
const isGuest = client ? client.isGuest() : true; const isGuest = client ? client.isGuest() : true;

View file

@ -43,7 +43,7 @@ interface IProps {
} }
interface IState { interface IState {
timelineSet: EventTimelineSet; timelineSet: EventTimelineSet | null;
narrow: boolean; narrow: boolean;
} }
@ -59,7 +59,7 @@ class FilePanel extends React.Component<IProps, IState> {
public noRoom: boolean; public noRoom: boolean;
private card = createRef<HTMLDivElement>(); private card = createRef<HTMLDivElement>();
public state = { public state: IState = {
timelineSet: null, timelineSet: null,
narrow: false, narrow: false,
}; };
@ -223,7 +223,7 @@ class FilePanel extends React.Component<IProps, IState> {
} }
} }
public render(): JSX.Element { public render(): React.ReactNode {
if (MatrixClientPeg.get().isGuest()) { if (MatrixClientPeg.get().isGuest()) {
return ( return (
<BaseCard className="mx_FilePanel mx_RoomView_messageListWrapper" onClose={this.props.onClose}> <BaseCard className="mx_FilePanel mx_RoomView_messageListWrapper" onClose={this.props.onClose}>

View file

@ -79,6 +79,12 @@ export function GenericDropdownMenuGroup<T extends Key>({
); );
} }
function isGenericDropdownMenuGroupArray<T>(
items: readonly GenericDropdownMenuItem<T>[],
): items is GenericDropdownMenuGroup<T>[] {
return isGenericDropdownMenuGroup(items[0]);
}
function isGenericDropdownMenuGroup<T>(item: GenericDropdownMenuItem<T>): item is GenericDropdownMenuGroup<T> { function isGenericDropdownMenuGroup<T>(item: GenericDropdownMenuItem<T>): item is GenericDropdownMenuGroup<T> {
return "options" in item; return "options" in item;
} }
@ -123,19 +129,19 @@ export function GenericDropdownMenu<T>({
.flatMap((it) => (isGenericDropdownMenuGroup(it) ? [it, ...it.options] : [it])) .flatMap((it) => (isGenericDropdownMenuGroup(it) ? [it, ...it.options] : [it]))
.find((option) => (toKey ? toKey(option.key) === toKey(value) : option.key === value)); .find((option) => (toKey ? toKey(option.key) === toKey(value) : option.key === value));
let contextMenuOptions: JSX.Element; let contextMenuOptions: JSX.Element;
if (options && isGenericDropdownMenuGroup(options[0])) { if (options && isGenericDropdownMenuGroupArray(options)) {
contextMenuOptions = ( contextMenuOptions = (
<> <>
{options.map((group) => ( {options.map((group) => (
<GenericDropdownMenuGroup <GenericDropdownMenuGroup
key={toKey?.(group.key) ?? group.key} key={toKey?.(group.key) ?? (group.key as Key)}
label={group.label} label={group.label}
description={group.description} description={group.description}
adornment={group.adornment} adornment={group.adornment}
> >
{group.options.map((option) => ( {group.options.map((option) => (
<GenericDropdownMenuOption <GenericDropdownMenuOption
key={toKey?.(option.key) ?? option.key} key={toKey?.(option.key) ?? (option.key as Key)}
label={option.label} label={option.label}
description={option.description} description={option.description}
onClick={(ev: ButtonEvent) => { onClick={(ev: ButtonEvent) => {
@ -156,7 +162,7 @@ export function GenericDropdownMenu<T>({
<> <>
{options.map((option) => ( {options.map((option) => (
<GenericDropdownMenuOption <GenericDropdownMenuOption
key={toKey?.(option.key) ?? option.key} key={toKey?.(option.key) ?? (option.key as Key)}
label={option.label} label={option.label}
description={option.description} description={option.description}
onClick={(ev: ButtonEvent) => { onClick={(ev: ButtonEvent) => {

View file

@ -22,7 +22,7 @@ interface IProps {
} }
export default class GenericErrorPage extends React.PureComponent<IProps> { export default class GenericErrorPage extends React.PureComponent<IProps> {
public render(): JSX.Element { public render(): React.ReactNode {
return ( return (
<div className="mx_GenericErrorPage"> <div className="mx_GenericErrorPage">
<div className="mx_GenericErrorPage_box"> <div className="mx_GenericErrorPage_box">

View file

@ -177,7 +177,7 @@ export default class IndicatorScrollbar<T extends keyof JSX.IntrinsicElements> e
} }
}; };
public render(): JSX.Element { public render(): React.ReactNode {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const { children, trackHorizontalOverflow, verticalScrollsHorizontally, ...otherProps } = this.props; const { children, trackHorizontalOverflow, verticalScrollsHorizontally, ...otherProps } = this.props;

View file

@ -80,7 +80,7 @@ interface IProps {
// Called when the stage changes, or the stage's phase changes. First // Called when the stage changes, or the stage's phase changes. First
// argument is the stage, second is the phase. Some stages do not have // argument is the stage, second is the phase. Some stages do not have
// phases and will be counted as 0 (numeric). // phases and will be counted as 0 (numeric).
onStagePhaseChange?(stage: string, phase: string | number): void; onStagePhaseChange?(stage: AuthType, phase: number): void;
} }
interface IState { interface IState {
@ -99,7 +99,7 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
private unmounted = false; private unmounted = false;
public constructor(props) { public constructor(props: IProps) {
super(props); super(props);
this.state = { this.state = {
@ -249,7 +249,7 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
this.authLogic.setEmailSid(sid); this.authLogic.setEmailSid(sid);
}; };
public render(): JSX.Element { public render(): React.ReactNode {
const stage = this.state.authStage; const stage = this.state.authStage;
if (!stage) { if (!stage) {
if (this.state.busy) { if (this.state.busy) {

View file

@ -68,7 +68,7 @@ interface IState {
export default class LeftPanel extends React.Component<IProps, IState> { export default class LeftPanel extends React.Component<IProps, IState> {
private listContainerRef = createRef<HTMLDivElement>(); private listContainerRef = createRef<HTMLDivElement>();
private roomListRef = createRef<RoomList>(); private roomListRef = createRef<RoomList>();
private focusedElement = null; private focusedElement: Element = null;
private isDoingStickyHeaders = false; private isDoingStickyHeaders = false;
public constructor(props: IProps) { public constructor(props: IProps) {

View file

@ -136,8 +136,8 @@ class LoggedInView extends React.Component<IProps, IState> {
protected backgroundImageWatcherRef: string; protected backgroundImageWatcherRef: string;
protected resizer: Resizer; protected resizer: Resizer;
public constructor(props, context) { public constructor(props: IProps) {
super(props, context); super(props);
this.state = { this.state = {
syncErrorData: undefined, syncErrorData: undefined,
@ -229,8 +229,8 @@ class LoggedInView extends React.Component<IProps, IState> {
}; };
private createResizer(): Resizer { private createResizer(): Resizer {
let panelSize; let panelSize: number;
let panelCollapsed; let panelCollapsed: boolean;
const collapseConfig: ICollapseConfig = { const collapseConfig: ICollapseConfig = {
// TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel // TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel
toggleSize: 206 - 50, toggleSize: 206 - 50,
@ -341,7 +341,7 @@ class LoggedInView extends React.Component<IProps, IState> {
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice]; const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
if (!serverNoticeList) return; if (!serverNoticeList) return;
const events = []; const events: MatrixEvent[] = [];
let pinnedEventTs = 0; let pinnedEventTs = 0;
for (const room of serverNoticeList) { for (const room of serverNoticeList) {
const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", ""); const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", "");
@ -369,7 +369,7 @@ class LoggedInView extends React.Component<IProps, IState> {
e.getContent()["server_notice_type"] === "m.server_notice.usage_limit_reached" e.getContent()["server_notice_type"] === "m.server_notice.usage_limit_reached"
); );
}); });
const usageLimitEventContent = usageLimitEvent && usageLimitEvent.getContent(); const usageLimitEventContent = usageLimitEvent?.getContent<IUsageLimit>();
this.calculateServerLimitToast(this.state.syncErrorData, usageLimitEventContent); this.calculateServerLimitToast(this.state.syncErrorData, usageLimitEventContent);
this.setState({ this.setState({
usageLimitEventContent, usageLimitEventContent,
@ -422,13 +422,13 @@ class LoggedInView extends React.Component<IProps, IState> {
We also listen with a native listener on the document to get keydown events when no element is focused. We also listen with a native listener on the document to get keydown events when no element is focused.
Bubbling is irrelevant here as the target is the body element. Bubbling is irrelevant here as the target is the body element.
*/ */
private onReactKeyDown = (ev): void => { private onReactKeyDown = (ev: React.KeyboardEvent): void => {
// events caught while bubbling up on the root element // events caught while bubbling up on the root element
// of this component, so something must be focused. // of this component, so something must be focused.
this.onKeyDown(ev); this.onKeyDown(ev);
}; };
private onNativeKeyDown = (ev): void => { private onNativeKeyDown = (ev: KeyboardEvent): void => {
// only pass this if there is no focused element. // only pass this if there is no focused element.
// if there is, onKeyDown will be called by the // if there is, onKeyDown will be called by the
// react keydown handler that respects the react bubbling order. // react keydown handler that respects the react bubbling order.
@ -437,7 +437,7 @@ class LoggedInView extends React.Component<IProps, IState> {
} }
}; };
private onKeyDown = (ev): void => { private onKeyDown = (ev: React.KeyboardEvent | KeyboardEvent): void => {
let handled = false; let handled = false;
const roomAction = getKeyBindingsManager().getRoomAction(ev); const roomAction = getKeyBindingsManager().getRoomAction(ev);
@ -571,7 +571,7 @@ class LoggedInView extends React.Component<IProps, IState> {
) { ) {
dis.dispatch<SwitchSpacePayload>({ dis.dispatch<SwitchSpacePayload>({
action: Action.SwitchSpace, action: Action.SwitchSpace,
num: ev.code.slice(5), // Cut off the first 5 characters - "Digit" num: parseInt(ev.code.slice(5), 10), // Cut off the first 5 characters - "Digit"
}); });
handled = true; handled = true;
} }
@ -615,13 +615,11 @@ class LoggedInView extends React.Component<IProps, IState> {
* dispatch a page-up/page-down/etc to the appropriate component * dispatch a page-up/page-down/etc to the appropriate component
* @param {Object} ev The key event * @param {Object} ev The key event
*/ */
private onScrollKeyPressed = (ev): void => { private onScrollKeyPressed = (ev: React.KeyboardEvent | KeyboardEvent): void => {
if (this._roomView.current) { this._roomView.current?.handleScrollKey(ev);
this._roomView.current.handleScrollKey(ev);
}
}; };
public render(): JSX.Element { public render(): React.ReactNode {
let pageElement; let pageElement;
switch (this.props.page_type) { switch (this.props.page_type) {

View file

@ -47,7 +47,7 @@ export default class MainSplit extends React.Component<IProps> {
}; };
private loadSidePanelSize(): { height: string | number; width: number } { private loadSidePanelSize(): { height: string | number; width: number } {
let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size"), 10); let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size")!, 10);
if (isNaN(rhsSize)) { if (isNaN(rhsSize)) {
rhsSize = 350; rhsSize = 350;
@ -59,7 +59,7 @@ export default class MainSplit extends React.Component<IProps> {
}; };
} }
public render(): JSX.Element { public render(): React.ReactNode {
const bodyView = React.Children.only(this.props.children); const bodyView = React.Children.only(this.props.children);
const panelView = this.props.panel; const panelView = this.props.panel;

View file

@ -419,7 +419,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
window.addEventListener("resize", this.onWindowResized); window.addEventListener("resize", this.onWindowResized);
} }
public componentDidUpdate(prevProps, prevState): void { public componentDidUpdate(prevProps: IProps, prevState: IState): void {
if (this.shouldTrackPageChange(prevState, this.state)) { if (this.shouldTrackPageChange(prevState, this.state)) {
const durationMs = this.stopPageChangeTimer(); const durationMs = this.stopPageChangeTimer();
PosthogTrackers.instance.trackPageChange(this.state.view, this.state.page_type, durationMs); PosthogTrackers.instance.trackPageChange(this.state.view, this.state.page_type, durationMs);
@ -544,12 +544,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
if (state.view === undefined) { if (state.view === undefined) {
throw new Error("setStateForNewView with no view!"); throw new Error("setStateForNewView with no view!");
} }
const newState = { this.setState({
currentUserId: null, currentUserId: undefined,
justRegistered: false, justRegistered: false,
}; ...state,
Object.assign(newState, state); } as IState);
this.setState(newState);
} }
private onAction = (payload: ActionPayload): void => { private onAction = (payload: ActionPayload): void => {
@ -2022,7 +2021,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
return fragmentAfterLogin; return fragmentAfterLogin;
} }
public render(): JSX.Element { public render(): React.ReactNode {
const fragmentAfterLogin = this.getFragmentAfterLogin(); const fragmentAfterLogin = this.getFragmentAfterLogin();
let view = null; let view = null;

View file

@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef, KeyboardEvent, ReactNode, TransitionEvent } from "react"; import React, { createRef, ReactNode, TransitionEvent } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import classNames from "classnames"; import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { EventType } from "matrix-js-sdk/src/@types/event"; import { EventType } from "matrix-js-sdk/src/@types/event";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon"; import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
@ -34,7 +34,7 @@ import SettingsStore from "../../settings/SettingsStore";
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import { Layout } from "../../settings/enums/Layout"; import { Layout } from "../../settings/enums/Layout";
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
import EventTile, { UnwrappedEventTile, GetRelationsForEvent, IReadReceiptProps } from "../views/rooms/EventTile"; import EventTile, { GetRelationsForEvent, IReadReceiptProps, UnwrappedEventTile } from "../views/rooms/EventTile";
import { hasText } from "../../TextForEvent"; import { hasText } from "../../TextForEvent";
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer"; import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
import DMRoomMap from "../../utils/DMRoomMap"; import DMRoomMap from "../../utils/DMRoomMap";
@ -272,7 +272,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
// A map to allow groupers to maintain consistent keys even if their first event is uprooted due to back-pagination. // A map to allow groupers to maintain consistent keys even if their first event is uprooted due to back-pagination.
public grouperKeyMap = new WeakMap<MatrixEvent, string>(); public grouperKeyMap = new WeakMap<MatrixEvent, string>();
public constructor(props, context) { public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
super(props, context); super(props, context);
this.state = { this.state = {
@ -308,7 +308,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
SettingsStore.unwatchSetting(this.showTypingNotificationsWatcherRef); SettingsStore.unwatchSetting(this.showTypingNotificationsWatcherRef);
} }
public componentDidUpdate(prevProps, prevState): void { public componentDidUpdate(prevProps: IProps, prevState: IState): void {
if (prevProps.layout !== this.props.layout) { if (prevProps.layout !== this.props.layout) {
this.calculateRoomMembersCount(); this.calculateRoomMembersCount();
} }
@ -410,17 +410,13 @@ export default class MessagePanel extends React.Component<IProps, IState> {
/* jump to the top of the content. /* jump to the top of the content.
*/ */
public scrollToTop(): void { public scrollToTop(): void {
if (this.scrollPanel.current) { this.scrollPanel.current?.scrollToTop();
this.scrollPanel.current.scrollToTop();
}
} }
/* jump to the bottom of the content. /* jump to the bottom of the content.
*/ */
public scrollToBottom(): void { public scrollToBottom(): void {
if (this.scrollPanel.current) { this.scrollPanel.current?.scrollToBottom();
this.scrollPanel.current.scrollToBottom();
}
} }
/** /**
@ -428,10 +424,8 @@ export default class MessagePanel extends React.Component<IProps, IState> {
* *
* @param {KeyboardEvent} ev: the keyboard event to handle * @param {KeyboardEvent} ev: the keyboard event to handle
*/ */
public handleScrollKey(ev: KeyboardEvent): void { public handleScrollKey(ev: React.KeyboardEvent | KeyboardEvent): void {
if (this.scrollPanel.current) { this.scrollPanel.current?.handleScrollKey(ev);
this.scrollPanel.current.handleScrollKey(ev);
}
} }
/* jump to the given event id. /* jump to the given event id.
@ -752,7 +746,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
const readReceipts = this.readReceiptsByEvent[eventId]; const readReceipts = this.readReceiptsByEvent[eventId];
let isLastSuccessful = false; let isLastSuccessful = false;
const isSentState = (s): boolean => !s || s === "sent"; const isSentState = (s: EventStatus): boolean => !s || s === EventStatus.SENT;
const isSent = isSentState(mxEv.getAssociatedStatus()); const isSent = isSentState(mxEv.getAssociatedStatus());
const hasNextEvent = nextEvent && this.shouldShowEvent(nextEvent); const hasNextEvent = nextEvent && this.shouldShowEvent(nextEvent);
if (!hasNextEvent && isSent) { if (!hasNextEvent && isSent) {
@ -869,8 +863,14 @@ export default class MessagePanel extends React.Component<IProps, IState> {
// should be shown next to that event. If a hidden event has read receipts, // should be shown next to that event. If a hidden event has read receipts,
// they are folded into the receipts of the last shown event. // they are folded into the receipts of the last shown event.
private getReadReceiptsByShownEvent(): Record<string, IReadReceiptProps[]> { private getReadReceiptsByShownEvent(): Record<string, IReadReceiptProps[]> {
const receiptsByEvent = {}; const receiptsByEvent: Record<string, IReadReceiptProps[]> = {};
const receiptsByUserId = {}; const receiptsByUserId: Record<
string,
{
lastShownEventId: string;
receipt: IReadReceiptProps;
}
> = {};
let lastShownEventId; let lastShownEventId;
for (const event of this.props.events) { for (const event of this.props.events) {
@ -982,7 +982,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
} }
} }
public render(): JSX.Element { public render(): React.ReactNode {
let topSpinner; let topSpinner;
let bottomSpinner; let bottomSpinner;
if (this.props.backPaginating) { if (this.props.backPaginating) {

View file

@ -27,8 +27,8 @@ interface IState {
} }
export default class NonUrgentToastContainer extends React.PureComponent<IProps, IState> { export default class NonUrgentToastContainer extends React.PureComponent<IProps, IState> {
public constructor(props, context) { public constructor(props: IProps) {
super(props, context); super(props);
this.state = { this.state = {
toasts: NonUrgentToastStore.instance.components, toasts: NonUrgentToastStore.instance.components,
@ -45,7 +45,7 @@ export default class NonUrgentToastContainer extends React.PureComponent<IProps,
this.setState({ toasts: NonUrgentToastStore.instance.components }); this.setState({ toasts: NonUrgentToastStore.instance.components });
}; };
public render(): JSX.Element { public render(): React.ReactNode {
const toasts = this.state.toasts.map((t, i) => { const toasts = this.state.toasts.map((t, i) => {
return ( return (
<div className="mx_NonUrgentToastContainer_toast" key={`toast-${i}`}> <div className="mx_NonUrgentToastContainer_toast" key={`toast-${i}`}>

View file

@ -43,7 +43,7 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
private card = React.createRef<HTMLDivElement>(); private card = React.createRef<HTMLDivElement>();
public constructor(props) { public constructor(props: IProps) {
super(props); super(props);
this.state = { this.state = {
@ -55,7 +55,7 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
this.setState({ narrow }); this.setState({ narrow });
}; };
public render(): JSX.Element { public render(): React.ReactNode {
const emptyState = ( const emptyState = (
<div className="mx_RightPanel_empty mx_NotificationPanel_empty"> <div className="mx_RightPanel_empty mx_NotificationPanel_empty">
<h2>{_t("You're all caught up")}</h2> <h2>{_t("You're all caught up")}</h2>

View file

@ -245,7 +245,7 @@ export default class PictureInPictureDragger extends React.Component<IProps> {
} }
}; };
public render(): JSX.Element { public render(): React.ReactNode {
const style = { const style = {
transform: `translateX(${this.translationX}px) translateY(${this.translationY}px)`, transform: `translateX(${this.translationX}px) translateY(${this.translationY}px)`,
}; };

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { MutableRefObject, useContext, useRef } from "react"; import React, { MutableRefObject, ReactNode, useContext, useRef } from "react";
import { CallEvent, CallState, MatrixCall } from "matrix-js-sdk/src/webrtc/call"; import { CallEvent, CallState, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { Optional } from "matrix-events-sdk"; import { Optional } from "matrix-events-sdk";
@ -288,7 +288,7 @@ class PipContainerInner extends React.Component<IProps, IState> {
); );
} }
public render(): JSX.Element { public render(): ReactNode {
const pipMode = true; const pipMode = true;
let pipContent: Array<CreatePipChildren> = []; let pipContent: Array<CreatePipChildren> = [];

View file

@ -63,7 +63,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
public static contextType = MatrixClientContext; public static contextType = MatrixClientContext;
public context!: React.ContextType<typeof MatrixClientContext>; public context!: React.ContextType<typeof MatrixClientContext>;
public constructor(props, context) { public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
super(props, context); super(props, context);
this.state = { this.state = {
@ -149,7 +149,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
this.setState({ searchQuery }); this.setState({ searchQuery });
}; };
public render(): JSX.Element { public render(): React.ReactNode {
let card = <div />; let card = <div />;
const roomId = this.props.room?.roomId; const roomId = this.props.room?.roomId;
const phase = this.props.overwriteCard?.phase ?? this.state.phase; const phase = this.props.overwriteCard?.phase ?? this.state.phase;

View file

@ -111,11 +111,11 @@ export const RoomSearchView = forwardRef<ScrollPanel, Props>(
); );
if (!bundledRelationship || event.getThread()) continue; if (!bundledRelationship || event.getThread()) continue;
const room = client.getRoom(event.getRoomId()); const room = client.getRoom(event.getRoomId());
const thread = room.findThreadForEvent(event); const thread = room?.findThreadForEvent(event);
if (thread) { if (thread) {
event.setThread(thread); event.setThread(thread);
} else { } else {
room.createThread(event.getId(), event, [], true); room?.createThread(event.getId()!, event, [], true);
} }
} }
} }
@ -214,7 +214,7 @@ export const RoomSearchView = forwardRef<ScrollPanel, Props>(
scrollPanel?.checkScroll(); scrollPanel?.checkScroll();
}; };
let lastRoomId: string; let lastRoomId: string | undefined;
let mergedTimeline: MatrixEvent[] = []; let mergedTimeline: MatrixEvent[] = [];
let ourEventsIndexes: number[] = []; let ourEventsIndexes: number[] = [];

View file

@ -14,10 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { ReactNode } from "react";
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { SyncState, ISyncStateData } from "matrix-js-sdk/src/sync"; import { SyncState, ISyncStateData } from "matrix-js-sdk/src/sync";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixError } from "matrix-js-sdk/src/matrix";
import { _t, _td } from "../../languageHandler"; import { _t, _td } from "../../languageHandler";
import Resend from "../../Resend"; import Resend from "../../Resend";
@ -192,10 +193,10 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
private getUnsentMessageContent(): JSX.Element { private getUnsentMessageContent(): JSX.Element {
const unsentMessages = this.state.unsentMessages; const unsentMessages = this.state.unsentMessages;
let title; let title: ReactNode;
let consentError = null; let consentError: MatrixError | null = null;
let resourceLimitError = null; let resourceLimitError: MatrixError | null = null;
for (const m of unsentMessages) { for (const m of unsentMessages) {
if (m.error && m.error.errcode === "M_CONSENT_NOT_GIVEN") { if (m.error && m.error.errcode === "M_CONSENT_NOT_GIVEN") {
consentError = m.error; consentError = m.error;
@ -212,7 +213,7 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
{}, {},
{ {
consentLink: (sub) => ( consentLink: (sub) => (
<a href={consentError.data && consentError.data.consent_uri} target="_blank"> <a href={consentError!.data?.consent_uri} target="_blank">
{sub} {sub}
</a> </a>
), ),
@ -271,7 +272,7 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
); );
} }
public render(): JSX.Element { public render(): React.ReactNode {
if (this.shouldShowConnectionError()) { if (this.shouldShowConnectionError()) {
return ( return (
<div className="mx_RoomStatusBar"> <div className="mx_RoomStatusBar">

View file

@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { ReactElement } from "react"; import React, { ReactElement, ReactNode } from "react";
import { StaticNotificationState } from "../../stores/notifications/StaticNotificationState"; import { StaticNotificationState } from "../../stores/notifications/StaticNotificationState";
import NotificationBadge from "../views/rooms/NotificationBadge"; import NotificationBadge from "../views/rooms/NotificationBadge";
interface RoomStatusBarUnsentMessagesProps { interface RoomStatusBarUnsentMessagesProps {
title: string; title: ReactNode;
description?: string; description?: string;
notificationState: StaticNotificationState; notificationState: StaticNotificationState;
buttons: ReactElement; buttons: ReactElement;

View file

@ -33,6 +33,7 @@ import { CryptoEvent } from "matrix-js-sdk/src/crypto";
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread"; import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
import { HistoryVisibility } from "matrix-js-sdk/src/@types/partials"; import { HistoryVisibility } from "matrix-js-sdk/src/@types/partials";
import { ISearchResults } from "matrix-js-sdk/src/@types/search"; import { ISearchResults } from "matrix-js-sdk/src/@types/search";
import { IRoomTimelineData } from "matrix-js-sdk/src/models/event-timeline-set";
import shouldHideEvent from "../../shouldHideEvent"; import shouldHideEvent from "../../shouldHideEvent";
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
@ -49,7 +50,7 @@ import RoomScrollStateStore, { ScrollState } from "../../stores/RoomScrollStateS
import WidgetEchoStore from "../../stores/WidgetEchoStore"; import WidgetEchoStore from "../../stores/WidgetEchoStore";
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import { Layout } from "../../settings/enums/Layout"; import { Layout } from "../../settings/enums/Layout";
import AccessibleButton from "../views/elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import { E2EStatus, shieldStatusForRoom } from "../../utils/ShieldUtils"; import { E2EStatus, shieldStatusForRoom } from "../../utils/ShieldUtils";
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
@ -223,6 +224,7 @@ export interface IRoomState {
narrow: boolean; narrow: boolean;
// List of undecryptable events currently visible on-screen // List of undecryptable events currently visible on-screen
visibleDecryptionFailures?: MatrixEvent[]; visibleDecryptionFailures?: MatrixEvent[];
msc3946ProcessDynamicPredecessor: boolean;
} }
interface LocalRoomViewProps { interface LocalRoomViewProps {
@ -416,6 +418,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
liveTimeline: undefined, liveTimeline: undefined,
narrow: false, narrow: false,
visibleDecryptionFailures: [], visibleDecryptionFailures: [],
msc3946ProcessDynamicPredecessor: SettingsStore.getValue("feature_dynamic_room_predecessors"),
}; };
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
@ -467,6 +470,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
), ),
SettingsStore.watchSetting("urlPreviewsEnabled", null, this.onUrlPreviewsEnabledChange), SettingsStore.watchSetting("urlPreviewsEnabled", null, this.onUrlPreviewsEnabledChange),
SettingsStore.watchSetting("urlPreviewsEnabled_e2ee", null, this.onUrlPreviewsEnabledChange), SettingsStore.watchSetting("urlPreviewsEnabled_e2ee", null, this.onUrlPreviewsEnabledChange),
SettingsStore.watchSetting("feature_dynamic_room_predecessors", null, (...[, , , value]) =>
this.setState({ msc3946ProcessDynamicPredecessor: value as boolean }),
),
]; ];
} }
@ -851,7 +857,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
window.addEventListener("beforeunload", this.onPageUnload); window.addEventListener("beforeunload", this.onPageUnload);
} }
public shouldComponentUpdate(nextProps, nextState): boolean { public shouldComponentUpdate(nextProps: IRoomProps, nextState: IRoomState): boolean {
const hasPropsDiff = objectHasDiff(this.props, nextProps); const hasPropsDiff = objectHasDiff(this.props, nextProps);
const { upgradeRecommendation, ...state } = this.state; const { upgradeRecommendation, ...state } = this.state;
@ -953,7 +959,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}); });
}; };
private onPageUnload = (event): string => { private onPageUnload = (event: BeforeUnloadEvent): string => {
if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) { if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) {
return (event.returnValue = _t("You seem to be uploading files, are you sure you want to quit?")); return (event.returnValue = _t("You seem to be uploading files, are you sure you want to quit?"));
} else if (this.getCallForRoom() && this.state.callState !== "ended") { } else if (this.getCallForRoom() && this.state.callState !== "ended") {
@ -961,7 +967,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
} }
}; };
private onReactKeyDown = (ev): void => { private onReactKeyDown = (ev: React.KeyboardEvent): void => {
let handled = false; let handled = false;
const action = getKeyBindingsManager().getRoomAction(ev); const action = getKeyBindingsManager().getRoomAction(ev);
@ -1125,7 +1131,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
createRoomFromLocalRoom(this.context.client, this.state.room as LocalRoom); createRoomFromLocalRoom(this.context.client, this.state.room as LocalRoom);
} }
private onRoomTimeline = (ev: MatrixEvent, room: Room | null, toStartOfTimeline: boolean, removed, data): void => { private onRoomTimeline = (
ev: MatrixEvent,
room: Room | null,
toStartOfTimeline: boolean,
removed: boolean,
data?: IRoomTimelineData,
): void => {
if (this.unmounted) return; if (this.unmounted) return;
// ignore events for other rooms or the notification timeline set // ignore events for other rooms or the notification timeline set
@ -1145,7 +1157,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// ignore anything but real-time updates at the end of the room: // ignore anything but real-time updates at the end of the room:
// updates from pagination will happen when the paginate completes. // updates from pagination will happen when the paginate completes.
if (toStartOfTimeline || !data || !data.liveEvent) return; if (toStartOfTimeline || !data?.liveEvent) return;
// no point handling anything while we're waiting for the join to finish: // no point handling anything while we're waiting for the join to finish:
// we'll only be showing a spinner. // we'll only be showing a spinner.
@ -1697,7 +1709,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}; };
// update the read marker to match the read-receipt // update the read marker to match the read-receipt
private forgetReadMarker = (ev): void => { private forgetReadMarker = (ev: ButtonEvent): void => {
ev.stopPropagation(); ev.stopPropagation();
this.messagePanel.forgetReadMarker(); this.messagePanel.forgetReadMarker();
}; };
@ -1770,7 +1782,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
* *
* We pass it down to the scroll panel. * We pass it down to the scroll panel.
*/ */
public handleScrollKey = (ev): void => { public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => {
let panel: ScrollPanel | TimelinePanel; let panel: ScrollPanel | TimelinePanel;
if (this.searchResultsPanel.current) { if (this.searchResultsPanel.current) {
panel = this.searchResultsPanel.current; panel = this.searchResultsPanel.current;
@ -1793,15 +1805,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// this has to be a proper method rather than an unnamed function, // this has to be a proper method rather than an unnamed function,
// otherwise react calls it with null on each update. // otherwise react calls it with null on each update.
private gatherTimelinePanelRef = (r): void => { private gatherTimelinePanelRef = (r?: TimelinePanel): void => {
this.messagePanel = r; this.messagePanel = r;
}; };
private getOldRoom(): Room | null { private getOldRoom(): Room | null {
const createEvent = this.state.room.currentState.getStateEvents(EventType.RoomCreate, ""); const { roomId } = this.state.room?.findPredecessor(this.state.msc3946ProcessDynamicPredecessor) || {};
if (!createEvent || !createEvent.getContent()["predecessor"]) return null; return this.context.client?.getRoom(roomId) || null;
return this.context.client.getRoom(createEvent.getContent()["predecessor"]["room_id"]);
} }
public getHiddenHighlightCount(): number { public getHiddenHighlightCount(): number {
@ -1869,7 +1879,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
); );
} }
public render(): JSX.Element { public render(): React.ReactNode {
if (this.state.room instanceof LocalRoom) { if (this.state.room instanceof LocalRoom) {
if (this.state.room.state === LocalRoomState.CREATING) { if (this.state.room.state === LocalRoomState.CREATING) {
return this.renderLocalRoomCreateLoader(); return this.renderLocalRoomCreateLoader();

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef, CSSProperties, ReactNode, KeyboardEvent } from "react"; import React, { createRef, CSSProperties, ReactNode } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
@ -195,8 +195,8 @@ export default class ScrollPanel extends React.Component<IProps> {
private heightUpdateInProgress: boolean; private heightUpdateInProgress: boolean;
private divScroll: HTMLDivElement; private divScroll: HTMLDivElement;
public constructor(props, context) { public constructor(props: IProps) {
super(props, context); super(props);
this.props.resizeNotifier?.on("middlePanelResizedNoisy", this.onResize); this.props.resizeNotifier?.on("middlePanelResizedNoisy", this.onResize);
@ -440,9 +440,9 @@ export default class ScrollPanel extends React.Component<IProps> {
// pagination. // pagination.
// //
// If backwards is true, we unpaginate (remove) tiles from the back (top). // If backwards is true, we unpaginate (remove) tiles from the back (top).
let tile; let tile: HTMLElement;
for (let i = 0; i < tiles.length; i++) { for (let i = 0; i < tiles.length; i++) {
tile = tiles[backwards ? i : tiles.length - 1 - i]; tile = tiles[backwards ? i : tiles.length - 1 - i] as HTMLElement;
// Subtract height of tile as if it were unpaginated // Subtract height of tile as if it were unpaginated
excessHeight -= tile.clientHeight; excessHeight -= tile.clientHeight;
//If removing the tile would lead to future pagination, break before setting scroll token //If removing the tile would lead to future pagination, break before setting scroll token
@ -587,7 +587,7 @@ export default class ScrollPanel extends React.Component<IProps> {
* Scroll up/down in response to a scroll key * Scroll up/down in response to a scroll key
* @param {object} ev the keyboard event * @param {object} ev the keyboard event
*/ */
public handleScrollKey = (ev: KeyboardEvent): void => { public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => {
const roomAction = getKeyBindingsManager().getRoomAction(ev); const roomAction = getKeyBindingsManager().getRoomAction(ev);
switch (roomAction) { switch (roomAction) {
case KeyBindingAction.ScrollUp: case KeyBindingAction.ScrollUp:

View file

@ -24,7 +24,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
interface IProps extends HTMLProps<HTMLInputElement> { interface IProps extends HTMLProps<HTMLInputElement> {
onSearch?: (query: string) => void; onSearch: (query: string) => void;
onCleared?: (source?: string) => void; onCleared?: (source?: string) => void;
onKeyDown?: (ev: React.KeyboardEvent) => void; onKeyDown?: (ev: React.KeyboardEvent) => void;
onFocus?: (ev: React.FocusEvent) => void; onFocus?: (ev: React.FocusEvent) => void;
@ -62,7 +62,7 @@ export default class SearchBox extends React.Component<IProps, IState> {
private onSearch = throttle( private onSearch = throttle(
(): void => { (): void => {
this.props.onSearch(this.search.current.value); this.props.onSearch(this.search.current?.value);
}, },
200, 200,
{ trailing: true, leading: true }, { trailing: true, leading: true },
@ -101,7 +101,7 @@ export default class SearchBox extends React.Component<IProps, IState> {
} }
} }
public render(): JSX.Element { public render(): React.ReactNode {
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */ /* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
const { const {
onSearch, onSearch,

View file

@ -280,7 +280,7 @@ const Tile: React.FC<ITileProps> = ({
); );
if (showChildren) { if (showChildren) {
const onChildrenKeyDown = (e): void => { const onChildrenKeyDown = (e: React.KeyboardEvent): void => {
const action = getKeyBindingsManager().getAccessibilityAction(e); const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) { switch (action) {
case KeyBindingAction.ArrowLeft: case KeyBindingAction.ArrowLeft:

View file

@ -318,7 +318,7 @@ const SpaceSetupFirstRooms: React.FC<{
label={_t("Room name")} label={_t("Room name")}
placeholder={placeholders[i]} placeholder={placeholders[i]}
value={roomNames[i]} value={roomNames[i]}
onChange={(ev) => setRoomName(i, ev.target.value)} onChange={(ev: React.ChangeEvent<HTMLInputElement>) => setRoomName(i, ev.target.value)}
autoFocus={i === 2} autoFocus={i === 2}
disabled={busy} disabled={busy}
autoComplete="off" autoComplete="off"
@ -814,7 +814,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
} }
} }
public render(): JSX.Element { public render(): React.ReactNode {
const rightPanel = const rightPanel =
this.state.showRightPanel && this.state.phase === Phase.Landing ? ( this.state.showRightPanel && this.state.phase === Phase.Landing ? (
<RightPanel room={this.props.space} resizeNotifier={this.props.resizeNotifier} /> <RightPanel room={this.props.space} resizeNotifier={this.props.resizeNotifier} />

View file

@ -137,7 +137,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
}); });
} }
public componentDidUpdate(prevProps): void { public componentDidUpdate(prevProps: IProps): void {
if (prevProps.mxEvent !== this.props.mxEvent) { if (prevProps.mxEvent !== this.props.mxEvent) {
this.setupThread(this.props.mxEvent); this.setupThread(this.props.mxEvent);
} }
@ -316,7 +316,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
}; };
private get threadRelation(): IEventRelation { private get threadRelation(): IEventRelation {
const relation = { const relation: IEventRelation = {
rel_type: THREAD_RELATION_TYPE.name, rel_type: THREAD_RELATION_TYPE.name,
event_id: this.state.thread?.id, event_id: this.state.thread?.id,
is_falling_back: true, is_falling_back: true,
@ -343,7 +343,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
); );
}; };
public render(): JSX.Element { public render(): React.ReactNode {
const highlightedEventId = this.props.isInitialEventHighlighted ? this.props.initialEvent?.getId() : null; const highlightedEventId = this.props.isInitialEventHighlighted ? this.props.initialEvent?.getId() : null;
const threadRelation = this.threadRelation; const threadRelation = this.threadRelation;

View file

@ -1316,7 +1316,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
* *
* We pass it down to the scroll panel. * We pass it down to the scroll panel.
*/ */
public handleScrollKey = (ev: React.KeyboardEvent): void => { public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => {
if (!this.messagePanel.current) return; if (!this.messagePanel.current) return;
// jump to the live timeline on ctrl-end, rather than the end of the // jump to the live timeline on ctrl-end, rather than the end of the
@ -1886,7 +1886,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
this.callEventGroupers = buildLegacyCallEventGroupers(this.callEventGroupers, events); this.callEventGroupers = buildLegacyCallEventGroupers(this.callEventGroupers, events);
} }
public render(): JSX.Element { public render(): React.ReactNode {
// just show a spinner while the timeline loads. // just show a spinner while the timeline loads.
// //
// put it in a div of the right class (mx_RoomView_messagePanel) so // put it in a div of the right class (mx_RoomView_messagePanel) so
@ -1977,9 +1977,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
* *
* @return An event ID list for every timeline in every timelineSet * @return An event ID list for every timeline in every timelineSet
*/ */
function serializeEventIdsFromTimelineSets(timelineSets): { [key: string]: string[] }[] { function serializeEventIdsFromTimelineSets(timelineSets: EventTimelineSet[]): { [key: string]: string[] }[] {
const serializedEventIdsInTimelineSet = timelineSets.map((timelineSet) => { const serializedEventIdsInTimelineSet = timelineSets.map((timelineSet) => {
const timelineMap = {}; const timelineMap: Record<string, string[]> = {};
const timelines = timelineSet.getTimelines(); const timelines = timelineSet.getTimelines();
const liveTimeline = timelineSet.getLiveTimeline(); const liveTimeline = timelineSet.getLiveTimeline();

View file

@ -25,8 +25,8 @@ interface IState {
} }
export default class ToastContainer extends React.Component<{}, IState> { export default class ToastContainer extends React.Component<{}, IState> {
public constructor(props, context) { public constructor(props: {}) {
super(props, context); super(props);
this.state = { this.state = {
toasts: ToastStore.sharedInstance().getToasts(), toasts: ToastStore.sharedInstance().getToasts(),
countSeen: ToastStore.sharedInstance().getCountSeen(), countSeen: ToastStore.sharedInstance().getCountSeen(),
@ -50,7 +50,7 @@ export default class ToastContainer extends React.Component<{}, IState> {
}); });
}; };
public render(): JSX.Element { public render(): React.ReactNode {
const totalCount = this.state.toasts.length; const totalCount = this.state.toasts.length;
const isStacked = totalCount > 1; const isStacked = totalCount > 1;
let toast; let toast;

View file

@ -57,7 +57,7 @@ export default class UploadBar extends React.PureComponent<IProps, IState> {
private dispatcherRef: Optional<string>; private dispatcherRef: Optional<string>;
private mounted = false; private mounted = false;
public constructor(props) { public constructor(props: IProps) {
super(props); super(props);
// Set initial state to any available upload in this room - we might be mounting // Set initial state to any available upload in this room - we might be mounting
@ -103,7 +103,7 @@ export default class UploadBar extends React.PureComponent<IProps, IState> {
ContentMessages.sharedInstance().cancelUpload(this.state.currentUpload!); ContentMessages.sharedInstance().cancelUpload(this.state.currentUpload!);
}; };
public render(): JSX.Element { public render(): React.ReactNode {
if (!this.state.currentFile) { if (!this.state.currentFile) {
return null; return null;
} }

View file

@ -429,7 +429,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
); );
}; };
public render(): JSX.Element { public render(): React.ReactNode {
const avatarSize = 32; // should match border-radius of the avatar const avatarSize = 32; // should match border-radius of the avatar
const userId = MatrixClientPeg.get().getUserId(); const userId = MatrixClientPeg.get().getUserId();

View file

@ -18,6 +18,7 @@ limitations under the License.
import React from "react"; import React from "react";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "../../MatrixClientPeg"; import { MatrixClientPeg } from "../../MatrixClientPeg";
import Modal from "../../Modal"; import Modal from "../../Modal";
@ -31,7 +32,7 @@ import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases
import { UserOnboardingPage } from "../views/user-onboarding/UserOnboardingPage"; import { UserOnboardingPage } from "../views/user-onboarding/UserOnboardingPage";
interface IProps { interface IProps {
userId?: string; userId: string;
resizeNotifier: ResizeNotifier; resizeNotifier: ResizeNotifier;
} }
@ -66,7 +67,7 @@ export default class UserView extends React.Component<IProps, IState> {
private async loadProfileInfo(): Promise<void> { private async loadProfileInfo(): Promise<void> {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
this.setState({ loading: true }); this.setState({ loading: true });
let profileInfo; let profileInfo: Awaited<ReturnType<MatrixClient["getProfileInfo"]>>;
try { try {
profileInfo = await cli.getProfileInfo(this.props.userId); profileInfo = await cli.getProfileInfo(this.props.userId);
} catch (err) { } catch (err) {
@ -83,7 +84,7 @@ export default class UserView extends React.Component<IProps, IState> {
this.setState({ member, loading: false }); this.setState({ member, loading: false });
} }
public render(): JSX.Element { public render(): React.ReactNode {
if (this.state.loading) { if (this.state.loading) {
return <Spinner />; return <Spinner />;
} else if (this.state.member) { } else if (this.state.member) {

View file

@ -142,7 +142,7 @@ export default class ViewSource extends React.Component<IProps, IState> {
return room.currentState.mayClientSendStateEvent(mxEvent.getType(), cli); return room.currentState.mayClientSendStateEvent(mxEvent.getType(), cli);
} }
public render(): JSX.Element { public render(): React.ReactNode {
const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit
const isEditing = this.state.isEditing; const isEditing = this.state.isEditing;

View file

@ -57,7 +57,7 @@ export default class CompleteSecurity extends React.Component<IProps, IState> {
store.stop(); store.stop();
} }
public render(): JSX.Element { public render(): React.ReactNode {
const { phase, lostKeys } = this.state; const { phase, lostKeys } = this.state;
let icon; let icon;
let title; let title;

View file

@ -27,7 +27,7 @@ interface IProps {
} }
export default class E2eSetup extends React.Component<IProps> { export default class E2eSetup extends React.Component<IProps> {
public render(): JSX.Element { public render(): React.ReactNode {
return ( return (
<AuthPage> <AuthPage>
<CompleteSecurityBody> <CompleteSecurityBody>

Some files were not shown because too many files have changed in this diff Show more