Comply with noImplicitAny (#9940)

* Stash noImplicitAny work

* Stash

* Fix imports

* Iterate

* Fix tests

* Delint

* Fix tests
This commit is contained in:
Michael Telatynski 2023-02-13 11:39:16 +00:00 committed by GitHub
parent ac7f69216e
commit 61a63e47f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
359 changed files with 1621 additions and 1353 deletions

View file

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

View file

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

65
src/@types/opus-recorder.d.ts vendored Normal file
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.
*/
import { IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix";
import { IAuthData, IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "./MatrixClientPeg";
import Modal from "./Modal";
@ -29,6 +29,12 @@ function getIdServerDomain(): string {
return MatrixClientPeg.get().idBaseUrl.split("://")[1];
}
export type Binding = {
bind: boolean;
label: string;
errorTitle: string;
};
/**
* Allows a user to add a third party identifier to their homeserver and,
* optionally, the identity servers.
@ -178,7 +184,7 @@ export default class AddThreepid {
* with a "message" property which contains a human-readable message detailing why
* the request failed.
*/
public async checkEmailLinkClicked(): Promise<any[]> {
public async checkEmailLinkClicked(): Promise<[boolean, IAuthData | Error | null]> {
try {
if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) {
if (this.bind) {
@ -220,16 +226,19 @@ export default class AddThreepid {
continueKind: "primary",
},
};
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
title: _t("Add Email Address"),
matrixClient: MatrixClientPeg.get(),
authData: e.data,
makeRequest: this.makeAddThreepidOnlyRequest,
aestheticsForStagePhases: {
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
const { finished } = Modal.createDialog<[boolean, IAuthData | Error | null]>(
InteractiveAuthDialog,
{
title: _t("Add Email Address"),
matrixClient: MatrixClientPeg.get(),
authData: e.data,
makeRequest: this.makeAddThreepidOnlyRequest,
aestheticsForStagePhases: {
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
},
},
});
);
return finished;
}
}

View file

@ -42,10 +42,7 @@ interface IState {
export default class AsyncWrapper extends React.Component<IProps, IState> {
private unmounted = false;
public state = {
component: null,
error: null,
};
public state: IState = {};
public componentDidMount(): void {
// XXX: temporary logging to try to diagnose

View file

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

View file

@ -228,7 +228,7 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = {
// Sanitise and transform data-mx-color and data-mx-bg-color to their CSS
// equivalents
const customCSSMapper = {
const customCSSMapper: Record<string, string> = {
"data-mx-color": "color",
"data-mx-bg-color": "background-color",
// $customAttributeKey: $cssAttributeKey

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

@ -697,11 +697,8 @@ export const Commands = [
}
if (viaServers) {
// For the join
dispatch["opts"] = {
// These are passed down to the js-sdk's /join call
viaServers: viaServers,
};
// For the join, these are passed down to the js-sdk's /join call
dispatch["opts"] = { viaServers };
// For if the join fails (rejoin button)
dispatch["via_servers"] = viaServers;

View file

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

View file

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

View file

@ -168,7 +168,7 @@ export default class UserActivity {
return this.activeRecentlyTimeout.isRunning();
}
private onPageVisibilityChanged = (e): void => {
private onPageVisibilityChanged = (e: Event): void => {
if (this.document.visibilityState === "hidden") {
this.activeNowTimeout.abort();
this.activeRecentlyTimeout.abort();
@ -182,7 +182,8 @@ export default class UserActivity {
this.activeRecentlyTimeout.abort();
};
private onUserActivity = (event: Event): void => {
// XXX: exported for tests
public onUserActivity = (event: Event): void => {
// ignore anything if the window isn't focused
if (!this.document.hasFocus()) return;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -73,9 +73,9 @@ export default class ImportE2eKeysDialog extends React.Component<IProps, IState>
}
private onFormChange = (): void => {
const files = this.file.current.files || [];
const files = this.file.current.files;
this.setState({
enableSubmit: this.state.passphrase !== "" && files.length > 0,
enableSubmit: this.state.passphrase !== "" && !!files?.length,
});
};

View file

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

View file

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// @ts-ignore
import Recorder from "opus-recorder/dist/recorder.min.js";
import encoderPath from "opus-recorder/dist/encoderWorker.min.js";
import { SimpleObservable } from "matrix-widget-api";

View file

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

View file

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

View file

@ -110,17 +110,14 @@ export default class UserProvider extends AutocompleteProvider {
// lazy-load user list into matcher
if (!this.users) this.makeUsers();
let completions = [];
const { command, range } = this.getCurrentCommand(rawQuery, selection, force);
if (!command) return completions;
const fullMatch = command[0];
const fullMatch = command?.[0];
// Don't search if the query is a single "@"
if (fullMatch && fullMatch !== "@") {
// Don't include the '@' in our search query - it's only used as a way to trigger completion
const query = fullMatch.startsWith("@") ? fullMatch.substring(1) : fullMatch;
completions = this.matcher.match(query, limit).map((user) => {
return this.matcher.match(query, limit).map((user) => {
const description = UserIdentifierCustomisations.getDisplayUserIdentifier(user.userId, {
roomId: this.room.roomId,
withDisplayName: true,
@ -143,7 +140,7 @@ export default class UserProvider extends AutocompleteProvider {
};
});
}
return completions;
return [];
}
public getName(): string {
@ -152,7 +149,7 @@ export default class UserProvider extends AutocompleteProvider {
private makeUsers(): void {
const events = this.room.getLiveTimeline().getEvents();
const lastSpoken = {};
const lastSpoken: Record<string, number> = {};
for (const event of events) {
lastSpoken[event.getSender()] = event.getTs();

View file

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

View file

@ -43,7 +43,7 @@ interface IProps {
}
interface IState {
timelineSet: EventTimelineSet;
timelineSet: EventTimelineSet | null;
narrow: boolean;
}
@ -59,7 +59,7 @@ class FilePanel extends React.Component<IProps, IState> {
public noRoom: boolean;
private card = createRef<HTMLDivElement>();
public state = {
public state: IState = {
timelineSet: null,
narrow: false,
};

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -27,8 +27,8 @@ interface IState {
}
export default class NonUrgentToastContainer extends React.PureComponent<IProps, IState> {
public constructor(props, context) {
super(props, context);
public constructor(props: IProps) {
super(props);
this.state = {
toasts: NonUrgentToastStore.instance.components,

View file

@ -43,7 +43,7 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
private card = React.createRef<HTMLDivElement>();
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.state = {

View file

@ -63,7 +63,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
public static contextType = MatrixClientContext;
public context!: React.ContextType<typeof MatrixClientContext>;
public constructor(props, context) {
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
super(props, context);
this.state = {

View file

@ -14,10 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import React, { ReactNode } from "react";
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { SyncState, ISyncStateData } from "matrix-js-sdk/src/sync";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixError } from "matrix-js-sdk/src/matrix";
import { _t, _td } from "../../languageHandler";
import Resend from "../../Resend";
@ -192,10 +193,10 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
private getUnsentMessageContent(): JSX.Element {
const unsentMessages = this.state.unsentMessages;
let title;
let title: ReactNode;
let consentError = null;
let resourceLimitError = null;
let consentError: MatrixError | null = null;
let resourceLimitError: MatrixError | null = null;
for (const m of unsentMessages) {
if (m.error && m.error.errcode === "M_CONSENT_NOT_GIVEN") {
consentError = m.error;

View file

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

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 { HistoryVisibility } from "matrix-js-sdk/src/@types/partials";
import { ISearchResults } from "matrix-js-sdk/src/@types/search";
import { IRoomTimelineData } from "matrix-js-sdk/src/models/event-timeline-set";
import shouldHideEvent from "../../shouldHideEvent";
import { _t } from "../../languageHandler";
@ -49,7 +50,7 @@ import RoomScrollStateStore, { ScrollState } from "../../stores/RoomScrollStateS
import WidgetEchoStore from "../../stores/WidgetEchoStore";
import SettingsStore from "../../settings/SettingsStore";
import { Layout } from "../../settings/enums/Layout";
import AccessibleButton from "../views/elements/AccessibleButton";
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import { E2EStatus, shieldStatusForRoom } from "../../utils/ShieldUtils";
import { Action } from "../../dispatcher/actions";
@ -856,7 +857,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
window.addEventListener("beforeunload", this.onPageUnload);
}
public shouldComponentUpdate(nextProps, nextState): boolean {
public shouldComponentUpdate(nextProps: IRoomProps, nextState: IRoomState): boolean {
const hasPropsDiff = objectHasDiff(this.props, nextProps);
const { upgradeRecommendation, ...state } = this.state;
@ -958,7 +959,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
});
};
private onPageUnload = (event): string => {
private onPageUnload = (event: BeforeUnloadEvent): string => {
if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) {
return (event.returnValue = _t("You seem to be uploading files, are you sure you want to quit?"));
} else if (this.getCallForRoom() && this.state.callState !== "ended") {
@ -966,7 +967,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}
};
private onReactKeyDown = (ev): void => {
private onReactKeyDown = (ev: React.KeyboardEvent): void => {
let handled = false;
const action = getKeyBindingsManager().getRoomAction(ev);
@ -1130,7 +1131,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
createRoomFromLocalRoom(this.context.client, this.state.room as LocalRoom);
}
private onRoomTimeline = (ev: MatrixEvent, room: Room | null, toStartOfTimeline: boolean, removed, data): void => {
private onRoomTimeline = (
ev: MatrixEvent,
room: Room | null,
toStartOfTimeline: boolean,
removed: boolean,
data?: IRoomTimelineData,
): void => {
if (this.unmounted) return;
// ignore events for other rooms or the notification timeline set
@ -1150,7 +1157,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// ignore anything but real-time updates at the end of the room:
// updates from pagination will happen when the paginate completes.
if (toStartOfTimeline || !data || !data.liveEvent) return;
if (toStartOfTimeline || !data?.liveEvent) return;
// no point handling anything while we're waiting for the join to finish:
// we'll only be showing a spinner.
@ -1702,7 +1709,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
};
// update the read marker to match the read-receipt
private forgetReadMarker = (ev): void => {
private forgetReadMarker = (ev: ButtonEvent): void => {
ev.stopPropagation();
this.messagePanel.forgetReadMarker();
};
@ -1775,7 +1782,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
*
* We pass it down to the scroll panel.
*/
public handleScrollKey = (ev): void => {
public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => {
let panel: ScrollPanel | TimelinePanel;
if (this.searchResultsPanel.current) {
panel = this.searchResultsPanel.current;
@ -1798,7 +1805,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// this has to be a proper method rather than an unnamed function,
// otherwise react calls it with null on each update.
private gatherTimelinePanelRef = (r): void => {
private gatherTimelinePanelRef = (r?: TimelinePanel): void => {
this.messagePanel = r;
};

View file

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

View file

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

View file

@ -318,7 +318,7 @@ const SpaceSetupFirstRooms: React.FC<{
label={_t("Room name")}
placeholder={placeholders[i]}
value={roomNames[i]}
onChange={(ev) => setRoomName(i, ev.target.value)}
onChange={(ev: React.ChangeEvent<HTMLInputElement>) => setRoomName(i, ev.target.value)}
autoFocus={i === 2}
disabled={busy}
autoComplete="off"

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) {
this.setupThread(this.props.mxEvent);
}
@ -316,7 +316,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
};
private get threadRelation(): IEventRelation {
const relation = {
const relation: IEventRelation = {
rel_type: THREAD_RELATION_TYPE.name,
event_id: this.state.thread?.id,
is_falling_back: true,

View file

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

View file

@ -25,8 +25,8 @@ interface IState {
}
export default class ToastContainer extends React.Component<{}, IState> {
public constructor(props, context) {
super(props, context);
public constructor(props: {}) {
super(props);
this.state = {
toasts: ToastStore.sharedInstance().getToasts(),
countSeen: ToastStore.sharedInstance().getCountSeen(),

View file

@ -57,7 +57,7 @@ export default class UploadBar extends React.PureComponent<IProps, IState> {
private dispatcherRef: Optional<string>;
private mounted = false;
public constructor(props) {
public constructor(props: IProps) {
super(props);
// Set initial state to any available upload in this room - we might be mounting

View file

@ -37,7 +37,7 @@ import SSOButtons from "../../views/elements/SSOButtons";
import ServerPicker from "../../views/elements/ServerPicker";
import AuthBody from "../../views/auth/AuthBody";
import AuthHeader from "../../views/auth/AuthHeader";
import AccessibleButton from "../../views/elements/AccessibleButton";
import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
// These are used in several places, and come from the js-sdk's autodiscovery
@ -101,6 +101,11 @@ interface IState {
serverDeadError?: ReactNode;
}
type OnPasswordLogin = {
(username: string, phoneCountry: undefined, phoneNumber: undefined, password: string): Promise<void>;
(username: undefined, phoneCountry: string, phoneNumber: string, password: string): Promise<void>;
};
/*
* A wire component which glues together login UI components and Login logic
*/
@ -110,7 +115,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
private readonly stepRendererMap: Record<string, () => ReactNode>;
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.state = {
@ -152,7 +157,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
this.unmounted = true;
}
public componentDidUpdate(prevProps): void {
public componentDidUpdate(prevProps: IProps): void {
if (
prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl ||
prevProps.serverConfig.isUrl !== this.props.serverConfig.isUrl
@ -164,7 +169,12 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
public isBusy = (): boolean => this.state.busy || this.props.busy;
public onPasswordLogin = async (username, phoneCountry, phoneNumber, password): Promise<void> => {
public onPasswordLogin: OnPasswordLogin = async (
username: string | undefined,
phoneCountry: string | undefined,
phoneNumber: string | undefined,
password: string,
): Promise<void> => {
if (!this.state.serverIsAlive) {
this.setState({ busy: true });
// Do a quick liveliness check on the URLs
@ -207,10 +217,10 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
if (this.unmounted) {
return;
}
let errorText;
let errorText: ReactNode;
// Some error strings only apply for logging in
const usingEmail = username.indexOf("@") > 0;
const usingEmail = username?.indexOf("@") > 0;
if (error.httpStatus === 400 && usingEmail) {
errorText = _t("This homeserver does not support login using email address.");
} else if (error.errcode === "M_RESOURCE_LIMIT_EXCEEDED") {
@ -264,11 +274,11 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
);
};
public onUsernameChanged = (username): void => {
this.setState({ username: username });
public onUsernameChanged = (username: string): void => {
this.setState({ username });
};
public onUsernameBlur = async (username): Promise<void> => {
public onUsernameBlur = async (username: string): Promise<void> => {
const doWellknownLookup = username[0] === "@";
this.setState({
username: username,
@ -315,23 +325,21 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
}
};
public onPhoneCountryChanged = (phoneCountry): void => {
this.setState({ phoneCountry: phoneCountry });
public onPhoneCountryChanged = (phoneCountry: string): void => {
this.setState({ phoneCountry });
};
public onPhoneNumberChanged = (phoneNumber): void => {
this.setState({
phoneNumber: phoneNumber,
});
public onPhoneNumberChanged = (phoneNumber: string): void => {
this.setState({ phoneNumber });
};
public onRegisterClick = (ev): void => {
public onRegisterClick = (ev: ButtonEvent): void => {
ev.preventDefault();
ev.stopPropagation();
this.props.onRegisterClick();
};
public onTryRegisterClick = (ev): void => {
public onTryRegisterClick = (ev: ButtonEvent): void => {
const hasPasswordFlow = this.state.flows?.find((flow) => flow.type === "m.login.password");
const ssoFlow = this.state.flows?.find((flow) => flow.type === "m.login.sso" || flow.type === "m.login.cas");
// If has no password flow but an SSO flow guess that the user wants to register with SSO.
@ -540,7 +548,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
);
};
private renderSsoStep = (loginType): JSX.Element => {
private renderSsoStep = (loginType: "cas" | "sso"): JSX.Element => {
const flow = this.state.flows.find((flow) => flow.type === "m.login." + loginType) as ISSOFlow;
return (

View file

@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { AuthType, createClient, IAuthData, IInputs } from "matrix-js-sdk/src/matrix";
import { AuthType, createClient, IAuthData, IInputs, MatrixError } from "matrix-js-sdk/src/matrix";
import React, { Fragment, ReactNode } from "react";
import { IRequestTokenResponse, MatrixClient } from "matrix-js-sdk/src/client";
import { IRegisterRequestParams, IRequestTokenResponse, MatrixClient } from "matrix-js-sdk/src/client";
import classNames from "classnames";
import { logger } from "matrix-js-sdk/src/logger";
import { ISSOFlow, SSOAction } from "matrix-js-sdk/src/@types/auth";
@ -125,7 +125,7 @@ export default class Registration extends React.Component<IProps, IState> {
// `replaceClient` tracks latest serverConfig to spot when it changes under the async method which fetches flows
private latestServerConfig: ValidatedServerConfig;
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.state = {
@ -166,7 +166,7 @@ export default class Registration extends React.Component<IProps, IState> {
}
};
public componentDidUpdate(prevProps): void {
public componentDidUpdate(prevProps: IProps): void {
if (
prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl ||
prevProps.serverConfig.isUrl !== this.props.serverConfig.isUrl
@ -307,7 +307,7 @@ export default class Registration extends React.Component<IProps, IState> {
if (!success) {
let errorText: ReactNode = (response as Error).message || (response as Error).toString();
// can we give a better error message?
if (response.errcode === "M_RESOURCE_LIMIT_EXCEEDED") {
if (response instanceof MatrixError && response.errcode === "M_RESOURCE_LIMIT_EXCEEDED") {
const errorTop = messageForResourceLimitError(response.data.limit_type, response.data.admin_contact, {
"monthly_active_user": _td("This homeserver has hit its Monthly Active User limit."),
"hs_blocked": _td("This homeserver has been blocked by its administrator."),
@ -326,17 +326,17 @@ export default class Registration extends React.Component<IProps, IState> {
<p>{errorDetail}</p>
</div>
);
} else if (response.required_stages && response.required_stages.includes(AuthType.Msisdn)) {
} else if ((response as IAuthData).required_stages?.includes(AuthType.Msisdn)) {
let msisdnAvailable = false;
for (const flow of response.available_flows) {
for (const flow of (response as IAuthData).available_flows) {
msisdnAvailable = msisdnAvailable || flow.stages.includes(AuthType.Msisdn);
}
if (!msisdnAvailable) {
errorText = _t("This server does not support authentication with a phone number.");
}
} else if (response.errcode === "M_USER_IN_USE") {
} else if (response instanceof MatrixError && response.errcode === "M_USER_IN_USE") {
errorText = _t("Someone already has that username, please try another.");
} else if (response.errcode === "M_THREEPID_IN_USE") {
} else if (response instanceof MatrixError && response.errcode === "M_THREEPID_IN_USE") {
errorText = _t("That e-mail address or phone number is already in use.");
}
@ -348,11 +348,11 @@ export default class Registration extends React.Component<IProps, IState> {
return;
}
MatrixClientPeg.setJustRegisteredUserId(response.user_id);
MatrixClientPeg.setJustRegisteredUserId((response as IAuthData).user_id);
const newState = {
const newState: Partial<IState> = {
doingUIAuth: false,
registeredUsername: response.user_id,
registeredUsername: (response as IAuthData).user_id,
differentLoggedInUserId: null,
completedNoSignin: false,
// we're still busy until we get unmounted: don't show the registration form again
@ -365,8 +365,10 @@ export default class Registration extends React.Component<IProps, IState> {
// starting the registration process. This isn't perfect since it's possible
// the user had a separate guest session they didn't actually mean to replace.
const [sessionOwner, sessionIsGuest] = await Lifecycle.getStoredSessionOwner();
if (sessionOwner && !sessionIsGuest && sessionOwner !== response.user_id) {
logger.log(`Found a session for ${sessionOwner} but ${response.user_id} has just registered.`);
if (sessionOwner && !sessionIsGuest && sessionOwner !== (response as IAuthData).user_id) {
logger.log(
`Found a session for ${sessionOwner} but ${(response as IAuthData).user_id} has just registered.`,
);
newState.differentLoggedInUserId = sessionOwner;
}
@ -383,7 +385,7 @@ export default class Registration extends React.Component<IProps, IState> {
// as the client that started registration may be gone by the time we've verified the email, and only the client
// that verified the email is guaranteed to exist, we'll always do the login in that client.
const hasEmail = Boolean(this.state.formVals.email);
const hasAccessToken = Boolean(response.access_token);
const hasAccessToken = Boolean((response as IAuthData).access_token);
debuglog("Registration: ui auth finished:", { hasEmail, hasAccessToken });
// dont log in if we found a session for a different user
if (!hasEmail && hasAccessToken && !newState.differentLoggedInUserId) {
@ -391,11 +393,11 @@ export default class Registration extends React.Component<IProps, IState> {
// the email, not the client that started the registration flow
await this.props.onLoggedIn(
{
userId: response.user_id,
deviceId: response.device_id,
userId: (response as IAuthData).user_id,
deviceId: (response as IAuthData).device_id,
homeserverUrl: this.state.matrixClient.getHomeserverUrl(),
identityServerUrl: this.state.matrixClient.getIdentityServerUrl(),
accessToken: response.access_token,
accessToken: (response as IAuthData).access_token,
},
this.state.formVals.password,
);
@ -406,7 +408,7 @@ export default class Registration extends React.Component<IProps, IState> {
newState.completedNoSignin = true;
}
this.setState(newState);
this.setState(newState as IState);
};
private setupPushers(): Promise<void> {
@ -455,7 +457,7 @@ export default class Registration extends React.Component<IProps, IState> {
};
private makeRegisterRequest = (auth: IAuthData | null): Promise<IAuthData> => {
const registerParams = {
const registerParams: IRegisterRequestParams = {
username: this.state.formVals.username,
password: this.state.formVals.password,
initial_device_display_name: this.props.defaultDeviceDisplayName,

View file

@ -45,7 +45,7 @@ interface IState {
}
export default class SetupEncryptionBody extends React.Component<IProps, IState> {
public constructor(props) {
public constructor(props: IProps) {
super(props);
const store = SetupEncryptionStore.sharedInstance();
store.on("update", this.onStoreUpdate);

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import React, { ChangeEvent, SyntheticEvent } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { Optional } from "matrix-events-sdk";
import { ISSOFlow, LoginFlow, SSOAction } from "matrix-js-sdk/src/@types/auth";
@ -44,7 +44,7 @@ enum LoginView {
Unsupported,
}
const STATIC_FLOWS_TO_VIEWS = {
const STATIC_FLOWS_TO_VIEWS: Record<string, LoginView> = {
"m.login.password": LoginView.Password,
"m.login.cas": LoginView.CAS,
"m.login.sso": LoginView.SSO,
@ -133,7 +133,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
this.setState({ flows, loginView: chosenView });
}
private onPasswordChange = (ev): void => {
private onPasswordChange = (ev: ChangeEvent<HTMLInputElement>): void => {
this.setState({ password: ev.target.value });
};
@ -141,7 +141,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
dis.dispatch({ action: "start_password_recovery" });
};
private onPasswordLogin = async (ev): Promise<void> => {
private onPasswordLogin = async (ev: SyntheticEvent): Promise<void> => {
ev.preventDefault();
ev.stopPropagation();

View file

@ -31,7 +31,7 @@ interface IState {
* A clock which shows a clip's maximum duration.
*/
export default class DurationClock extends React.PureComponent<IProps, IState> {
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.state = {

View file

@ -34,12 +34,12 @@ interface IState {
*/
export default class LiveRecordingClock extends React.PureComponent<IProps, IState> {
private seconds = 0;
private scheduledUpdate = new MarkedExecution(
private scheduledUpdate: MarkedExecution = new MarkedExecution(
() => this.updateClock(),
() => requestAnimationFrame(() => this.scheduledUpdate.trigger()),
);
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.state = {
seconds: 0,

View file

@ -39,12 +39,12 @@ export default class LiveRecordingWaveform extends React.PureComponent<IProps, I
};
private waveform: number[] = [];
private scheduledUpdate = new MarkedExecution(
private scheduledUpdate: MarkedExecution = new MarkedExecution(
() => this.updateWaveform(),
() => requestAnimationFrame(() => this.scheduledUpdate.trigger()),
);
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.state = {
waveform: arraySeed(0, RECORDING_PLAYBACK_SAMPLES),

View file

@ -35,7 +35,7 @@ interface IProps extends Omit<React.ComponentProps<typeof AccessibleTooltipButto
* to be displayed in reference to a recording.
*/
export default class PlayPauseButton extends React.PureComponent<IProps> {
public constructor(props) {
public constructor(props: IProps) {
super(props);
}

View file

@ -39,7 +39,7 @@ interface IState {
* A clock for a playback of a recording.
*/
export default class PlaybackClock extends React.PureComponent<IProps, IState> {
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.state = {

View file

@ -34,7 +34,7 @@ interface IState {
* A waveform which shows the waveform of a previously recorded recording
*/
export default class PlaybackWaveform extends React.PureComponent<IProps, IState> {
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.state = {

View file

@ -46,7 +46,7 @@ export default class SeekBar extends React.PureComponent<IProps, IState> {
// We use an animation frame request to avoid overly spamming prop updates, even if we aren't
// really using anything demanding on the CSS front.
private animationFrameFn = new MarkedExecution(
private animationFrameFn: MarkedExecution = new MarkedExecution(
() => this.doUpdate(),
() => requestAnimationFrame(() => this.animationFrameFn.trigger()),
);

View file

@ -21,7 +21,7 @@ import SdkConfig from "../../../SdkConfig";
import { _t } from "../../../languageHandler";
import Dropdown from "../elements/Dropdown";
const COUNTRIES_BY_ISO2 = {};
const COUNTRIES_BY_ISO2: Record<string, PhoneNumberCountryDefinition> = {};
for (const c of COUNTRIES) {
COUNTRIES_BY_ISO2[c.iso2] = c;
}

View file

@ -100,7 +100,7 @@ interface IPasswordAuthEntryState {
export class PasswordAuthEntry extends React.Component<IAuthEntryProps, IPasswordAuthEntryState> {
public static LOGIN_TYPE = AuthType.Password;
public constructor(props) {
public constructor(props: IAuthEntryProps) {
super(props);
this.state = {
@ -264,7 +264,7 @@ interface ITermsAuthEntryState {
export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITermsAuthEntryState> {
public static LOGIN_TYPE = AuthType.Terms;
public constructor(props) {
public constructor(props: ITermsAuthEntryProps) {
super(props);
// example stageParams:
@ -288,8 +288,12 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
const allPolicies = this.props.stageParams.policies || {};
const prefLang = SettingsStore.getValue("language");
const initToggles = {};
const pickedPolicies = [];
const initToggles: Record<string, boolean> = {};
const pickedPolicies: {
id: string;
name: string;
url: string;
}[] = [];
for (const policyId of Object.keys(allPolicies)) {
const policy = allPolicies[policyId];
@ -325,7 +329,7 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
}
private togglePolicy(policyId: string): void {
const newToggles = {};
const newToggles: Record<string, boolean> = {};
for (const policy of this.state.policies) {
let checked = this.state.toggledPolicies[policy.id];
if (policy.id === policyId) checked = !checked;
@ -484,7 +488,7 @@ export class EmailIdentityAuthEntry extends React.Component<
{
a: (text: string) => (
<Fragment>
<AccessibleButton kind="link_inline" onClick={() => null} disabled>
<AccessibleButton kind="link_inline" onClick={null} disabled>
{text} <Spinner w={14} h={14} />
</AccessibleButton>
</Fragment>
@ -555,7 +559,7 @@ export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsi
private sid: string;
private msisdn: string;
public constructor(props) {
public constructor(props: IMsisdnAuthEntryProps) {
super(props);
this.state = {
@ -908,7 +912,7 @@ export class FallbackAuthEntry extends React.Component<IAuthEntryProps> {
private popupWindow: Window;
private fallbackButton = createRef<HTMLButtonElement>();
public constructor(props) {
public constructor(props: IAuthEntryProps) {
super(props);
// we have to make the user click a button, as browsers will block

View file

@ -75,7 +75,7 @@ interface IState {
* This uses the unstable feature of MSC3906: https://github.com/matrix-org/matrix-spec-proposals/pull/3906
*/
export default class LoginWithQR extends React.Component<IProps, IState> {
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.state = {

View file

@ -41,7 +41,7 @@ interface IProps {
* This uses the unstable feature of MSC3906: https://github.com/matrix-org/matrix-spec-proposals/pull/3906
*/
export default class LoginWithQRFlow extends React.Component<IProps> {
public constructor(props) {
public constructor(props: IProps) {
super(props);
}

View file

@ -30,8 +30,8 @@ interface IProps extends Omit<IInputProps, "onValidate"> {
labelRequired?: string;
labelInvalid?: string;
onChange(ev: React.FormEvent<HTMLElement>);
onValidate?(result: IValidationResult);
onChange(ev: React.FormEvent<HTMLElement>): void;
onValidate?(result: IValidationResult): void;
}
class PassphraseConfirmField extends PureComponent<IProps> {

View file

@ -36,8 +36,8 @@ interface IProps extends Omit<IInputProps, "onValidate"> {
labelStrongPassword?: string;
labelAllowedButUnsafe?: string;
onChange(ev: React.FormEvent<HTMLElement>);
onValidate?(result: IValidationResult);
onChange(ev: React.FormEvent<HTMLElement>): void;
onValidate?(result: IValidationResult): void;
}
class PassphraseField extends PureComponent<IProps> {

View file

@ -14,17 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import React, { SyntheticEvent } from "react";
import classNames from "classnames";
import { _t } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig";
import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
import AccessibleButton from "../elements/AccessibleButton";
import withValidation, { IValidationResult } from "../elements/Validation";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
import Field from "../elements/Field";
import CountryDropdown from "./CountryDropdown";
import EmailField from "./EmailField";
import { PhoneNumberCountryDefinition } from "../../../phonenumber";
// For validating phone numbers without country codes
const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/;
@ -51,7 +52,7 @@ interface IProps {
interface IState {
fieldValid: Partial<Record<LoginField, boolean>>;
loginType: LoginField.Email | LoginField.MatrixId | LoginField.Phone;
password: "";
password: string;
}
const enum LoginField {
@ -66,6 +67,10 @@ const enum LoginField {
* The email/username/phone fields are fully-controlled, the password field is not.
*/
export default class PasswordLogin extends React.PureComponent<IProps, IState> {
private [LoginField.Email]: Field;
private [LoginField.Phone]: Field;
private [LoginField.MatrixId]: Field;
public static defaultProps = {
onUsernameChanged: function () {},
onUsernameBlur: function () {},
@ -75,7 +80,7 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
disableSubmit: false,
};
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.state = {
// Field error codes by field ID
@ -85,13 +90,13 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
};
}
private onForgotPasswordClick = (ev): void => {
private onForgotPasswordClick = (ev: ButtonEvent): void => {
ev.preventDefault();
ev.stopPropagation();
this.props.onForgotPasswordClick();
};
private onSubmitForm = async (ev): Promise<void> => {
private onSubmitForm = async (ev: SyntheticEvent): Promise<void> => {
ev.preventDefault();
const allFieldsValid = await this.verifyFieldsBeforeSubmit();
@ -99,47 +104,40 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
return;
}
let username = ""; // XXX: Synapse breaks if you send null here:
let phoneCountry = null;
let phoneNumber = null;
switch (this.state.loginType) {
case LoginField.Email:
case LoginField.MatrixId:
username = this.props.username;
this.props.onSubmit(this.props.username, undefined, undefined, this.state.password);
break;
case LoginField.Phone:
phoneCountry = this.props.phoneCountry;
phoneNumber = this.props.phoneNumber;
this.props.onSubmit(undefined, this.props.phoneCountry, this.props.phoneNumber, this.state.password);
break;
}
this.props.onSubmit(username, phoneCountry, phoneNumber, this.state.password);
};
private onUsernameChanged = (ev): void => {
private onUsernameChanged = (ev: React.ChangeEvent<HTMLInputElement>): void => {
this.props.onUsernameChanged(ev.target.value);
};
private onUsernameBlur = (ev): void => {
private onUsernameBlur = (ev: React.FocusEvent<HTMLInputElement>): void => {
this.props.onUsernameBlur(ev.target.value);
};
private onLoginTypeChange = (ev): void => {
const loginType = ev.target.value;
private onLoginTypeChange = (ev: React.ChangeEvent<HTMLSelectElement>): void => {
const loginType = ev.target.value as IState["loginType"];
this.setState({ loginType });
this.props.onUsernameChanged(""); // Reset because email and username use the same state
};
private onPhoneCountryChanged = (country): void => {
private onPhoneCountryChanged = (country: PhoneNumberCountryDefinition): void => {
this.props.onPhoneCountryChanged(country.iso2);
};
private onPhoneNumberChanged = (ev): void => {
private onPhoneNumberChanged = (ev: React.ChangeEvent<HTMLInputElement>): void => {
this.props.onPhoneNumberChanged(ev.target.value);
};
private onPasswordChanged = (ev): void => {
private onPasswordChanged = (ev: React.ChangeEvent<HTMLInputElement>): void => {
this.setState({ password: ev.target.value });
};
@ -151,7 +149,7 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
activeElement.blur();
}
const fieldIDsInDisplayOrder = [this.state.loginType, LoginField.Password];
const fieldIDsInDisplayOrder: LoginField[] = [this.state.loginType, LoginField.Password];
// Run all fields with stricter validation that no longer allows empty
// values for required fields.
@ -221,7 +219,7 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
],
});
private onUsernameValidate = async (fieldState): Promise<IValidationResult> => {
private onUsernameValidate = async (fieldState: IFieldState): Promise<IValidationResult> => {
const result = await this.validateUsernameRules(fieldState);
this.markFieldValid(LoginField.MatrixId, result.valid);
return result;
@ -248,7 +246,7 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
],
});
private onPhoneNumberValidate = async (fieldState): Promise<IValidationResult> => {
private onPhoneNumberValidate = async (fieldState: IFieldState): Promise<IValidationResult> => {
const result = await this.validatePhoneNumberRules(fieldState);
this.markFieldValid(LoginField.Password, result.valid);
return result;
@ -266,7 +264,7 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
],
});
private onPasswordValidate = async (fieldState): Promise<IValidationResult> => {
private onPasswordValidate = async (fieldState: IFieldState): Promise<IValidationResult> => {
const result = await this.validatePasswordRules(fieldState);
this.markFieldValid(LoginField.Password, result.valid);
return result;

View file

@ -15,18 +15,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import React, { BaseSyntheticEvent } from "react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixError } from "matrix-js-sdk/src/matrix";
import * as Email from "../../../email";
import { looksValid as phoneNumberLooksValid } from "../../../phonenumber";
import { looksValid as phoneNumberLooksValid, PhoneNumberCountryDefinition } from "../../../phonenumber";
import Modal from "../../../Modal";
import { _t, _td } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig";
import { SAFE_LOCALPART_REGEX } from "../../../Registration";
import withValidation, { IValidationResult } from "../elements/Validation";
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
import EmailField from "./EmailField";
import PassphraseField from "./PassphraseField";
@ -95,12 +95,18 @@ interface IState {
* A pure UI component which displays a registration form.
*/
export default class RegistrationForm extends React.PureComponent<IProps, IState> {
private [RegistrationField.Email]: Field;
private [RegistrationField.Password]: Field;
private [RegistrationField.PasswordConfirm]: Field;
private [RegistrationField.Username]: Field;
private [RegistrationField.PhoneNumber]: Field;
public static defaultProps = {
onValidationChange: logger.error,
canSubmit: true,
};
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.state = {
@ -115,7 +121,9 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
};
}
private onSubmit = async (ev): Promise<void> => {
private onSubmit = async (
ev: BaseSyntheticEvent<Event, EventTarget & HTMLFormElement, EventTarget & HTMLFormElement>,
): Promise<void> => {
ev.preventDefault();
ev.persist();
@ -152,7 +160,9 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
}
};
private doSubmit(ev): void {
private doSubmit(
ev: BaseSyntheticEvent<Event, EventTarget & HTMLFormElement, EventTarget & HTMLFormElement>,
): void {
PosthogAnalytics.instance.setAuthenticationType("Password");
const email = this.state.email.trim();
@ -248,7 +258,7 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
});
}
private onEmailChange = (ev): void => {
private onEmailChange = (ev: React.ChangeEvent<HTMLInputElement>): void => {
this.setState({
email: ev.target.value.trim(),
});
@ -277,7 +287,7 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
],
});
private onPasswordChange = (ev): void => {
private onPasswordChange = (ev: React.ChangeEvent<HTMLInputElement>): void => {
this.setState({
password: ev.target.value,
});
@ -287,7 +297,7 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
this.markFieldValid(RegistrationField.Password, result.valid);
};
private onPasswordConfirmChange = (ev): void => {
private onPasswordConfirmChange = (ev: React.ChangeEvent<HTMLInputElement>): void => {
this.setState({
passwordConfirm: ev.target.value,
});
@ -297,19 +307,19 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
this.markFieldValid(RegistrationField.PasswordConfirm, result.valid);
};
private onPhoneCountryChange = (newVal): void => {
private onPhoneCountryChange = (newVal: PhoneNumberCountryDefinition): void => {
this.setState({
phoneCountry: newVal.iso2,
});
};
private onPhoneNumberChange = (ev): void => {
private onPhoneNumberChange = (ev: React.ChangeEvent<HTMLInputElement>): void => {
this.setState({
phoneNumber: ev.target.value,
});
};
private onPhoneNumberValidate = async (fieldState): Promise<IValidationResult> => {
private onPhoneNumberValidate = async (fieldState: IFieldState): Promise<IValidationResult> => {
const result = await this.validatePhoneNumberRules(fieldState);
this.markFieldValid(RegistrationField.PhoneNumber, result.valid);
return result;
@ -334,13 +344,13 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
],
});
private onUsernameChange = (ev): void => {
private onUsernameChange = (ev: React.ChangeEvent<HTMLInputElement>): void => {
this.setState({
username: ev.target.value,
});
};
private onUsernameValidate = async (fieldState): Promise<IValidationResult> => {
private onUsernameValidate = async (fieldState: IFieldState): Promise<IValidationResult> => {
const result = await this.validateUsernameRules(fieldState);
this.markFieldValid(RegistrationField.Username, result.valid);
return result;

View file

@ -48,7 +48,7 @@ interface IProps {
tabIndex?: number;
}
const calculateUrls = (url: string, urls: string[], lowBandwidth: boolean): string[] => {
const calculateUrls = (url?: string, urls?: string[], lowBandwidth = false): string[] => {
// work out the full set of urls to try to load. This is formed like so:
// imageUrls: [ props.url, ...props.urls ]
@ -66,7 +66,7 @@ const calculateUrls = (url: string, urls: string[], lowBandwidth: boolean): stri
return Array.from(new Set(_urls));
};
const useImageUrl = ({ url, urls }): [string, () => void] => {
const useImageUrl = ({ url, urls }: { url?: string; urls?: string[] }): [string, () => void] => {
// Since this is a hot code path and the settings store can be slow, we
// use the cached lowBandwidth value from the room context if it exists
const roomContext = useContext(RoomContext);

View file

@ -34,7 +34,7 @@ interface IState {
export default class DialpadContextMenu extends React.Component<IProps, IState> {
private numberEntryFieldRef: React.RefObject<Field> = createRef();
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.state = {
@ -58,14 +58,14 @@ export default class DialpadContextMenu extends React.Component<IProps, IState>
this.props.onFinished();
};
public onKeyDown = (ev): void => {
public onKeyDown = (ev: React.KeyboardEvent): void => {
// Prevent Backspace and Delete keys from functioning in the entry field
if (ev.code === "Backspace" || ev.code === "Delete") {
ev.preventDefault();
}
};
public onChange = (ev): void => {
public onChange = (ev: React.ChangeEvent<HTMLInputElement>): void => {
this.setState({ value: ev.target.value });
};

View file

@ -26,7 +26,7 @@ interface IProps extends IContextMenuProps {
}
export default class LegacyCallContextMenu extends React.Component<IProps> {
public constructor(props) {
public constructor(props: IProps) {
super(props);
}

View file

@ -88,7 +88,7 @@ export default class BaseDialog extends React.Component<IProps> {
fixedWidth: true,
};
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.matrixClient = MatrixClientPeg.get();
@ -132,7 +132,7 @@ export default class BaseDialog extends React.Component<IProps> {
headerImage = <img className="mx_Dialog_titleImage" src={this.props.headerImage} alt="" />;
}
const lockProps = {
const lockProps: Record<string, any> = {
"onKeyDown": this.onKeyDown,
"role": "dialog",
// This should point to a node describing the dialog.

View file

@ -54,7 +54,7 @@ interface IState {
export default class BugReportDialog extends React.Component<IProps, IState> {
private unmounted: boolean;
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.state = {
sendLogs: true,

View file

@ -21,6 +21,7 @@ import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { Room } from "matrix-js-sdk/src/models/room";
import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher";
@ -42,7 +43,7 @@ const BulkRedactDialog: React.FC<IBulkRedactDialogProps> = (props) => {
const [keepStateEvents, setKeepStateEvents] = useState(true);
let timeline = room.getLiveTimeline();
let eventsToRedact = [];
let eventsToRedact: MatrixEvent[] = [];
while (timeline) {
eventsToRedact = [
...eventsToRedact,

View file

@ -27,16 +27,26 @@ interface IProps {
onFinished: (success: boolean) => void;
}
const REPOS = ["vector-im/element-web", "matrix-org/matrix-react-sdk", "matrix-org/matrix-js-sdk"];
type State = Partial<Record<typeof REPOS[number], null | string | Commit[]>>;
export default class ChangelogDialog extends React.Component<IProps> {
public constructor(props) {
interface Commit {
sha: string;
html_url: string;
commit: {
message: string;
};
}
const REPOS = ["vector-im/element-web", "matrix-org/matrix-react-sdk", "matrix-org/matrix-js-sdk"] as const;
export default class ChangelogDialog extends React.Component<IProps, State> {
public constructor(props: IProps) {
super(props);
this.state = {};
}
private async fetchChanges(repo: string, oldVersion: string, newVersion: string): Promise<void> {
private async fetchChanges(repo: typeof REPOS[number], oldVersion: string, newVersion: string): Promise<void> {
const url = `https://riot.im/github/repos/${repo}/compare/${oldVersion}...${newVersion}`;
try {
@ -66,7 +76,7 @@ export default class ChangelogDialog extends React.Component<IProps> {
}
}
private elementsForCommit(commit): JSX.Element {
private elementsForCommit(commit: Commit): JSX.Element {
return (
<li key={commit.sha} className="mx_ChangelogDialog_li">
<a href={commit.html_url} target="_blank" rel="noreferrer noopener">
@ -86,7 +96,7 @@ export default class ChangelogDialog extends React.Component<IProps> {
msg: this.state[repo],
});
} else {
content = this.state[repo].map(this.elementsForCommit);
content = (this.state[repo] as Commit[]).map(this.elementsForCommit);
}
return (
<div key={repo}>

View file

@ -45,7 +45,7 @@ interface IState {
* To avoid this, we keep the dialog open as long as /redact is in progress.
*/
export default class ConfirmAndWaitRedactDialog extends React.PureComponent<IProps, IState> {
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.state = {
isRedacting: false,

View file

@ -62,7 +62,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
private nameField = createRef<Field>();
private aliasField = createRef<RoomAliasField>();
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.supportsRestricted = !!this.props.parentSpace;

View file

@ -21,7 +21,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import { _t } from "../../../languageHandler";
import BaseDialog from "./BaseDialog";
import AccessibleButton from "../elements/AccessibleButton";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { BetaPill } from "../beta/BetaCard";
import Field from "../elements/Field";
@ -54,7 +54,7 @@ const CreateSubspaceDialog: React.FC<IProps> = ({ space, onAddExistingSpaceClick
}
const [joinRule, setJoinRule] = useState<JoinRule>(defaultJoinRule);
const onCreateSubspaceClick = async (e): Promise<void> => {
const onCreateSubspaceClick = async (e: ButtonEvent): Promise<void> => {
e.preventDefault();
if (busy) return;

View file

@ -28,6 +28,16 @@ import BaseDialog from "./BaseDialog";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
type DialogAesthetics = Partial<{
[x in AuthType]: {
[x: number]: {
body: string;
continueText?: string;
continueKind?: string;
};
};
}>;
interface IProps {
onFinished: (success: boolean) => void;
}
@ -46,7 +56,7 @@ interface IState {
}
export default class DeactivateAccountDialog extends React.Component<IProps, IState> {
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.state = {
@ -65,7 +75,7 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
this.initAuth(/* shouldErase= */ false);
}
private onStagePhaseChange = (stage: AuthType, phase: string): void => {
private onStagePhaseChange = (stage: AuthType, phase: number): void => {
const dialogAesthetics = {
[SSOAuthEntry.PHASE_PREAUTH]: {
body: _t("Confirm your account deactivation by using Single Sign On to prove your identity."),
@ -80,7 +90,7 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
};
// This is the same as aestheticsForStagePhases in InteractiveAuthDialog minus the `title`
const DEACTIVATE_AESTHETICS = {
const DEACTIVATE_AESTHETICS: DialogAesthetics = {
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
[PasswordAuthEntry.LOGIN_TYPE]: {
@ -96,9 +106,9 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
let continueKind = null;
if (aesthetics) {
const phaseAesthetics = aesthetics[phase];
if (phaseAesthetics && phaseAesthetics.body) bodyText = phaseAesthetics.body;
if (phaseAesthetics && phaseAesthetics.continueText) continueText = phaseAesthetics.continueText;
if (phaseAesthetics && phaseAesthetics.continueKind) continueKind = phaseAesthetics.continueKind;
if (phaseAesthetics?.body) bodyText = phaseAesthetics.body;
if (phaseAesthetics?.continueText) continueText = phaseAesthetics.continueText;
if (phaseAesthetics?.continueKind) continueKind = phaseAesthetics.continueKind;
}
this.setState({ bodyText, continueText, continueKind });
};

View file

@ -91,7 +91,7 @@ const DevtoolsDialog: React.FC<IProps> = ({ roomId, onFinished }) => {
<BaseTool onBack={onBack}>
{Object.entries(Tools).map(([category, tools]) => (
<div key={category}>
<h3>{_t(categoryLabels[category])}</h3>
<h3>{_t(categoryLabels[category as unknown as Category])}</h3>
{tools.map(([label, tool]) => {
const onClick = (): void => {
setTool([label, tool]);

View file

@ -46,9 +46,6 @@ interface IState {
export default class ErrorDialog extends React.Component<IProps, IState> {
public static defaultProps = {
focus: true,
title: null,
description: null,
button: null,
};
private onClick = (): void => {

View file

@ -25,7 +25,14 @@ import DialogButtons from "../elements/DialogButtons";
import Field from "../elements/Field";
import StyledRadioGroup from "../elements/StyledRadioGroup";
import StyledCheckbox from "../elements/StyledCheckbox";
import { ExportFormat, ExportType, textForFormat, textForType } from "../../../utils/exportUtils/exportUtils";
import {
ExportFormat,
ExportFormatKey,
ExportType,
ExportTypeKey,
textForFormat,
textForType,
} from "../../../utils/exportUtils/exportUtils";
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
import HTMLExporter from "../../../utils/exportUtils/HtmlExport";
import JSONExporter from "../../../utils/exportUtils/JSONExport";
@ -237,15 +244,15 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
setExporter(null);
};
const exportFormatOptions = Object.keys(ExportFormat).map((format) => ({
value: ExportFormat[format],
label: textForFormat(ExportFormat[format]),
const exportFormatOptions = Object.values(ExportFormat).map((format) => ({
value: format,
label: textForFormat(format),
}));
const exportTypeOptions = Object.keys(ExportType).map((type) => {
const exportTypeOptions = Object.values(ExportType).map((type) => {
return (
<option key={type} value={ExportType[type]}>
{textForType(ExportType[type])}
<option key={ExportType[type]} value={type}>
{textForType(type)}
</option>
);
});
@ -332,7 +339,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
<StyledRadioGroup
name="exportFormat"
value={exportFormat}
onChange={(key) => setExportFormat(ExportFormat[key])}
onChange={(key: ExportFormatKey) => setExportFormat(ExportFormat[key])}
definitions={exportFormatOptions}
/>
</>
@ -347,7 +354,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
element="select"
value={exportType}
onChange={(e) => {
setExportType(ExportType[e.target.value]);
setExportType(ExportType[e.target.value as ExportTypeKey]);
}}
>
{exportTypeOptions}

View file

@ -243,7 +243,7 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
}
const [truncateAt, setTruncateAt] = useState(20);
function overflowTile(overflowCount, totalCount): JSX.Element {
function overflowTile(overflowCount: number, totalCount: number): JSX.Element {
const text = _t("and %(count)s others...", { count: overflowCount });
return (
<EntityTile

View file

@ -155,7 +155,7 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
});
}
private onAccountDetailsDialogFinished = async (result): Promise<void> => {
private onAccountDetailsDialogFinished = async (result: boolean): Promise<void> => {
if (result) {
return this.sendAccountDetails();
}

View file

@ -18,7 +18,7 @@ limitations under the License.
import React from "react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { IAuthData } from "matrix-js-sdk/src/interactive-auth";
import { AuthType, IAuthData } from "matrix-js-sdk/src/interactive-auth";
import { _t } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton";
@ -27,8 +27,8 @@ import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
import BaseDialog from "./BaseDialog";
import { IDialogProps } from "./IDialogProps";
interface IDialogAesthetics {
[x: string]: {
type DialogAesthetics = Partial<{
[x in AuthType]: {
[x: number]: {
title: string;
body: string;
@ -36,7 +36,7 @@ interface IDialogAesthetics {
continueKind: string;
};
};
}
}>;
export interface InteractiveAuthDialogProps extends IDialogProps {
// matrix client to use for UI auth requests
@ -71,15 +71,15 @@ export interface InteractiveAuthDialogProps extends IDialogProps {
// }
//
// Default is defined in _getDefaultDialogAesthetics()
aestheticsForStagePhases?: IDialogAesthetics;
aestheticsForStagePhases?: DialogAesthetics;
}
interface IState {
authError: Error;
// See _onUpdateStagePhase()
uiaStage: number | string;
uiaStagePhase: number | string;
uiaStage: AuthType | null;
uiaStagePhase: number | null;
}
export default class InteractiveAuthDialog extends React.Component<InteractiveAuthDialogProps, IState> {
@ -95,7 +95,7 @@ export default class InteractiveAuthDialog extends React.Component<InteractiveAu
};
}
private getDefaultDialogAesthetics(): IDialogAesthetics {
private getDefaultDialogAesthetics(): DialogAesthetics {
const ssoAesthetics = {
[SSOAuthEntry.PHASE_PREAUTH]: {
title: _t("Use Single Sign On to continue"),
@ -125,13 +125,13 @@ export default class InteractiveAuthDialog extends React.Component<InteractiveAu
this.props.onFinished(false, null);
} else {
this.setState({
authError: result,
authError: result as Error,
});
}
}
};
private onUpdateStagePhase = (newStage: string | number, newPhase: string | number): void => {
private onUpdateStagePhase = (newStage: AuthType, newPhase: number): void => {
// We copy the stage and stage phase params into state for title selection in render()
this.setState({ uiaStage: newStage, uiaStagePhase: newPhase });
};

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { createRef, ReactNode } from "react";
import React, { createRef, ReactNode, SyntheticEvent } from "react";
import classNames from "classnames";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { Room } from "matrix-js-sdk/src/models/room";
@ -92,7 +92,7 @@ enum TabId {
}
class DMUserTile extends React.PureComponent<IDMUserTileProps> {
private onRemove = (e): void => {
private onRemove = (e: ButtonEvent): void => {
// Stop the browser from highlighting text
e.preventDefault();
e.stopPropagation();
@ -139,7 +139,7 @@ interface IDMRoomTileProps {
}
class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
private onClick = (e): void => {
private onClick = (e: ButtonEvent): void => {
// Stop the browser from highlighting text
e.preventDefault();
e.stopPropagation();
@ -271,6 +271,10 @@ interface InviteRoomProps extends BaseProps {
roomId: string;
}
function isRoomInvite(props: Props): props is InviteRoomProps {
return props.kind === KIND_INVITE;
}
interface InviteCallProps extends BaseProps {
kind: typeof KIND_CALL_TRANSFER;
@ -311,7 +315,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
private numberEntryFieldRef: React.RefObject<Field> = createRef();
private unmounted = false;
public constructor(props) {
public constructor(props: Props) {
super(props);
if (props.kind === KIND_INVITE && !props.roomId) {
@ -321,7 +325,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
}
const alreadyInvited = new Set([MatrixClientPeg.get().getUserId(), SdkConfig.get("welcome_user_id")]);
if (props.roomId) {
if (isRoomInvite(props)) {
const room = MatrixClientPeg.get().getRoom(props.roomId);
if (!room) throw new Error("Room ID given to InviteDialog does not look like a room");
room.getMembersWithMembership("invite").forEach((m) => alreadyInvited.add(m.userId));
@ -361,7 +365,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
this.unmounted = true;
}
private onConsultFirstChange = (ev): void => {
private onConsultFirstChange = (ev: React.ChangeEvent<HTMLInputElement>): void => {
this.setState({ consultFirst: ev.target.checked });
};
@ -538,11 +542,11 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
this.props.onFinished(true);
};
private onKeyDown = (e): void => {
private onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => {
if (this.state.busy) return;
let handled = false;
const value = e.target.value.trim();
const value = e.currentTarget.value.trim();
const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) {
@ -692,7 +696,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
}
};
private updateFilter = (e): void => {
private updateFilter = (e: React.ChangeEvent<HTMLInputElement>): void => {
const term = e.target.value;
this.setState({ filterText: term });
@ -750,7 +754,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
}
};
private onPaste = async (e): Promise<void> => {
private onPaste = async (e: React.ClipboardEvent): Promise<void> => {
if (this.state.filterText) {
// if the user has already typed something, just let them
// paste normally.
@ -825,7 +829,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
this.setState({ targets: [...this.state.targets, ...toAdd] });
};
private onClickInputArea = (e): void => {
private onClickInputArea = (e: React.MouseEvent): void => {
// Stop the browser from highlighting text
e.preventDefault();
e.stopPropagation();
@ -835,7 +839,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
}
};
private onUseDefaultIdentityServerClick = (e): void => {
private onUseDefaultIdentityServerClick = (e: ButtonEvent): void => {
e.preventDefault();
// Update the IS in account data. Actually using it may trigger terms.
@ -844,7 +848,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
this.setState({ canUseIdentityServer: true, tryingIdentityServer: false });
};
private onManageSettingsClick = (e): void => {
private onManageSettingsClick = (e: ButtonEvent): void => {
e.preventDefault();
dis.fire(Action.ViewUserSettings);
this.props.onFinished(false);
@ -864,8 +868,8 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
// Mix in the server results if we have any, but only if we're searching. We track the additional
// members separately because we want to filter sourceMembers but trust the mixin arrays to have
// the right members in them.
let priorityAdditionalMembers = []; // Shows up before our own suggestions, higher quality
let otherAdditionalMembers = []; // Shows up after our own suggestions, lower quality
let priorityAdditionalMembers: Result[] = []; // Shows up before our own suggestions, higher quality
let otherAdditionalMembers: Result[] = []; // Shows up after our own suggestions, lower quality
const hasMixins = this.state.serverResultsMixin || this.state.threepidResultsMixin;
if (this.state.filterText && hasMixins && kind === "suggestions") {
// We don't want to duplicate members though, so just exclude anyone we've already seen.
@ -1030,12 +1034,12 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
}
}
private onDialFormSubmit = (ev): void => {
private onDialFormSubmit = (ev: SyntheticEvent): void => {
ev.preventDefault();
this.transferCall();
};
private onDialChange = (ev): void => {
private onDialChange = (ev: React.ChangeEvent<HTMLInputElement>): void => {
this.setState({ dialPadValue: ev.currentTarget.value });
};
@ -1066,9 +1070,9 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
this.setState({ currentTabId: tabId });
};
private async onLinkClick(e): Promise<void> {
private async onLinkClick(e: React.MouseEvent<HTMLAnchorElement>): Promise<void> {
e.preventDefault();
selectText(e.target);
selectText(e.currentTarget);
}
private get screenName(): ScreenName {

View file

@ -45,7 +45,7 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
onFinished: function () {},
};
public constructor(props) {
public constructor(props: IProps) {
super(props);
const cli = MatrixClientPeg.get();

View file

@ -121,8 +121,8 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
}
private renderEdits(): JSX.Element[] {
const nodes = [];
let lastEvent;
const nodes: JSX.Element[] = [];
let lastEvent: MatrixEvent;
let allEvents = this.state.events;
// append original event when we've done last pagination
if (this.state.originalEvent && !this.state.nextBatch) {

View file

@ -65,7 +65,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
disabledButtonIds: (this.props.widgetDefinition.buttons || []).filter((b) => b.disabled).map((b) => b.id),
};
public constructor(props) {
public constructor(props: IProps) {
super(props);
this.widget = new ElementWidget({

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import * as React from "react";
import { useRef, useState } from "react";
import { SyntheticEvent, useRef, useState } from "react";
import { _t, _td } from "../../../languageHandler";
import { IDialogProps } from "./IDialogProps";
@ -32,7 +32,7 @@ const RegistrationEmailPromptDialog: React.FC<IProps> = ({ onFinished }) => {
const [email, setEmail] = useState("");
const fieldRef = useRef<Field>();
const onSubmit = async (e): Promise<void> => {
const onSubmit = async (e: SyntheticEvent): Promise<void> => {
e.preventDefault();
if (email) {
const valid = await fieldRef.current.validate({});

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