diff --git a/package.json b/package.json index 02649d60d99..0f4e52d2e6d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index f1e72ca2fc4..9d3b64fd6af 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -218,7 +218,7 @@ declare global { processorCtor: (new (options?: AudioWorkletNodeOptions) => AudioWorkletProcessor) & { parameterDescriptors?: AudioParamDescriptor[]; }, - ); + ): void; // eslint-disable-next-line no-var var grecaptcha: diff --git a/src/@types/opus-recorder.d.ts b/src/@types/opus-recorder.d.ts new file mode 100644 index 00000000000..a964278aa1d --- /dev/null +++ b/src/@types/opus-recorder.d.ts @@ -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; + + public stop(): Promise; + + 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; +} diff --git a/src/AddThreepid.ts b/src/AddThreepid.ts index b6ed0d57387..5d8d947854d 100644 --- a/src/AddThreepid.ts +++ b/src/AddThreepid.ts @@ -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 { + 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; } } diff --git a/src/AsyncWrapper.tsx b/src/AsyncWrapper.tsx index 226f5b692bc..9d0ba11b062 100644 --- a/src/AsyncWrapper.tsx +++ b/src/AsyncWrapper.tsx @@ -42,10 +42,7 @@ interface IState { export default class AsyncWrapper extends React.Component { private unmounted = false; - public state = { - component: null, - error: null, - }; + public state: IState = {}; public componentDidMount(): void { // XXX: temporary logging to try to diagnose diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 46f964995af..7676305c4f7 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -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 }; diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index f2452327e50..a6b999fa273 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -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 = { "data-mx-color": "color", "data-mx-bg-color": "background-color", // $customAttributeKey: $cssAttributeKey diff --git a/src/IConfigOptions.ts b/src/IConfigOptions.ts index 8234f5bc757..1db73cc0745 100644 --- a/src/IConfigOptions.ts +++ b/src/IConfigOptions.ts @@ -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; + }; }; }; diff --git a/src/Keyboard.ts b/src/Keyboard.ts index 9d4d3f61521..7b1ea4031be 100644 --- a/src/Keyboard.ts +++ b/src/Keyboard.ts @@ -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 { diff --git a/src/LegacyCallHandler.tsx b/src/LegacyCallHandler.tsx index 82e5cac996c..c3ca2566462 100644 --- a/src/LegacyCallHandler.tsx +++ b/src/LegacyCallHandler.tsx @@ -158,9 +158,9 @@ export default class LegacyCallHandler extends EventEmitter { private transferees = new Map(); // callId (target) -> call (transferee) private audioPromises = new Map>(); private audioElementsWithListeners = new Map(); - 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); diff --git a/src/Login.ts b/src/Login.ts index 6475a9f5c93..dbcdfe954e2 100644 --- a/src/Login.ts +++ b/src/Login.ts @@ -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 { - const isEmail = username.indexOf("@") > 0; + const isEmail = username?.indexOf("@") > 0; let identifier; if (phoneCountry && phoneNumber) { diff --git a/src/MediaDeviceHandler.ts b/src/MediaDeviceHandler.ts index 329741fe511..fcb066e92fe 100644 --- a/src/MediaDeviceHandler.ts +++ b/src/MediaDeviceHandler.ts @@ -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 { try { const devices = await navigator.mediaDevices.enumerateDevices(); - const output = { + const output: Record = { [MediaDeviceKindEnum.AudioOutput]: [], [MediaDeviceKindEnum.AudioInput]: [], [MediaDeviceKindEnum.VideoInput]: [], diff --git a/src/NodeAnimator.tsx b/src/NodeAnimator.tsx index 24b4e85ae37..be2a3442e92 100644 --- a/src/NodeAnimator.tsx +++ b/src/NodeAnimator.tsx @@ -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 { - private nodes = {}; + private nodes: Record = {}; private children: { [key: string]: React.DetailedReactHTMLElement }; public static defaultProps: Partial = { startStyles: [], @@ -65,7 +65,7 @@ export default class NodeAnimator extends React.Component { */ private applyStyles(node: HTMLElement, styles: React.CSSProperties): void { Object.entries(styles).forEach(([property, value]) => { - node.style[property] = value; + node.style[property as keyof Omit] = value; }); } diff --git a/src/Notifier.ts b/src/Notifier.ts index 42909a2632e..0faa5333410 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -68,7 +68,7 @@ Override both the content body and the TextForEvent handler for specific msgtype This is useful when the content body contains fallback text that would explain that the client can't handle a particular type of tile. */ -const msgTypeHandlers = { +const msgTypeHandlers: Record string> = { [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 = {}; // 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 { + // XXX: Exported for tests + public async playAudioNotification(ev: MatrixEvent, room: Room): Promise { 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; diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts index c8a99ab4264..173fdb0b19b 100644 --- a/src/PosthogAnalytics.ts +++ b/src/PosthogAnalytics.ts @@ -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> = {}; private userPropertyCache: UserProperties = {}; diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts index 20db6594b01..bc1c4bb4b98 100644 --- a/src/SecurityManager.ts +++ b/src/SecurityManager.ts @@ -182,7 +182,7 @@ async function getSecretStorageKey({ export async function getDehydrationKey( keyInfo: ISecretStorageKeyInfo, - checkFunc: (Uint8Array) => void, + checkFunc: (data: Uint8Array) => void, ): Promise { const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.(); if (keyFromCustomisations) { @@ -196,7 +196,7 @@ export async function getDehydrationKey( /* props= */ { keyInfo, - checkPrivateKey: async (input): Promise => { + checkPrivateKey: async (input: KeyParams): Promise => { const key = await inputToKey(input); try { checkFunc(key); @@ -290,7 +290,7 @@ export async function promptForBackupPassphrase(): Promise { RestoreKeyBackupDialog, { showSummary: false, - keyCallback: (k) => (key = k), + keyCallback: (k: Uint8Array) => (key = k), }, null, /* priority = */ false, diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 3434090d8ea..43350e44d0b 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -697,11 +697,8 @@ export const Commands = [ } if (viaServers) { - // For the join - dispatch["opts"] = { - // These are passed down to the js-sdk's /join call - viaServers: viaServers, - }; + // For the join, these are passed down to the js-sdk's /join call + dispatch["opts"] = { viaServers }; // For if the join fails (rejoin button) dispatch["via_servers"] = viaServers; @@ -1042,7 +1039,7 @@ export const Commands = [ throw newTranslatableError("Session already verified!"); } else { throw newTranslatableError( - "WARNING: Session already verified, but keys do NOT MATCH!", + "WARNING: session already verified, but keys do NOT MATCH!", ); } } diff --git a/src/Terms.ts b/src/Terms.ts index bb18a18cf7e..f66f543887c 100644 --- a/src/Terms.ts +++ b/src/Terms.ts @@ -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; @@ -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)) { diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx index 7f874f8a898..a515576748b 100644 --- a/src/TextForEvent.tsx +++ b/src/TextForEvent.tsx @@ -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) => ( - highlightEvent(roomId, messageId)}> + highlightEvent(roomId, messageId)} + > {sub} ), @@ -580,7 +583,10 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render { senderName }, { a: (sub) => ( - highlightEvent(roomId, messageId)}> + highlightEvent(roomId, messageId)} + > {sub} ), diff --git a/src/UserActivity.ts b/src/UserActivity.ts index 44c8abb2c7f..ae6417d4f4d 100644 --- a/src/UserActivity.ts +++ b/src/UserActivity.ts @@ -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; diff --git a/src/accessibility/KeyboardShortcutUtils.ts b/src/accessibility/KeyboardShortcutUtils.ts index bb42f7c1ce9..8ba866be3fa 100644 --- a/src/accessibility/KeyboardShortcutUtils.ts +++ b/src/accessibility/KeyboardShortcutUtils.ts @@ -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, + ][]; 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); }; diff --git a/src/accessibility/KeyboardShortcuts.ts b/src/accessibility/KeyboardShortcuts.ts index 0e536ac1497..3011a5b5bd7 100644 --- a/src/accessibility/KeyboardShortcuts.ts +++ b/src/accessibility/KeyboardShortcuts.ts @@ -156,10 +156,8 @@ export enum KeyBindingAction { type KeyboardShortcutSetting = IBaseSetting; -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>; export interface ICategory { categoryLabel?: string; diff --git a/src/accessibility/RovingTabIndex.tsx b/src/accessibility/RovingTabIndex.tsx index 605ffb1f5b5..b449b10710f 100644 --- a/src/accessibility/RovingTabIndex.tsx +++ b/src/accessibility/RovingTabIndex.tsx @@ -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 = ( diff --git a/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx b/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx index ee3a0e4d368..e8e69865d78 100644 --- a/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx +++ b/src/accessibility/context_menu/StyledMenuItemCheckbox.tsx @@ -25,7 +25,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager"; interface IProps extends React.ComponentProps { 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 } diff --git a/src/accessibility/context_menu/StyledMenuItemRadio.tsx b/src/accessibility/context_menu/StyledMenuItemRadio.tsx index 2fe87384340..7a394a3d1f9 100644 --- a/src/accessibility/context_menu/StyledMenuItemRadio.tsx +++ b/src/accessibility/context_menu/StyledMenuItemRadio.tsx @@ -25,7 +25,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager"; interface IProps extends React.ComponentProps { 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 } diff --git a/src/accessibility/roving/RovingAccessibleButton.tsx b/src/accessibility/roving/RovingAccessibleButton.tsx index 3968ef6d6bd..71818c6cda1 100644 --- a/src/accessibility/roving/RovingAccessibleButton.tsx +++ b/src/accessibility/roving/RovingAccessibleButton.tsx @@ -30,7 +30,7 @@ export const RovingAccessibleButton: React.FC = ({ inputRef, onFocus, .. return ( { + onFocus={(event: React.FocusEvent) => { onFocusInternal(); onFocus?.(event); }} diff --git a/src/accessibility/roving/RovingAccessibleTooltipButton.tsx b/src/accessibility/roving/RovingAccessibleTooltipButton.tsx index f30225f0f72..f06cc934bbc 100644 --- a/src/accessibility/roving/RovingAccessibleTooltipButton.tsx +++ b/src/accessibility/roving/RovingAccessibleTooltipButton.tsx @@ -31,7 +31,7 @@ export const RovingAccessibleTooltipButton: React.FC = ({ inputRef, onFo return ( { + onFocus={(event: React.FocusEvent) => { onFocusInternal(); onFocus?.(event); }} diff --git a/src/accessibility/roving/RovingTabIndexWrapper.tsx b/src/accessibility/roving/RovingTabIndexWrapper.tsx index b549f18119e..4208d47499f 100644 --- a/src/accessibility/roving/RovingTabIndexWrapper.tsx +++ b/src/accessibility/roving/RovingTabIndexWrapper.tsx @@ -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; } // Wrapper to allow use of useRovingTabIndex outside of React Functional Components. diff --git a/src/actions/RoomListActions.ts b/src/actions/RoomListActions.ts index 637b57071e4..49351757ca7 100644 --- a/src/actions/RoomListActions.ts +++ b/src/actions/RoomListActions.ts @@ -54,7 +54,7 @@ export default class RoomListActions { oldIndex: number | null, newIndex: number | null, ): AsyncActionPayload { - let metaData = null; + let metaData: Parameters[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[] = []; 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); diff --git a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx index 63a132077fa..f93e097a9d8 100644 --- a/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx +++ b/src/async-components/views/dialogs/eventindex/ManageEventIndexDialog.tsx @@ -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 { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { @@ -56,9 +58,9 @@ export default class ManageEventIndexDialog extends React.Component => { + public updateCurrentRoom = async (room: Room): Promise => { 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 { - this.setState({ crawlerSleepTime: e.target.value }); + private onCrawlerSleepTimeChange = (e: ChangeEvent): void => { + this.setState({ crawlerSleepTime: parseInt(e.target.value, 10) }); SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value); }; diff --git a/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx b/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx index a75b41f602b..36fd23a7fc3 100644 --- a/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx +++ b/src/async-components/views/dialogs/security/CreateKeyBackupDialog.tsx @@ -239,7 +239,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent

{_t( - "Warning: You should only set up key backup from a trusted computer.", + "Warning: you should only set up key backup from a trusted computer.", {}, { b: (sub) => {sub} }, )} diff --git a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx index b595a60a2e0..41b91fba1dc 100644 --- a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx +++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx @@ -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 { + const canUploadKeysWithPasswordOnly = error.data.flows.some((f: UIAFlow) => { return f.stages.length === 1 && f.stages[0] === "m.login.password"; }); this.setState({ diff --git a/src/async-components/views/dialogs/security/ExportE2eKeysDialog.tsx b/src/async-components/views/dialogs/security/ExportE2eKeysDialog.tsx index c8a561e7dae..7bfdf400cf7 100644 --- a/src/async-components/views/dialogs/security/ExportE2eKeysDialog.tsx +++ b/src/async-components/views/dialogs/security/ExportE2eKeysDialog.tsx @@ -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 this.onPassphraseChange(e, "passphrase1")} + onChange={(e: ChangeEvent) => + this.onPassphraseChange(e, "passphrase1") + } autoFocus={true} size={64} type="password" @@ -174,7 +176,9 @@ export default class ExportE2eKeysDialog extends React.Component this.onPassphraseChange(e, "passphrase2")} + onChange={(e: ChangeEvent) => + this.onPassphraseChange(e, "passphrase2") + } size={64} type="password" disabled={disableForm} diff --git a/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx index 079271b0213..e49c090a75a 100644 --- a/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx +++ b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.tsx @@ -73,9 +73,9 @@ export default class ImportE2eKeysDialog extends React.Component } 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, }); }; diff --git a/src/audio/RecorderWorklet.ts b/src/audio/RecorderWorklet.ts index 0c0cc56cd68..3cb9cf03d28 100644 --- a/src/audio/RecorderWorklet.ts +++ b/src/audio/RecorderWorklet.ts @@ -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, + ): 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 diff --git a/src/audio/VoiceRecording.ts b/src/audio/VoiceRecording.ts index 32fcb5a97ab..f8f9f01b667 100644 --- a/src/audio/VoiceRecording.ts +++ b/src/audio/VoiceRecording.ts @@ -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"; diff --git a/src/audio/compat.ts b/src/audio/compat.ts index ab63f644a19..ce0fc30816d 100644 --- a/src/audio/compat.ts +++ b/src/audio/compat.ts @@ -69,7 +69,7 @@ export function decodeOgg(audioBuffer: ArrayBuffer): Promise { command: "encode", buffers: ev.data, }, - ev.data.map((b) => b.buffer), + ev.data.map((b: Float32Array) => b.buffer), ); }; diff --git a/src/autocomplete/EmojiProvider.tsx b/src/autocomplete/EmojiProvider.tsx index cc25068db86..6b55dbf857b 100644 --- a/src/autocomplete/EmojiProvider.tsx +++ b/src/autocomplete/EmojiProvider.tsx @@ -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[] = []; // make sure that emoticons come first sorters.push((c) => score(matchedString, c.emoji.emoticon || "")); diff --git a/src/autocomplete/UserProvider.tsx b/src/autocomplete/UserProvider.tsx index 65de4b1bb4f..5e8d25f4c83 100644 --- a/src/autocomplete/UserProvider.tsx +++ b/src/autocomplete/UserProvider.tsx @@ -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 = {}; for (const event of events) { lastSpoken[event.getSender()] = event.getTs(); diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index 978dd07be91..118e1d8d954 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -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 { 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 { 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 = (

, ): { close: (...args: any[]) => void } { - const onFinished = function (...args): void { + const onFinished = function (...args: any[]): void { ReactDOM.unmountComponentAtNode(getOrCreateContainer()); props?.onFinished?.apply(null, args); }; diff --git a/src/components/structures/FilePanel.tsx b/src/components/structures/FilePanel.tsx index 4390dcf36ed..1f6c1d25837 100644 --- a/src/components/structures/FilePanel.tsx +++ b/src/components/structures/FilePanel.tsx @@ -43,7 +43,7 @@ interface IProps { } interface IState { - timelineSet: EventTimelineSet; + timelineSet: EventTimelineSet | null; narrow: boolean; } @@ -59,7 +59,7 @@ class FilePanel extends React.Component { public noRoom: boolean; private card = createRef(); - public state = { + public state: IState = { timelineSet: null, narrow: false, }; diff --git a/src/components/structures/GenericDropdownMenu.tsx b/src/components/structures/GenericDropdownMenu.tsx index 6b727c5140d..98dfbf0851f 100644 --- a/src/components/structures/GenericDropdownMenu.tsx +++ b/src/components/structures/GenericDropdownMenu.tsx @@ -79,6 +79,12 @@ export function GenericDropdownMenuGroup({ ); } +function isGenericDropdownMenuGroupArray( + items: readonly GenericDropdownMenuItem[], +): items is GenericDropdownMenuGroup[] { + return isGenericDropdownMenuGroup(items[0]); +} + function isGenericDropdownMenuGroup(item: GenericDropdownMenuItem): item is GenericDropdownMenuGroup { return "options" in item; } @@ -123,19 +129,19 @@ export function GenericDropdownMenu({ .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) => ( {group.options.map((option) => ( { @@ -156,7 +162,7 @@ export function GenericDropdownMenu({ <> {options.map((option) => ( { diff --git a/src/components/structures/InteractiveAuth.tsx b/src/components/structures/InteractiveAuth.tsx index 99be8705a4d..f11b2390523 100644 --- a/src/components/structures/InteractiveAuth.tsx +++ b/src/components/structures/InteractiveAuth.tsx @@ -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 { private listContainerRef = createRef(); private roomListRef = createRef(); - private focusedElement = null; + private focusedElement: Element = null; private isDoingStickyHeaders = false; public constructor(props: IProps) { diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 242bbdc0280..1cffd6b6f43 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -136,8 +136,8 @@ class LoggedInView extends React.Component { 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 { }; 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 { 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 { e.getContent()["server_notice_type"] === "m.server_notice.usage_limit_reached" ); }); - const usageLimitEventContent = usageLimitEvent && usageLimitEvent.getContent(); + const usageLimitEventContent = usageLimitEvent?.getContent(); this.calculateServerLimitToast(this.state.syncErrorData, usageLimitEventContent); this.setState({ usageLimitEventContent, @@ -422,13 +422,13 @@ class LoggedInView extends React.Component { 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 { } }; - 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 { ) { dis.dispatch({ 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 { * 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 { diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 7fdda93eb61..8bc29eb33af 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -419,7 +419,7 @@ export default class MatrixChat extends React.PureComponent { 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 { 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 => { diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 2dd432cb928..c5dc2491994 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -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 { // A map to allow groupers to maintain consistent keys even if their first event is uprooted due to back-pagination. public grouperKeyMap = new WeakMap(); - public constructor(props, context) { + public constructor(props: IProps, context: React.ContextType) { super(props, context); this.state = { @@ -308,7 +308,7 @@ export default class MessagePanel extends React.Component { 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 { /* 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 { * * @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 { 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 { // 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 { - const receiptsByEvent = {}; - const receiptsByUserId = {}; + const receiptsByEvent: Record = {}; + const receiptsByUserId: Record< + string, + { + lastShownEventId: string; + receipt: IReadReceiptProps; + } + > = {}; let lastShownEventId; for (const event of this.props.events) { diff --git a/src/components/structures/NonUrgentToastContainer.tsx b/src/components/structures/NonUrgentToastContainer.tsx index 813522ffcb2..508812ae164 100644 --- a/src/components/structures/NonUrgentToastContainer.tsx +++ b/src/components/structures/NonUrgentToastContainer.tsx @@ -27,8 +27,8 @@ interface IState { } export default class NonUrgentToastContainer extends React.PureComponent { - public constructor(props, context) { - super(props, context); + public constructor(props: IProps) { + super(props); this.state = { toasts: NonUrgentToastStore.instance.components, diff --git a/src/components/structures/NotificationPanel.tsx b/src/components/structures/NotificationPanel.tsx index ac351399d49..7a72ea67f7d 100644 --- a/src/components/structures/NotificationPanel.tsx +++ b/src/components/structures/NotificationPanel.tsx @@ -43,7 +43,7 @@ export default class NotificationPanel extends React.PureComponent(); - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 3748ee0ec74..60138dd4428 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -63,7 +63,7 @@ export default class RightPanel extends React.Component { public static contextType = MatrixClientContext; public context!: React.ContextType; - public constructor(props, context) { + public constructor(props: IProps, context: React.ContextType) { super(props, context); this.state = { diff --git a/src/components/structures/RoomStatusBar.tsx b/src/components/structures/RoomStatusBar.tsx index f370091a8af..3a4af77441c 100644 --- a/src/components/structures/RoomStatusBar.tsx +++ b/src/components/structures/RoomStatusBar.tsx @@ -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 { 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; diff --git a/src/components/structures/RoomStatusBarUnsentMessages.tsx b/src/components/structures/RoomStatusBarUnsentMessages.tsx index 3ce300f0eb4..38dbae281e7 100644 --- a/src/components/structures/RoomStatusBarUnsentMessages.tsx +++ b/src/components/structures/RoomStatusBarUnsentMessages.tsx @@ -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; diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 221d2151efd..6e2b72cf9f0 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -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 { 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 { }); }; - 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 { } }; - 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 { 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 { // 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 { }; // 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 { * * 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 { // 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; }; diff --git a/src/components/structures/ScrollPanel.tsx b/src/components/structures/ScrollPanel.tsx index f51cba66a32..7779f97a547 100644 --- a/src/components/structures/ScrollPanel.tsx +++ b/src/components/structures/ScrollPanel.tsx @@ -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 { 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 { // 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 { * 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: diff --git a/src/components/structures/SpaceHierarchy.tsx b/src/components/structures/SpaceHierarchy.tsx index 60a80bc25f0..03401119b0c 100644 --- a/src/components/structures/SpaceHierarchy.tsx +++ b/src/components/structures/SpaceHierarchy.tsx @@ -280,7 +280,7 @@ const Tile: React.FC = ({ ); if (showChildren) { - const onChildrenKeyDown = (e): void => { + const onChildrenKeyDown = (e: React.KeyboardEvent): void => { const action = getKeyBindingsManager().getAccessibilityAction(e); switch (action) { case KeyBindingAction.ArrowLeft: diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 96ff90936d5..6d28106ddb1 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -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) => setRoomName(i, ev.target.value)} autoFocus={i === 2} disabled={busy} autoComplete="off" diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 8d553b55497..ae93080937d 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -137,7 +137,7 @@ export default class ThreadView extends React.Component { }); } - 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 { }; 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, diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index d0f0f9e876b..f49a3ea896c 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -1316,7 +1316,7 @@ class TimelinePanel extends React.Component { * * 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 { * * @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 = {}; const timelines = timelineSet.getTimelines(); const liveTimeline = timelineSet.getLiveTimeline(); diff --git a/src/components/structures/ToastContainer.tsx b/src/components/structures/ToastContainer.tsx index c4d121d12a6..378c33b5136 100644 --- a/src/components/structures/ToastContainer.tsx +++ b/src/components/structures/ToastContainer.tsx @@ -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(), diff --git a/src/components/structures/UploadBar.tsx b/src/components/structures/UploadBar.tsx index 9d256b6aa5d..c928c42ec44 100644 --- a/src/components/structures/UploadBar.tsx +++ b/src/components/structures/UploadBar.tsx @@ -57,7 +57,7 @@ export default class UploadBar extends React.PureComponent { private dispatcherRef: Optional; 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 diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index 4cbe0f5bc60..b0303245f9a 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -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; + (username: undefined, phoneCountry: string, phoneNumber: string, password: string): Promise; +}; + /* * A wire component which glues together login UI components and Login logic */ @@ -110,7 +115,7 @@ export default class LoginComponent extends React.PureComponent private readonly stepRendererMap: Record ReactNode>; - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { @@ -152,7 +157,7 @@ export default class LoginComponent extends React.PureComponent 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 public isBusy = (): boolean => this.state.busy || this.props.busy; - public onPasswordLogin = async (username, phoneCountry, phoneNumber, password): Promise => { + public onPasswordLogin: OnPasswordLogin = async ( + username: string | undefined, + phoneCountry: string | undefined, + phoneNumber: string | undefined, + password: string, + ): Promise => { 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 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 ); }; - public onUsernameChanged = (username): void => { - this.setState({ username: username }); + public onUsernameChanged = (username: string): void => { + this.setState({ username }); }; - public onUsernameBlur = async (username): Promise => { + public onUsernameBlur = async (username: string): Promise => { const doWellknownLookup = username[0] === "@"; this.setState({ username: username, @@ -315,23 +325,21 @@ export default class LoginComponent extends React.PureComponent } }; - 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 ); }; - 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 ( diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index aac39334a0d..7cfebc6d0ad 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -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 { // `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 { } }; - 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 { 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 {

{errorDetail}

); - } 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 { return; } - MatrixClientPeg.setJustRegisteredUserId(response.user_id); + MatrixClientPeg.setJustRegisteredUserId((response as IAuthData).user_id); - const newState = { + const newState: Partial = { 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 { // 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 { // 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 }); // don’t 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 { // 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 { newState.completedNoSignin = true; } - this.setState(newState); + this.setState(newState as IState); }; private setupPushers(): Promise { @@ -455,7 +457,7 @@ export default class Registration extends React.Component { }; private makeRegisterRequest = (auth: IAuthData | null): Promise => { - const registerParams = { + const registerParams: IRegisterRequestParams = { username: this.state.formVals.username, password: this.state.formVals.password, initial_device_display_name: this.props.defaultDeviceDisplayName, diff --git a/src/components/structures/auth/SetupEncryptionBody.tsx b/src/components/structures/auth/SetupEncryptionBody.tsx index 8102cbcfab7..4eec31a226b 100644 --- a/src/components/structures/auth/SetupEncryptionBody.tsx +++ b/src/components/structures/auth/SetupEncryptionBody.tsx @@ -45,7 +45,7 @@ interface IState { } export default class SetupEncryptionBody extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); const store = SetupEncryptionStore.sharedInstance(); store.on("update", this.onStoreUpdate); diff --git a/src/components/structures/auth/SoftLogout.tsx b/src/components/structures/auth/SoftLogout.tsx index d6ad4bfb165..e3cb98bd860 100644 --- a/src/components/structures/auth/SoftLogout.tsx +++ b/src/components/structures/auth/SoftLogout.tsx @@ -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 = { "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 { this.setState({ flows, loginView: chosenView }); } - private onPasswordChange = (ev): void => { + private onPasswordChange = (ev: ChangeEvent): void => { this.setState({ password: ev.target.value }); }; @@ -141,7 +141,7 @@ export default class SoftLogout extends React.Component { dis.dispatch({ action: "start_password_recovery" }); }; - private onPasswordLogin = async (ev): Promise => { + private onPasswordLogin = async (ev: SyntheticEvent): Promise => { ev.preventDefault(); ev.stopPropagation(); @@ -339,7 +339,7 @@ export default class SoftLogout extends React.Component {

{_t("Clear personal data")}

{_t( - "Warning: Your personal data (including encryption keys) is still stored " + + "Warning: your personal data (including encryption keys) is still stored " + "in this session. Clear it if you're finished using this session, or want to sign " + "in to another account.", )} diff --git a/src/components/structures/auth/forgot-password/VerifyEmailModal.tsx b/src/components/structures/auth/forgot-password/VerifyEmailModal.tsx index 1f63d51bd97..d1de4ba4b3f 100644 --- a/src/components/structures/auth/forgot-password/VerifyEmailModal.tsx +++ b/src/components/structures/auth/forgot-password/VerifyEmailModal.tsx @@ -52,8 +52,8 @@ export const VerifyEmailModal: React.FC = ({

{_t("Verify your email to continue")}

{_t( - `We need to know it’s you before resetting your password. - Click the link in the email we just sent to %(email)s`, + "We need to know it’s you before resetting your password. " + + "Click the link in the email we just sent to %(email)s", { email, }, diff --git a/src/components/views/audio_messages/DurationClock.tsx b/src/components/views/audio_messages/DurationClock.tsx index 975fb79a361..805c8f4f834 100644 --- a/src/components/views/audio_messages/DurationClock.tsx +++ b/src/components/views/audio_messages/DurationClock.tsx @@ -31,7 +31,7 @@ interface IState { * A clock which shows a clip's maximum duration. */ export default class DurationClock extends React.PureComponent { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/audio_messages/LiveRecordingClock.tsx b/src/components/views/audio_messages/LiveRecordingClock.tsx index 1a3f30b1087..50dd272a0d8 100644 --- a/src/components/views/audio_messages/LiveRecordingClock.tsx +++ b/src/components/views/audio_messages/LiveRecordingClock.tsx @@ -34,12 +34,12 @@ interface IState { */ export default class LiveRecordingClock extends React.PureComponent { 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, diff --git a/src/components/views/audio_messages/LiveRecordingWaveform.tsx b/src/components/views/audio_messages/LiveRecordingWaveform.tsx index 36ca2b4e135..050a01a21e1 100644 --- a/src/components/views/audio_messages/LiveRecordingWaveform.tsx +++ b/src/components/views/audio_messages/LiveRecordingWaveform.tsx @@ -39,12 +39,12 @@ export default class LiveRecordingWaveform extends React.PureComponent this.updateWaveform(), () => requestAnimationFrame(() => this.scheduledUpdate.trigger()), ); - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { waveform: arraySeed(0, RECORDING_PLAYBACK_SAMPLES), diff --git a/src/components/views/audio_messages/PlayPauseButton.tsx b/src/components/views/audio_messages/PlayPauseButton.tsx index eb31c01c333..a865f0aeef2 100644 --- a/src/components/views/audio_messages/PlayPauseButton.tsx +++ b/src/components/views/audio_messages/PlayPauseButton.tsx @@ -35,7 +35,7 @@ interface IProps extends Omit { - public constructor(props) { + public constructor(props: IProps) { super(props); } diff --git a/src/components/views/audio_messages/PlaybackClock.tsx b/src/components/views/audio_messages/PlaybackClock.tsx index e225bf1150b..c5e569ce921 100644 --- a/src/components/views/audio_messages/PlaybackClock.tsx +++ b/src/components/views/audio_messages/PlaybackClock.tsx @@ -39,7 +39,7 @@ interface IState { * A clock for a playback of a recording. */ export default class PlaybackClock extends React.PureComponent { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/audio_messages/PlaybackWaveform.tsx b/src/components/views/audio_messages/PlaybackWaveform.tsx index fe86ef3326e..32c34db6b29 100644 --- a/src/components/views/audio_messages/PlaybackWaveform.tsx +++ b/src/components/views/audio_messages/PlaybackWaveform.tsx @@ -34,7 +34,7 @@ interface IState { * A waveform which shows the waveform of a previously recorded recording */ export default class PlaybackWaveform extends React.PureComponent { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/audio_messages/SeekBar.tsx b/src/components/views/audio_messages/SeekBar.tsx index 85fef772784..4af84325e58 100644 --- a/src/components/views/audio_messages/SeekBar.tsx +++ b/src/components/views/audio_messages/SeekBar.tsx @@ -46,7 +46,7 @@ export default class SeekBar extends React.PureComponent { // 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()), ); diff --git a/src/components/views/auth/CountryDropdown.tsx b/src/components/views/auth/CountryDropdown.tsx index ae155696c02..4b4396ebb5e 100644 --- a/src/components/views/auth/CountryDropdown.tsx +++ b/src/components/views/auth/CountryDropdown.tsx @@ -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 = {}; for (const c of COUNTRIES) { COUNTRIES_BY_ISO2[c.iso2] = c; } diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx index 4a995e4d06b..2d11ff3fab8 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx +++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx @@ -100,7 +100,7 @@ interface IPasswordAuthEntryState { export class PasswordAuthEntry extends React.Component { 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 { 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 = {}; + 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 = {}; 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) => ( - null} disabled> + {text} @@ -555,7 +559,7 @@ export class MsisdnAuthEntry extends React.Component { private popupWindow: Window; private fallbackButton = createRef(); - public constructor(props) { + public constructor(props: IAuthEntryProps) { super(props); // we have to make the user click a button, as browsers will block diff --git a/src/components/views/auth/LoginWithQR.tsx b/src/components/views/auth/LoginWithQR.tsx index 74e69c96b7d..4ca07323f7a 100644 --- a/src/components/views/auth/LoginWithQR.tsx +++ b/src/components/views/auth/LoginWithQR.tsx @@ -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 { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/auth/LoginWithQRFlow.tsx b/src/components/views/auth/LoginWithQRFlow.tsx index be79d8d2f38..fce721bbe8d 100644 --- a/src/components/views/auth/LoginWithQRFlow.tsx +++ b/src/components/views/auth/LoginWithQRFlow.tsx @@ -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 { - public constructor(props) { + public constructor(props: IProps) { super(props); } @@ -184,7 +184,11 @@ export default class LoginWithQRFlow extends React.Component {

{_t("Scan the QR code below with your device that's signed out.")}

  1. {_t("Start at the sign in screen")}
  2. -
  3. {_t("Select 'Scan QR code'")}
  4. +
  5. + {_t("Select '%(scanQRCode)s'", { + scanQRCode: _t("Scan QR code"), + })} +
  6. {_t("Review and approve the sign in")}
{code} diff --git a/src/components/views/auth/PassphraseConfirmField.tsx b/src/components/views/auth/PassphraseConfirmField.tsx index 36b6bdb7fbb..1d002310fa5 100644 --- a/src/components/views/auth/PassphraseConfirmField.tsx +++ b/src/components/views/auth/PassphraseConfirmField.tsx @@ -30,8 +30,8 @@ interface IProps extends Omit { labelRequired?: string; labelInvalid?: string; - onChange(ev: React.FormEvent); - onValidate?(result: IValidationResult); + onChange(ev: React.FormEvent): void; + onValidate?(result: IValidationResult): void; } class PassphraseConfirmField extends PureComponent { diff --git a/src/components/views/auth/PassphraseField.tsx b/src/components/views/auth/PassphraseField.tsx index f8e82d68bf1..f9cbc8fe063 100644 --- a/src/components/views/auth/PassphraseField.tsx +++ b/src/components/views/auth/PassphraseField.tsx @@ -36,8 +36,8 @@ interface IProps extends Omit { labelStrongPassword?: string; labelAllowedButUnsafe?: string; - onChange(ev: React.FormEvent); - onValidate?(result: IValidationResult); + onChange(ev: React.FormEvent): void; + onValidate?(result: IValidationResult): void; } class PassphraseField extends PureComponent { diff --git a/src/components/views/auth/PasswordLogin.tsx b/src/components/views/auth/PasswordLogin.tsx index 164860ab416..3100b36a831 100644 --- a/src/components/views/auth/PasswordLogin.tsx +++ b/src/components/views/auth/PasswordLogin.tsx @@ -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>; 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 { + 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 { 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 { }; } - private onForgotPasswordClick = (ev): void => { + private onForgotPasswordClick = (ev: ButtonEvent): void => { ev.preventDefault(); ev.stopPropagation(); this.props.onForgotPasswordClick(); }; - private onSubmitForm = async (ev): Promise => { + private onSubmitForm = async (ev: SyntheticEvent): Promise => { ev.preventDefault(); const allFieldsValid = await this.verifyFieldsBeforeSubmit(); @@ -99,47 +104,40 @@ export default class PasswordLogin extends React.PureComponent { 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): void => { this.props.onUsernameChanged(ev.target.value); }; - private onUsernameBlur = (ev): void => { + private onUsernameBlur = (ev: React.FocusEvent): void => { this.props.onUsernameBlur(ev.target.value); }; - private onLoginTypeChange = (ev): void => { - const loginType = ev.target.value; + private onLoginTypeChange = (ev: React.ChangeEvent): 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): void => { this.props.onPhoneNumberChanged(ev.target.value); }; - private onPasswordChanged = (ev): void => { + private onPasswordChanged = (ev: React.ChangeEvent): void => { this.setState({ password: ev.target.value }); }; @@ -151,7 +149,7 @@ export default class PasswordLogin extends React.PureComponent { 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 { ], }); - private onUsernameValidate = async (fieldState): Promise => { + private onUsernameValidate = async (fieldState: IFieldState): Promise => { 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 { ], }); - private onPhoneNumberValidate = async (fieldState): Promise => { + private onPhoneNumberValidate = async (fieldState: IFieldState): Promise => { 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 { ], }); - private onPasswordValidate = async (fieldState): Promise => { + private onPasswordValidate = async (fieldState: IFieldState): Promise => { const result = await this.validatePasswordRules(fieldState); this.markFieldValid(LoginField.Password, result.valid); return result; diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index 3f41cb61273..4d2cd9214c6 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -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 { + 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 => { + private onSubmit = async ( + ev: BaseSyntheticEvent, + ): Promise => { ev.preventDefault(); ev.persist(); @@ -152,7 +160,9 @@ export default class RegistrationForm extends React.PureComponent, + ): void { PosthogAnalytics.instance.setAuthenticationType("Password"); const email = this.state.email.trim(); @@ -248,7 +258,7 @@ export default class RegistrationForm extends React.PureComponent { + private onEmailChange = (ev: React.ChangeEvent): void => { this.setState({ email: ev.target.value.trim(), }); @@ -277,7 +287,7 @@ export default class RegistrationForm extends React.PureComponent { + private onPasswordChange = (ev: React.ChangeEvent): void => { this.setState({ password: ev.target.value, }); @@ -287,7 +297,7 @@ export default class RegistrationForm extends React.PureComponent { + private onPasswordConfirmChange = (ev: React.ChangeEvent): void => { this.setState({ passwordConfirm: ev.target.value, }); @@ -297,19 +307,19 @@ export default class RegistrationForm extends React.PureComponent { + private onPhoneCountryChange = (newVal: PhoneNumberCountryDefinition): void => { this.setState({ phoneCountry: newVal.iso2, }); }; - private onPhoneNumberChange = (ev): void => { + private onPhoneNumberChange = (ev: React.ChangeEvent): void => { this.setState({ phoneNumber: ev.target.value, }); }; - private onPhoneNumberValidate = async (fieldState): Promise => { + private onPhoneNumberValidate = async (fieldState: IFieldState): Promise => { 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 { + private onUsernameChange = (ev: React.ChangeEvent): void => { this.setState({ username: ev.target.value, }); }; - private onUsernameValidate = async (fieldState): Promise => { + private onUsernameValidate = async (fieldState: IFieldState): Promise => { const result = await this.validateUsernameRules(fieldState); this.markFieldValid(RegistrationField.Username, result.valid); return result; diff --git a/src/components/views/avatars/BaseAvatar.tsx b/src/components/views/avatars/BaseAvatar.tsx index 025cb9d2711..469c5fbb2ab 100644 --- a/src/components/views/avatars/BaseAvatar.tsx +++ b/src/components/views/avatars/BaseAvatar.tsx @@ -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); diff --git a/src/components/views/context_menus/DialpadContextMenu.tsx b/src/components/views/context_menus/DialpadContextMenu.tsx index b0af3f4cc7e..9d8c5c8e90a 100644 --- a/src/components/views/context_menus/DialpadContextMenu.tsx +++ b/src/components/views/context_menus/DialpadContextMenu.tsx @@ -34,7 +34,7 @@ interface IState { export default class DialpadContextMenu extends React.Component { private numberEntryFieldRef: React.RefObject = createRef(); - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { @@ -58,14 +58,14 @@ export default class DialpadContextMenu extends React.Component 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): void => { this.setState({ value: ev.target.value }); }; diff --git a/src/components/views/context_menus/LegacyCallContextMenu.tsx b/src/components/views/context_menus/LegacyCallContextMenu.tsx index 8e4efa28e47..4de776ff066 100644 --- a/src/components/views/context_menus/LegacyCallContextMenu.tsx +++ b/src/components/views/context_menus/LegacyCallContextMenu.tsx @@ -26,7 +26,7 @@ interface IProps extends IContextMenuProps { } export default class LegacyCallContextMenu extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); } diff --git a/src/components/views/dialogs/BaseDialog.tsx b/src/components/views/dialogs/BaseDialog.tsx index 2eb5234caa9..1f16821ee5a 100644 --- a/src/components/views/dialogs/BaseDialog.tsx +++ b/src/components/views/dialogs/BaseDialog.tsx @@ -88,7 +88,7 @@ export default class BaseDialog extends React.Component { 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 { headerImage = ; } - const lockProps = { + const lockProps: Record = { "onKeyDown": this.onKeyDown, "role": "dialog", // This should point to a node describing the dialog. diff --git a/src/components/views/dialogs/BugReportDialog.tsx b/src/components/views/dialogs/BugReportDialog.tsx index 7b389a3e9b6..e3ae127024a 100644 --- a/src/components/views/dialogs/BugReportDialog.tsx +++ b/src/components/views/dialogs/BugReportDialog.tsx @@ -54,7 +54,7 @@ interface IState { export default class BugReportDialog extends React.Component { private unmounted: boolean; - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { sendLogs: true, diff --git a/src/components/views/dialogs/BulkRedactDialog.tsx b/src/components/views/dialogs/BulkRedactDialog.tsx index 9c503d24fe0..e1f9a11cd22 100644 --- a/src/components/views/dialogs/BulkRedactDialog.tsx +++ b/src/components/views/dialogs/BulkRedactDialog.tsx @@ -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 = (props) => { const [keepStateEvents, setKeepStateEvents] = useState(true); let timeline = room.getLiveTimeline(); - let eventsToRedact = []; + let eventsToRedact: MatrixEvent[] = []; while (timeline) { eventsToRedact = [ ...eventsToRedact, diff --git a/src/components/views/dialogs/ChangelogDialog.tsx b/src/components/views/dialogs/ChangelogDialog.tsx index 696c3616bd1..1d7c3e196af 100644 --- a/src/components/views/dialogs/ChangelogDialog.tsx +++ b/src/components/views/dialogs/ChangelogDialog.tsx @@ -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>; + +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 { - public constructor(props) { +export default class ChangelogDialog extends React.Component { + public constructor(props: IProps) { super(props); this.state = {}; } - private async fetchChanges(repo: string, oldVersion: string, newVersion: string): Promise { + private async fetchChanges(repo: typeof REPOS[number], oldVersion: string, newVersion: string): Promise { const url = `https://riot.im/github/repos/${repo}/compare/${oldVersion}...${newVersion}`; try { @@ -66,7 +76,7 @@ export default class ChangelogDialog extends React.Component { } } - private elementsForCommit(commit): JSX.Element { + private elementsForCommit(commit: Commit): JSX.Element { return (
  • @@ -86,7 +96,7 @@ export default class ChangelogDialog extends React.Component { msg: this.state[repo], }); } else { - content = this.state[repo].map(this.elementsForCommit); + content = (this.state[repo] as Commit[]).map(this.elementsForCommit); } return (
    diff --git a/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx b/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx index a9cf28d91a3..1d8e4880737 100644 --- a/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx +++ b/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx @@ -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 { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { isRedacting: false, diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index 0fcb6e1353e..c13f7921ab8 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -62,7 +62,7 @@ export default class CreateRoomDialog extends React.Component { private nameField = createRef(); private aliasField = createRef(); - public constructor(props) { + public constructor(props: IProps) { super(props); this.supportsRestricted = !!this.props.parentSpace; diff --git a/src/components/views/dialogs/CreateSubspaceDialog.tsx b/src/components/views/dialogs/CreateSubspaceDialog.tsx index 96d8eec1b8d..aaf14452e3a 100644 --- a/src/components/views/dialogs/CreateSubspaceDialog.tsx +++ b/src/components/views/dialogs/CreateSubspaceDialog.tsx @@ -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 = ({ space, onAddExistingSpaceClick } const [joinRule, setJoinRule] = useState(defaultJoinRule); - const onCreateSubspaceClick = async (e): Promise => { + const onCreateSubspaceClick = async (e: ButtonEvent): Promise => { e.preventDefault(); if (busy) return; diff --git a/src/components/views/dialogs/DeactivateAccountDialog.tsx b/src/components/views/dialogs/DeactivateAccountDialog.tsx index ad9f657baa1..8876038ec40 100644 --- a/src/components/views/dialogs/DeactivateAccountDialog.tsx +++ b/src/components/views/dialogs/DeactivateAccountDialog.tsx @@ -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 { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { @@ -65,7 +75,7 @@ export default class DeactivateAccountDialog extends React.Component { + 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 = ({ roomId, onFinished }) => { {Object.entries(Tools).map(([category, tools]) => (
    -

    {_t(categoryLabels[category])}

    +

    {_t(categoryLabels[category as unknown as Category])}

    {tools.map(([label, tool]) => { const onClick = (): void => { setTool([label, tool]); diff --git a/src/components/views/dialogs/ErrorDialog.tsx b/src/components/views/dialogs/ErrorDialog.tsx index d0d4b22f6e0..298398b0781 100644 --- a/src/components/views/dialogs/ErrorDialog.tsx +++ b/src/components/views/dialogs/ErrorDialog.tsx @@ -46,9 +46,6 @@ interface IState { export default class ErrorDialog extends React.Component { public static defaultProps = { focus: true, - title: null, - description: null, - button: null, }; private onClick = (): void => { diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx index 6b7df64cb53..58b84436a50 100644 --- a/src/components/views/dialogs/ExportDialog.tsx +++ b/src/components/views/dialogs/ExportDialog.tsx @@ -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 = ({ 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 ( - ); }); @@ -332,7 +339,7 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { setExportFormat(ExportFormat[key])} + onChange={(key: ExportFormatKey) => setExportFormat(ExportFormat[key])} definitions={exportFormatOptions} /> @@ -347,7 +354,7 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { element="select" value={exportType} onChange={(e) => { - setExportType(ExportType[e.target.value]); + setExportType(ExportType[e.target.value as ExportTypeKey]); }} > {exportTypeOptions} diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 561bbd0b1c6..0bd462c65a4 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -243,7 +243,7 @@ const ForwardDialog: React.FC = ({ 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 ( => { + private onAccountDetailsDialogFinished = async (result: boolean): Promise => { if (result) { return this.sendAccountDetails(); } diff --git a/src/components/views/dialogs/IntegrationsDisabledDialog.tsx b/src/components/views/dialogs/IntegrationsDisabledDialog.tsx index e28d83aae75..d542c9a0fea 100644 --- a/src/components/views/dialogs/IntegrationsDisabledDialog.tsx +++ b/src/components/views/dialogs/IntegrationsDisabledDialog.tsx @@ -44,7 +44,11 @@ export default class IntegrationsDisabledDialog extends React.Component title={_t("Integrations are disabled")} >
    -

    {_t("Enable 'Manage Integrations' in Settings to do this.")}

    +

    + {_t("Enable '%(manageIntegrations)s' in Settings to do this.", { + manageIntegrations: _t("Manage integrations"), + })} +

    ; 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 { @@ -95,7 +95,7 @@ export default class InteractiveAuthDialog extends React.Component { + 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 }); }; diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index ff28f6636a1..4cb29c61d70 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -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 { - 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 { - 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 = 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 alreadyInvited.add(m.userId)); @@ -361,7 +365,7 @@ export default class InviteDialog extends React.PureComponent { + private onConsultFirstChange = (ev: React.ChangeEvent): void => { this.setState({ consultFirst: ev.target.checked }); }; @@ -538,11 +542,11 @@ export default class InviteDialog extends React.PureComponent { + private onKeyDown = (e: React.KeyboardEvent): 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 { + private updateFilter = (e: React.ChangeEvent): void => { const term = e.target.value; this.setState({ filterText: term }); @@ -750,7 +754,7 @@ export default class InviteDialog extends React.PureComponent => { + private onPaste = async (e: React.ClipboardEvent): Promise => { 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 { + 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 { + 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 { + 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 { + private onDialFormSubmit = (ev: SyntheticEvent): void => { ev.preventDefault(); this.transferCall(); }; - private onDialChange = (ev): void => { + private onDialChange = (ev: React.ChangeEvent): void => { this.setState({ dialPadValue: ev.currentTarget.value }); }; @@ -1066,9 +1070,9 @@ export default class InviteDialog extends React.PureComponent { + private async onLinkClick(e: React.MouseEvent): Promise { e.preventDefault(); - selectText(e.target); + selectText(e.currentTarget); } private get screenName(): ScreenName { diff --git a/src/components/views/dialogs/LogoutDialog.tsx b/src/components/views/dialogs/LogoutDialog.tsx index 6e4fb1b2e9f..dfb4a5fb375 100644 --- a/src/components/views/dialogs/LogoutDialog.tsx +++ b/src/components/views/dialogs/LogoutDialog.tsx @@ -45,7 +45,7 @@ export default class LogoutDialog extends React.Component { onFinished: function () {}, }; - public constructor(props) { + public constructor(props: IProps) { super(props); const cli = MatrixClientPeg.get(); diff --git a/src/components/views/dialogs/MessageEditHistoryDialog.tsx b/src/components/views/dialogs/MessageEditHistoryDialog.tsx index 8775b4eb5c3..2e3d0aca05c 100644 --- a/src/components/views/dialogs/MessageEditHistoryDialog.tsx +++ b/src/components/views/dialogs/MessageEditHistoryDialog.tsx @@ -121,8 +121,8 @@ export default class MessageEditHistoryDialog extends React.PureComponent b.disabled).map((b) => b.id), }; - public constructor(props) { + public constructor(props: IProps) { super(props); this.widget = new ElementWidget({ diff --git a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx index e3be5410148..1e6c9901ee3 100644 --- a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx +++ b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx @@ -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 = ({ onFinished }) => { const [email, setEmail] = useState(""); const fieldRef = useRef(); - const onSubmit = async (e): Promise => { + const onSubmit = async (e: SyntheticEvent): Promise => { e.preventDefault(); if (email) { const valid = await fieldRef.current.validate({}); diff --git a/src/components/views/dialogs/ReportEventDialog.tsx b/src/components/views/dialogs/ReportEventDialog.tsx index 28ae30b6f1e..00e430b2c3a 100644 --- a/src/components/views/dialogs/ReportEventDialog.tsx +++ b/src/components/views/dialogs/ReportEventDialog.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ChangeEvent } from "react"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { logger } from "matrix-js-sdk/src/logger"; @@ -189,7 +189,7 @@ export default class ReportEventDialog extends React.Component { }; // The user has written down a freeform description of the abuse. - private onReasonChange = ({ target: { value: reason } }): void => { + private onReasonChange = ({ target: { value: reason } }: ChangeEvent): void => { this.setState({ reason }); }; diff --git a/src/components/views/dialogs/RoomSettingsDialog.tsx b/src/components/views/dialogs/RoomSettingsDialog.tsx index 68616cf1179..67b46b371e4 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.tsx +++ b/src/components/views/dialogs/RoomSettingsDialog.tsx @@ -33,6 +33,7 @@ import { UIFeature } from "../../../settings/UIFeature"; import BaseDialog from "./BaseDialog"; import { Action } from "../../../dispatcher/actions"; import { VoipRoomSettingsTab } from "../settings/tabs/room/VoipRoomSettingsTab"; +import { ActionPayload } from "../../../dispatcher/payloads"; export const ROOM_GENERAL_TAB = "ROOM_GENERAL_TAB"; export const ROOM_VOIP_TAB = "ROOM_VOIP_TAB"; @@ -74,7 +75,7 @@ export default class RoomSettingsDialog extends React.Component MatrixClientPeg.get().removeListener(RoomEvent.Name, this.onRoomName); } - private onAction = (payload): void => { + private onAction = (payload: ActionPayload): void => { // When view changes below us, close the room settings // whilst the modal is open this can only be triggered when someone hits Leave Room if (payload.action === Action.ViewHomePage) { diff --git a/src/components/views/dialogs/RoomUpgradeWarningDialog.tsx b/src/components/views/dialogs/RoomUpgradeWarningDialog.tsx index a62c1467dc5..c3b2cd19bf0 100644 --- a/src/components/views/dialogs/RoomUpgradeWarningDialog.tsx +++ b/src/components/views/dialogs/RoomUpgradeWarningDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactNode } from "react"; +import React, { ReactNode, SyntheticEvent } from "react"; import { EventType } from "matrix-js-sdk/src/@types/event"; import { JoinRule } from "matrix-js-sdk/src/@types/partials"; @@ -53,7 +53,7 @@ export default class RoomUpgradeWarningDialog extends React.Component { + private openBugReportDialog = (e: SyntheticEvent): void => { e.preventDefault(); e.stopPropagation(); diff --git a/src/components/views/dialogs/ServerPickerDialog.tsx b/src/components/views/dialogs/ServerPickerDialog.tsx index 31c7a006598..7c567dadf64 100644 --- a/src/components/views/dialogs/ServerPickerDialog.tsx +++ b/src/components/views/dialogs/ServerPickerDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef } from "react"; +import React, { ChangeEvent, createRef, SyntheticEvent } from "react"; import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery"; import { logger } from "matrix-js-sdk/src/logger"; @@ -45,7 +45,7 @@ export default class ServerPickerDialog extends React.PureComponent(); private validatedConf: ValidatedServerConfig; - public constructor(props) { + public constructor(props: IProps) { super(props); const config = SdkConfig.get(); @@ -75,7 +75,7 @@ export default class ServerPickerDialog extends React.PureComponent { + private onHomeserverChange = (ev: ChangeEvent): void => { this.setState({ otherHomeserver: ev.target.value }); }; @@ -149,7 +149,7 @@ export default class ServerPickerDialog extends React.PureComponent => this.validate(fieldState); - private onSubmit = async (ev): Promise => { + private onSubmit = async (ev: SyntheticEvent): Promise => { ev.preventDefault(); const valid = await this.fieldRef.current.validate({ allowEmpty: false }); diff --git a/src/components/views/dialogs/ShareDialog.tsx b/src/components/views/dialogs/ShareDialog.tsx index b3761a49b69..9a78b35b2f0 100644 --- a/src/components/views/dialogs/ShareDialog.tsx +++ b/src/components/views/dialogs/ShareDialog.tsx @@ -36,12 +36,12 @@ const socials = [ { name: "Facebook", img: require("../../../../res/img/social/facebook.png"), - url: (url) => `https://www.facebook.com/sharer/sharer.php?u=${url}`, + url: (url: String) => `https://www.facebook.com/sharer/sharer.php?u=${url}`, }, { name: "Twitter", img: require("../../../../res/img/social/twitter-2.png"), - url: (url) => `https://twitter.com/home?status=${url}`, + url: (url: string) => `https://twitter.com/home?status=${url}`, }, /* // icon missing name: 'Google Plus', @@ -50,17 +50,17 @@ const socials = [ },*/ { name: "LinkedIn", img: require("../../../../res/img/social/linkedin.png"), - url: (url) => `https://www.linkedin.com/shareArticle?mini=true&url=${url}`, + url: (url: string) => `https://www.linkedin.com/shareArticle?mini=true&url=${url}`, }, { name: "Reddit", img: require("../../../../res/img/social/reddit.png"), - url: (url) => `https://www.reddit.com/submit?url=${url}`, + url: (url: string) => `https://www.reddit.com/submit?url=${url}`, }, { name: "email", img: require("../../../../res/img/social/email-1.png"), - url: (url) => `mailto:?body=${url}`, + url: (url: string) => `mailto:?body=${url}`, }, ]; @@ -75,7 +75,7 @@ interface IState { } export default class ShareDialog extends React.PureComponent { - public constructor(props) { + public constructor(props: IProps) { super(props); let permalinkCreator: RoomPermalinkCreator = null; @@ -91,9 +91,9 @@ export default class ShareDialog extends React.PureComponent { }; } - public static onLinkClick(e): void { + public static onLinkClick(e: React.MouseEvent): void { e.preventDefault(); - selectText(e.target); + selectText(e.currentTarget); } private onLinkSpecificEventCheckboxClick = (): void => { diff --git a/src/components/views/dialogs/SlashCommandHelpDialog.tsx b/src/components/views/dialogs/SlashCommandHelpDialog.tsx index f12143418c4..1324babbebe 100644 --- a/src/components/views/dialogs/SlashCommandHelpDialog.tsx +++ b/src/components/views/dialogs/SlashCommandHelpDialog.tsx @@ -17,14 +17,14 @@ limitations under the License. import React from "react"; import { _t } from "../../../languageHandler"; -import { CommandCategories, Commands } from "../../../SlashCommands"; +import { Command, CommandCategories, Commands } from "../../../SlashCommands"; import { IDialogProps } from "./IDialogProps"; import InfoDialog from "./InfoDialog"; interface IProps extends IDialogProps {} const SlashCommandHelpDialog: React.FC = ({ onFinished }) => { - const categories = {}; + const categories: Record = {}; Commands.forEach((cmd) => { if (!cmd.isEnabled()) return; if (!categories[cmd.category]) { diff --git a/src/components/views/dialogs/TermsDialog.tsx b/src/components/views/dialogs/TermsDialog.tsx index 819e14be7c3..fd420d436c2 100644 --- a/src/components/views/dialogs/TermsDialog.tsx +++ b/src/components/views/dialogs/TermsDialog.tsx @@ -21,6 +21,7 @@ import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types"; import { _t, pickBestLanguage } from "../../../languageHandler"; import DialogButtons from "../elements/DialogButtons"; import BaseDialog from "./BaseDialog"; +import { ServicePolicyPair } from "../../../Terms"; interface ITermsCheckboxProps { onChange: (url: string, checked: boolean) => void; @@ -43,7 +44,7 @@ interface ITermsDialogProps { * Array of [Service, policies] pairs, where policies is the response from the * /terms endpoint for that service */ - policiesAndServicePairs: any[]; + policiesAndServicePairs: ServicePolicyPair[]; /** * urls that the user has already agreed to @@ -63,7 +64,7 @@ interface IState { } export default class TermsDialog extends React.PureComponent { - public constructor(props) { + public constructor(props: ITermsDialogProps) { super(props); this.state = { // url -> boolean diff --git a/src/components/views/dialogs/UploadConfirmDialog.tsx b/src/components/views/dialogs/UploadConfirmDialog.tsx index 9b22b33b67f..688ee03b811 100644 --- a/src/components/views/dialogs/UploadConfirmDialog.tsx +++ b/src/components/views/dialogs/UploadConfirmDialog.tsx @@ -39,7 +39,7 @@ export default class UploadConfirmDialog extends React.Component { totalFiles: 1, }; - public constructor(props) { + public constructor(props: IProps) { super(props); // Create a fresh `Blob` for previewing (even though `File` already is diff --git a/src/components/views/dialogs/UserSettingsDialog.tsx b/src/components/views/dialogs/UserSettingsDialog.tsx index 7b33ca58da3..aca4a1b37b9 100644 --- a/src/components/views/dialogs/UserSettingsDialog.tsx +++ b/src/components/views/dialogs/UserSettingsDialog.tsx @@ -50,7 +50,7 @@ interface IState { export default class UserSettingsDialog extends React.Component { private settingsWatchers: string[] = []; - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/dialogs/VerificationRequestDialog.tsx b/src/components/views/dialogs/VerificationRequestDialog.tsx index 25e7e835bd0..b8b1e2ed8b8 100644 --- a/src/components/views/dialogs/VerificationRequestDialog.tsx +++ b/src/components/views/dialogs/VerificationRequestDialog.tsx @@ -35,7 +35,7 @@ interface IState { } export default class VerificationRequestDialog extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { verificationRequest: this.props.verificationRequest, diff --git a/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx b/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx index 4c3acb89600..4ede87b1ebe 100644 --- a/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx +++ b/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx @@ -33,13 +33,12 @@ interface IProps extends IDialogProps { widgetKind: WidgetKind; // TODO: Refactor into the Widget class } -interface IBooleanStates { - // @ts-ignore - TS wants a string key, but we know better - [capability: Capability]: boolean; -} +type BooleanStates = Partial<{ + [capability in Capability]: boolean; +}>; interface IState { - booleanStates: IBooleanStates; + booleanStates: BooleanStates; rememberSelection: boolean; } @@ -52,7 +51,7 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent< const parsedEvents = WidgetEventCapability.findEventCapabilities(this.props.requestedCapabilities); parsedEvents.forEach((e) => this.eventPermissionsMap.set(e.raw, e)); - const states: IBooleanStates = {}; + const states: BooleanStates = {}; this.props.requestedCapabilities.forEach((c) => (states[c] = true)); this.state = { @@ -71,7 +70,7 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent< this.setState({ rememberSelection: newVal }); }; - private onSubmit = async (ev): Promise => { + private onSubmit = async (): Promise => { this.closeAndTryRemember( Object.entries(this.state.booleanStates) .filter(([_, isSelected]) => isSelected) @@ -79,7 +78,7 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent< ); }; - private onReject = async (ev): Promise => { + private onReject = async (): Promise => { this.closeAndTryRemember([]); // nothing was approved }; diff --git a/src/components/views/dialogs/devtools/Event.tsx b/src/components/views/dialogs/devtools/Event.tsx index 6af242b68b7..ce7b1e23617 100644 --- a/src/components/views/dialogs/devtools/Event.tsx +++ b/src/components/views/dialogs/devtools/Event.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useContext, useMemo, useRef, useState } from "react"; +import React, { ChangeEvent, useContext, useMemo, useRef, useState } from "react"; import { IContent, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { _t, _td } from "../../../../languageHandler"; @@ -87,7 +87,7 @@ export const EventEditor: React.FC = ({ fieldDefs, defaultCon type="text" autoComplete="on" value={fieldData[i]} - onChange={(ev) => + onChange={(ev: ChangeEvent) => setFieldData((data) => { data[i] = ev.target.value; return [...data]; diff --git a/src/components/views/dialogs/devtools/FilteredList.tsx b/src/components/views/dialogs/devtools/FilteredList.tsx index 46d3f956614..11ee0156714 100644 --- a/src/components/views/dialogs/devtools/FilteredList.tsx +++ b/src/components/views/dialogs/devtools/FilteredList.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useEffect, useState } from "react"; +import React, { ChangeEvent, useEffect, useState } from "react"; import { _t } from "../../../../languageHandler"; import Field from "../../elements/Field"; @@ -72,7 +72,7 @@ const FilteredList: React.FC = ({ children, query, onChange }) => { type="text" autoComplete="off" value={query} - onChange={(ev) => onChange(ev.target.value)} + onChange={(ev: ChangeEvent) => onChange(ev.target.value)} className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query" // force re-render so that autoFocus is applied when this component is re-used key={children?.[0]?.key ?? ""} diff --git a/src/components/views/dialogs/devtools/SettingExplorer.tsx b/src/components/views/dialogs/devtools/SettingExplorer.tsx index 72876f35511..c0801cd062b 100644 --- a/src/components/views/dialogs/devtools/SettingExplorer.tsx +++ b/src/components/views/dialogs/devtools/SettingExplorer.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useContext, useMemo, useState } from "react"; +import React, { ChangeEvent, useContext, useMemo, useState } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import { _t } from "../../../../languageHandler"; @@ -74,7 +74,7 @@ const CanEditLevelField: React.FC = ({ setting, roomId, }; function renderExplicitSettingValues(setting: string, roomId: string): string { - const vals = {}; + const vals: Record = {}; for (const level of LEVEL_ORDER) { try { vals[level] = SettingsStore.getValueAt(level, setting, roomId, true, true); @@ -283,7 +283,7 @@ const SettingsList: React.FC = ({ onBack, onView, onEdit }) type="text" autoComplete="off" value={query} - onChange={(ev) => setQuery(ev.target.value)} + onChange={(ev: ChangeEvent) => setQuery(ev.target.value)} className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query" /> diff --git a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx index d7154b3aa2f..8964b189929 100644 --- a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx +++ b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx @@ -62,7 +62,7 @@ interface IState { export default class AccessSecretStorageDialog extends React.PureComponent { private fileUpload = React.createRef(); - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/dialogs/security/CreateCrossSigningDialog.tsx b/src/components/views/dialogs/security/CreateCrossSigningDialog.tsx index b69fc9cbb32..0b6438df72a 100644 --- a/src/components/views/dialogs/security/CreateCrossSigningDialog.tsx +++ b/src/components/views/dialogs/security/CreateCrossSigningDialog.tsx @@ -18,6 +18,7 @@ limitations under the License. import React from "react"; import { CrossSigningKeys } from "matrix-js-sdk/src/client"; import { logger } from "matrix-js-sdk/src/logger"; +import { UIAFlow } from "matrix-js-sdk/src/matrix"; import { MatrixClientPeg } from "../../../../MatrixClientPeg"; import { _t } from "../../../../languageHandler"; @@ -82,7 +83,7 @@ export default class CreateCrossSigningDialog extends React.PureComponent { + const canUploadKeysWithPasswordOnly = error.data.flows.some((f: UIAFlow) => { return f.stages.length === 1 && f.stages[0] === "m.login.password"; }); this.setState({ diff --git a/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx b/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx index bc386cb9763..66a9314ea3d 100644 --- a/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx +++ b/src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ChangeEvent } from "react"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { IKeyBackupInfo, IKeyBackupRestoreResult } from "matrix-js-sdk/src/crypto/keybackup"; import { ISecretStorageKeyInfo } from "matrix-js-sdk/src/crypto/api"; @@ -81,7 +81,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { + private progressCallback = (data: IState["progress"]): void => { this.setState({ progress: data, }); @@ -128,7 +128,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent => {}, /* forceReset = */ true); }; - private onRecoveryKeyChange = (e): void => { + private onRecoveryKeyChange = (e: ChangeEvent): void => { this.setState({ recoveryKey: e.target.value, recoveryKeyValid: MatrixClientPeg.get().isValidRecoveryKey(e.target.value), @@ -213,7 +213,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { + private onPassPhraseChange = (e: ChangeEvent): void => { this.setState({ passPhrase: e.target.value, }); @@ -247,7 +247,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { + private async restoreWithCachedKey(backupInfo?: IKeyBackupInfo): Promise { if (!backupInfo) return false; try { const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithCache( @@ -480,7 +480,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent

    {_t( - "Warning: You should only set up key backup " + "from a trusted computer.", + "Warning: you should only set up key backup " + "from a trusted computer.", {}, { b: (sub) => {sub} }, )} diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx index bf255695682..0102935f28f 100644 --- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx +++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx @@ -77,7 +77,7 @@ import BaseAvatar from "../../avatars/BaseAvatar"; import DecoratedRoomAvatar from "../../avatars/DecoratedRoomAvatar"; import { SearchResultAvatar } from "../../avatars/SearchResultAvatar"; import { NetworkDropdown } from "../../directory/NetworkDropdown"; -import AccessibleButton from "../../elements/AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "../../elements/AccessibleButton"; import LabelledCheckbox from "../../elements/LabelledCheckbox"; import Spinner from "../../elements/Spinner"; import NotificationBadge from "../../rooms/NotificationBadge"; @@ -625,7 +625,7 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n const showViewButton = clientRoom?.getMyMembership() === "join" || result.publicRoom.world_readable || cli.isGuest(); - const listener = (ev): void => { + const listener = (ev: ButtonEvent): void => { const { publicRoom } = result; viewRoom( { diff --git a/src/components/views/elements/AppTile.tsx b/src/components/views/elements/AppTile.tsx index b75de2ee449..de0b1a17e14 100644 --- a/src/components/views/elements/AppTile.tsx +++ b/src/components/views/elements/AppTile.tsx @@ -18,7 +18,7 @@ limitations under the License. */ import url from "url"; -import React, { ContextType, createRef, MutableRefObject, ReactNode } from "react"; +import React, { ContextType, createRef, CSSProperties, MutableRefObject, ReactNode } from "react"; import classNames from "classnames"; import { MatrixCapabilities } from "matrix-widget-api"; import { Room, RoomEvent } from "matrix-js-sdk/src/models/room"; @@ -81,7 +81,7 @@ interface IProps { // Is this an instance of a user widget userWidget: boolean; // sets the pointer-events property on the iframe - pointerEvents?: string; + pointerEvents?: CSSProperties["pointerEvents"]; widgetPageTitle?: string; showLayoutButtons?: boolean; // Handle to manually notify the PersistedElement that it needs to move @@ -562,9 +562,9 @@ export default class AppTile extends React.Component { "microphone; camera; encrypted-media; autoplay; display-capture; clipboard-write; " + "clipboard-read;"; const appTileBodyClass = "mx_AppTileBody" + (this.props.miniMode ? "_mini " : " "); - const appTileBodyStyles = {}; + const appTileBodyStyles: CSSProperties = {}; if (this.props.pointerEvents) { - appTileBodyStyles["pointerEvents"] = this.props.pointerEvents; + appTileBodyStyles.pointerEvents = this.props.pointerEvents; } const loadingElement = ( diff --git a/src/components/views/elements/Dropdown.tsx b/src/components/views/elements/Dropdown.tsx index c7abf5246c7..a41aa5321da 100644 --- a/src/components/views/elements/Dropdown.tsx +++ b/src/components/views/elements/Dropdown.tsx @@ -373,7 +373,7 @@ export default class Dropdown extends React.Component { ); } - const dropdownClasses = { + const dropdownClasses: Record = { mx_Dropdown: true, mx_Dropdown_disabled: this.props.disabled, }; diff --git a/src/components/views/elements/EditableItemList.tsx b/src/components/views/elements/EditableItemList.tsx index 9cc5371ff56..2d299766046 100644 --- a/src/components/views/elements/EditableItemList.tsx +++ b/src/components/views/elements/EditableItemList.tsx @@ -14,11 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ChangeEvent } from "react"; import { _t } from "../../../languageHandler"; import Field from "./Field"; -import AccessibleButton from "./AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "./AccessibleButton"; interface IItemProps { index?: number; @@ -35,21 +35,21 @@ export class EditableItem extends React.Component { verifyRemove: false, }; - private onRemove = (e): void => { + private onRemove = (e: ButtonEvent): void => { e.stopPropagation(); e.preventDefault(); this.setState({ verifyRemove: true }); }; - private onDontRemove = (e): void => { + private onDontRemove = (e: ButtonEvent): void => { e.stopPropagation(); e.preventDefault(); this.setState({ verifyRemove: false }); }; - private onActuallyRemove = (e): void => { + private onActuallyRemove = (e: ButtonEvent): void => { e.stopPropagation(); e.preventDefault(); @@ -105,19 +105,19 @@ interface IProps { } export default class EditableItemList

    extends React.PureComponent { - protected onItemAdded = (e): void => { + protected onItemAdded = (e: ButtonEvent): void => { e.stopPropagation(); e.preventDefault(); - if (this.props.onItemAdded) this.props.onItemAdded(this.props.newItem); + this.props.onItemAdded?.(this.props.newItem); }; protected onItemRemoved = (index: number): void => { - if (this.props.onItemRemoved) this.props.onItemRemoved(index); + this.props.onItemRemoved?.(index); }; - protected onNewItemChanged = (e): void => { - if (this.props.onNewItemChanged) this.props.onNewItemChanged(e.target.value); + protected onNewItemChanged = (e: ChangeEvent): void => { + this.props.onNewItemChanged?.(e.target.value); }; protected renderNewItemField(): JSX.Element { diff --git a/src/components/views/elements/EffectsOverlay.tsx b/src/components/views/elements/EffectsOverlay.tsx index 423ae62d19a..cf11350c2b9 100644 --- a/src/components/views/elements/EffectsOverlay.tsx +++ b/src/components/views/elements/EffectsOverlay.tsx @@ -32,13 +32,13 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) => { const lazyLoadEffectModule = async (name: string): Promise => { if (!name) return null; - let effect: ICanvasEffect | null = effectsRef.current[name] || null; + let effect: ICanvasEffect | null = effectsRef.current.get(name) || null; if (effect === null) { const options = CHAT_EFFECTS.find((e) => e.command === name)?.options; try { const { default: Effect } = await import(`../../../effects/${name}`); effect = new Effect(options); - effectsRef.current[name] = effect; + effectsRef.current.set(name, effect); } catch (err) { logger.warn(`Unable to load effect module at '../../../effects/${name}.`, err); } @@ -70,7 +70,7 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) => { // eslint-disable-next-line react-hooks/exhaustive-deps const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored for (const effect in currentEffects) { - const effectModule: ICanvasEffect = currentEffects[effect]; + const effectModule: ICanvasEffect = currentEffects.get(effect); if (effectModule && effectModule.isRunning) { effectModule.stop(); } diff --git a/src/components/views/elements/ErrorBoundary.tsx b/src/components/views/elements/ErrorBoundary.tsx index 87b81f3280c..76030414785 100644 --- a/src/components/views/elements/ErrorBoundary.tsx +++ b/src/components/views/elements/ErrorBoundary.tsx @@ -34,7 +34,7 @@ interface IState { * catch exceptions during rendering in the component tree below them. */ export default class ErrorBoundary extends React.PureComponent<{}, IState> { - public constructor(props) { + public constructor(props: {}) { super(props); this.state = { diff --git a/src/components/views/elements/EventListSummary.tsx b/src/components/views/elements/EventListSummary.tsx index 60288fb2f56..2a55eec8e1c 100644 --- a/src/components/views/elements/EventListSummary.tsx +++ b/src/components/views/elements/EventListSummary.tsx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ComponentProps } from "react"; +import React, { ComponentProps, ReactNode } from "react"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { EventType } from "matrix-js-sdk/src/@types/event"; @@ -161,7 +161,15 @@ export default class EventListSummary extends React.Component { * @returns {string[]} an array of transitions. */ private static getCanonicalTransitions(transitions: TransitionType[]): TransitionType[] { - const modMap = { + const modMap: Partial< + Record< + TransitionType, + { + after: TransitionType; + newTransition: TransitionType; + } + > + > = { [TransitionType.Joined]: { after: TransitionType.Left, newTransition: TransitionType.JoinedAndLeft, @@ -170,10 +178,6 @@ export default class EventListSummary extends React.Component { after: TransitionType.Joined, newTransition: TransitionType.LeftAndJoined, }, - // $currentTransition : { - // 'after' : $nextTransition, - // 'newTransition' : 'new_transition_type', - // }, }; const res: TransitionType[] = []; @@ -237,15 +241,11 @@ export default class EventListSummary extends React.Component { * @param {number} repeats the number of times the transition was repeated in a row. * @returns {string} the written Human Readable equivalent of the transition. */ - private static getDescriptionForTransition( - t: TransitionType, - userCount: number, - count: number, - ): string | JSX.Element { + private static getDescriptionForTransition(t: TransitionType, userCount: number, count: number): ReactNode | null { // The empty interpolations 'severalUsers' and 'oneUser' // are there only to show translators to non-English languages // that the verb is conjugated to plural or singular Subject. - let res = null; + let res: ReactNode | undefined; switch (t) { case TransitionType.Joined: res = @@ -377,7 +377,7 @@ export default class EventListSummary extends React.Component { break; } - return res; + return res ?? null; } private static getTransitionSequence(events: IUserEvents[]): TransitionType[] { diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx index b22c1d2b27f..bdb54251951 100644 --- a/src/components/views/elements/Field.tsx +++ b/src/components/views/elements/Field.tsx @@ -145,7 +145,7 @@ export default class Field extends React.PureComponent { }); }, VALIDATION_THROTTLE_MS); - public constructor(props) { + public constructor(props: PropShapes) { super(props); this.state = { valid: undefined, @@ -165,7 +165,7 @@ export default class Field extends React.PureComponent { }); } - private onFocus = (ev): void => { + private onFocus = (ev: React.FocusEvent): void => { this.setState({ focused: true, }); @@ -175,22 +175,18 @@ export default class Field extends React.PureComponent { }); } // Parent component may have supplied its own `onFocus` as well - if (this.props.onFocus) { - this.props.onFocus(ev); - } + this.props.onFocus?.(ev); }; - private onChange = (ev): void => { + private onChange = (ev: React.ChangeEvent): void => { if (this.props.validateOnChange) { this.validateOnChange(); } // Parent component may have supplied its own `onChange` as well - if (this.props.onChange) { - this.props.onChange(ev); - } + this.props.onChange?.(ev); }; - private onBlur = (ev): void => { + private onBlur = (ev: React.FocusEvent): void => { this.setState({ focused: false, }); @@ -200,9 +196,7 @@ export default class Field extends React.PureComponent { }); } // Parent component may have supplied its own `onBlur` as well - if (this.props.onBlur) { - this.props.onBlur(ev); - } + this.props.onBlur?.(ev); }; public async validate({ focused, allowEmpty = true }: IValidateOpts): Promise { diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index b6dff90b9c1..ac5a78566e6 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -91,7 +91,7 @@ interface IState { } export default class ImageView extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); const { thumbnailInfo } = this.props; diff --git a/src/components/views/elements/InteractiveTooltip.tsx b/src/components/views/elements/InteractiveTooltip.tsx index 33819f8ed6a..1986de02433 100644 --- a/src/components/views/elements/InteractiveTooltip.tsx +++ b/src/components/views/elements/InteractiveTooltip.tsx @@ -308,8 +308,8 @@ export default class InteractiveTooltip extends React.Component side: Direction.Top, }; - public constructor(props, context) { - super(props, context); + public constructor(props: IProps) { + super(props); this.state = { contentRect: null, diff --git a/src/components/views/elements/InviteReason.tsx b/src/components/views/elements/InviteReason.tsx index 08e0ceca318..2bdbb98b214 100644 --- a/src/components/views/elements/InviteReason.tsx +++ b/src/components/views/elements/InviteReason.tsx @@ -30,7 +30,7 @@ interface IState { } export default class InviteReason extends React.PureComponent { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { // We hide the reason for invitation by default, since it can be a diff --git a/src/components/views/elements/LabelledToggleSwitch.tsx b/src/components/views/elements/LabelledToggleSwitch.tsx index ce4db91117f..83a1e66f8a0 100644 --- a/src/components/views/elements/LabelledToggleSwitch.tsx +++ b/src/components/views/elements/LabelledToggleSwitch.tsx @@ -22,22 +22,24 @@ import { Caption } from "../typography/Caption"; interface IProps { // The value for the toggle switch - value: boolean; + "value": boolean; // The translated label for the switch - label: string; + "label": string; // The translated caption for the switch - caption?: string; + "caption"?: string; // Tooltip to display - tooltip?: string; + "tooltip"?: string; // Whether or not to disable the toggle switch - disabled?: boolean; + "disabled"?: boolean; // True to put the toggle in front of the label // Default false. - toggleInFront?: boolean; + "toggleInFront"?: boolean; // Additional class names to append to the switch. Optional. - className?: string; + "className"?: string; // The function to call when the value changes onChange(checked: boolean): void; + + "data-testid"?: string; } export default class LabelledToggleSwitch extends React.PureComponent { diff --git a/src/components/views/elements/Measured.tsx b/src/components/views/elements/Measured.tsx index 9445973b1d4..2f7862c9220 100644 --- a/src/components/views/elements/Measured.tsx +++ b/src/components/views/elements/Measured.tsx @@ -32,7 +32,7 @@ export default class Measured extends React.PureComponent { breakpoint: 500, }; - public constructor(props) { + public constructor(props: IProps) { super(props); this.instanceId = Measured.instanceCount++; diff --git a/src/components/views/elements/PersistentApp.tsx b/src/components/views/elements/PersistentApp.tsx index f692f74aa88..67ad09018d5 100644 --- a/src/components/views/elements/PersistentApp.tsx +++ b/src/components/views/elements/PersistentApp.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ContextType, MutableRefObject } from "react"; +import React, { ContextType, CSSProperties, MutableRefObject } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import WidgetUtils from "../../../utils/WidgetUtils"; @@ -26,7 +26,7 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; interface IProps { persistentWidgetId: string; persistentRoomId: string; - pointerEvents?: string; + pointerEvents?: CSSProperties["pointerEvents"]; movePersistedElement: MutableRefObject<(() => void) | undefined>; } diff --git a/src/components/views/elements/Pill.tsx b/src/components/views/elements/Pill.tsx index 8f905f5b591..e5df681cc71 100644 --- a/src/components/views/elements/Pill.tsx +++ b/src/components/views/elements/Pill.tsx @@ -20,6 +20,7 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { logger } from "matrix-js-sdk/src/logger"; import { MatrixClient } from "matrix-js-sdk/src/client"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import dis from "../../../dispatcher/dispatcher"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; @@ -30,6 +31,7 @@ import Tooltip, { Alignment } from "./Tooltip"; import RoomAvatar from "../avatars/RoomAvatar"; import MemberAvatar from "../avatars/MemberAvatar"; import { objectHasDiff } from "../../../utils/objects"; +import { ButtonEvent } from "./AccessibleButton"; export enum PillType { UserMention = "TYPE_USER_MENTION", @@ -180,7 +182,7 @@ export default class Pill extends React.Component { }); }; - private doProfileLookup(userId: string, member): void { + private doProfileLookup(userId: string, member: RoomMember): void { MatrixClientPeg.get() .getProfileInfo(userId) .then((resp) => { @@ -196,7 +198,7 @@ export default class Pill extends React.Component { getDirectionalContent: function () { return this.getContent(); }, - }; + } as MatrixEvent; this.setState({ member }); }) .catch((err) => { @@ -204,7 +206,7 @@ export default class Pill extends React.Component { }); } - private onUserPillClicked = (e): void => { + private onUserPillClicked = (e: ButtonEvent): void => { e.preventDefault(); dis.dispatch({ action: Action.ViewUser, diff --git a/src/components/views/elements/RoomAliasField.tsx b/src/components/views/elements/RoomAliasField.tsx index 7ba6be8588b..f03b40173a7 100644 --- a/src/components/views/elements/RoomAliasField.tsx +++ b/src/components/views/elements/RoomAliasField.tsx @@ -44,7 +44,7 @@ export default class RoomAliasField extends React.PureComponent private fieldRef = createRef(); - public constructor(props, context) { + public constructor(props: IProps, context: React.ContextType) { super(props, context); this.state = { diff --git a/src/components/views/elements/RoomFacePile.tsx b/src/components/views/elements/RoomFacePile.tsx index 7149c7e0ce5..fc4792c2e0d 100644 --- a/src/components/views/elements/RoomFacePile.tsx +++ b/src/components/views/elements/RoomFacePile.tsx @@ -42,7 +42,7 @@ const RoomFacePile: FC = ({ room, onlyKnownUsers = true, numShown = DEFA const count = members.length; // sort users with an explicit avatar first - const iteratees = [(member) => (member.getMxcAvatarUrl() ? 0 : 1)]; + const iteratees = [(member: RoomMember) => (member.getMxcAvatarUrl() ? 0 : 1)]; if (onlyKnownUsers) { members = members.filter(isKnownMember); } else { diff --git a/src/components/views/elements/SearchWarning.tsx b/src/components/views/elements/SearchWarning.tsx index 0737ff3c1b6..fec5eee37f1 100644 --- a/src/components/views/elements/SearchWarning.tsx +++ b/src/components/views/elements/SearchWarning.tsx @@ -23,7 +23,7 @@ import SdkConfig from "../../../SdkConfig"; import dis from "../../../dispatcher/dispatcher"; import { Action } from "../../../dispatcher/actions"; import { UserTab } from "../dialogs/UserTab"; -import AccessibleButton from "./AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "./AccessibleButton"; export enum WarningKind { Files, @@ -49,7 +49,7 @@ export default function SearchWarning({ isRoomEncrypted, kind }: IProps): JSX.El a: (sub) => ( { + onClick={(evt: ButtonEvent) => { evt.preventDefault(); dis.dispatch({ action: Action.ViewUserSettings, diff --git a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx index 78644dc962b..a930ec409bc 100644 --- a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx +++ b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx @@ -33,7 +33,7 @@ function languageMatchesSearchQuery(query: string, language: Languages[0]): bool interface SpellCheckLanguagesDropdownIProps { className: string; value: string; - onOptionChange(language: string); + onOptionChange(language: string): void; } interface SpellCheckLanguagesDropdownIState { @@ -45,7 +45,7 @@ export default class SpellCheckLanguagesDropdown extends React.Component< SpellCheckLanguagesDropdownIProps, SpellCheckLanguagesDropdownIState > { - public constructor(props) { + public constructor(props: SpellCheckLanguagesDropdownIProps) { super(props); this.onSearchChange = this.onSearchChange.bind(this); diff --git a/src/components/views/elements/StyledRadioGroup.tsx b/src/components/views/elements/StyledRadioGroup.tsx index 8ad41c47053..3a09b3feff1 100644 --- a/src/components/views/elements/StyledRadioGroup.tsx +++ b/src/components/views/elements/StyledRadioGroup.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactNode } from "react"; +import React, { ChangeEvent, ReactNode } from "react"; import classNames from "classnames"; import StyledRadioButton from "./StyledRadioButton"; @@ -47,8 +47,8 @@ function StyledRadioGroup({ disabled, onChange, }: IProps): JSX.Element { - const _onChange = (e): void => { - onChange(e.target.value); + const _onChange = (e: ChangeEvent): void => { + onChange(e.target.value as T); }; return ( diff --git a/src/components/views/elements/Tooltip.tsx b/src/components/views/elements/Tooltip.tsx index 9b927d81891..5e65824454a 100644 --- a/src/components/views/elements/Tooltip.tsx +++ b/src/components/views/elements/Tooltip.tsx @@ -68,7 +68,7 @@ export default class Tooltip extends React.PureComponent { alignment: Alignment.Natural, }; - public constructor(props) { + public constructor(props: ITooltipProps) { super(props); this.state = {}; @@ -92,7 +92,7 @@ export default class Tooltip extends React.PureComponent { this.updatePosition(); } - public componentDidUpdate(prevProps): void { + public componentDidUpdate(prevProps: ITooltipProps): void { if (objectHasDiff(prevProps, this.props)) { this.updatePosition(); } diff --git a/src/components/views/elements/TooltipButton.tsx b/src/components/views/elements/TooltipButton.tsx index 415e25cf9e0..ceb547a9ce3 100644 --- a/src/components/views/elements/TooltipButton.tsx +++ b/src/components/views/elements/TooltipButton.tsx @@ -24,7 +24,7 @@ interface IProps { } export default class TooltipButton extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); } diff --git a/src/components/views/elements/TruncatedList.tsx b/src/components/views/elements/TruncatedList.tsx index 45a621eaf35..4f5284d54ff 100644 --- a/src/components/views/elements/TruncatedList.tsx +++ b/src/components/views/elements/TruncatedList.tsx @@ -40,7 +40,7 @@ interface IProps { export default class TruncatedList extends React.Component { public static defaultProps = { truncateAt: 2, - createOverflowElement(overflowCount, totalCount) { + createOverflowElement(overflowCount: number, totalCount: number) { return

    {_t("And %(count)s more...", { count: overflowCount })}
    ; }, }; diff --git a/src/components/views/emojipicker/QuickReactions.tsx b/src/components/views/emojipicker/QuickReactions.tsx index be6d6a56968..bb0a9a12d0d 100644 --- a/src/components/views/emojipicker/QuickReactions.tsx +++ b/src/components/views/emojipicker/QuickReactions.tsx @@ -40,7 +40,7 @@ interface IState { } class QuickReactions extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { hover: null, diff --git a/src/components/views/emojipicker/ReactionPicker.tsx b/src/components/views/emojipicker/ReactionPicker.tsx index 023328cedb5..e113ab89503 100644 --- a/src/components/views/emojipicker/ReactionPicker.tsx +++ b/src/components/views/emojipicker/ReactionPicker.tsx @@ -50,7 +50,7 @@ class ReactionPicker extends React.Component { this.addListeners(); } - public componentDidUpdate(prevProps): void { + public componentDidUpdate(prevProps: IProps): void { if (prevProps.reactions !== this.props.reactions) { this.addListeners(); this.onReactionsChange(); @@ -78,7 +78,7 @@ class ReactionPicker extends React.Component { return {}; } const userId = MatrixClientPeg.get().getUserId(); - const myAnnotations = this.props.reactions.getAnnotationsBySender()[userId] || []; + const myAnnotations = this.props.reactions.getAnnotationsBySender()[userId] || new Set(); return Object.fromEntries( [...myAnnotations] .filter((event) => !event.isRedacted()) diff --git a/src/components/views/location/LocationShareMenu.tsx b/src/components/views/location/LocationShareMenu.tsx index 31664a872fa..5968d50c472 100644 --- a/src/components/views/location/LocationShareMenu.tsx +++ b/src/components/views/location/LocationShareMenu.tsx @@ -38,7 +38,7 @@ type Props = Omit & { relation?: IEventRelation; }; -const getEnabledShareTypes = (relation): LocationShareType[] => { +const getEnabledShareTypes = (relation?: IEventRelation): LocationShareType[] => { const enabledShareTypes = [LocationShareType.Own]; // live locations cannot have a relation diff --git a/src/components/views/messages/DateSeparator.tsx b/src/components/views/messages/DateSeparator.tsx index 183bfffd15e..1f8f166bf5d 100644 --- a/src/components/views/messages/DateSeparator.tsx +++ b/src/components/views/messages/DateSeparator.tsx @@ -53,10 +53,10 @@ interface IState { } export default class DateSeparator extends React.Component { - private settingWatcherRef = null; + private settingWatcherRef?: string; - public constructor(props, context) { - super(props, context); + public constructor(props: IProps) { + super(props); this.state = { jumpToDateEnabled: SettingsStore.getValue("feature_jump_to_date"), }; @@ -116,7 +116,7 @@ export default class DateSeparator extends React.Component { } } - private pickDate = async (inputTimestamp): Promise => { + private pickDate = async (inputTimestamp: number | string | Date): Promise => { const unixTimestamp = new Date(inputTimestamp).getTime(); const cli = MatrixClientPeg.get(); @@ -175,7 +175,7 @@ export default class DateSeparator extends React.Component { this.closeMenu(); }; - private onDatePicked = (dateString): void => { + private onDatePicked = (dateString: string): void => { this.pickDate(dateString); this.closeMenu(); }; diff --git a/src/components/views/messages/EventTileBubble.tsx b/src/components/views/messages/EventTileBubble.tsx index 90b1811e199..db60d706f24 100644 --- a/src/components/views/messages/EventTileBubble.tsx +++ b/src/components/views/messages/EventTileBubble.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { forwardRef, ReactNode, ReactChildren } from "react"; +import React, { forwardRef, ReactNode, ReactChild } from "react"; import classNames from "classnames"; interface IProps { @@ -22,7 +22,7 @@ interface IProps { title: string; timestamp?: JSX.Element; subtitle?: ReactNode; - children?: ReactChildren; + children?: ReactChild; } const EventTileBubble = forwardRef( diff --git a/src/components/views/messages/MAudioBody.tsx b/src/components/views/messages/MAudioBody.tsx index 2a78e79e7fb..188f4526a9c 100644 --- a/src/components/views/messages/MAudioBody.tsx +++ b/src/components/views/messages/MAudioBody.tsx @@ -16,6 +16,7 @@ limitations under the License. import React from "react"; import { logger } from "matrix-js-sdk/src/logger"; +import { IContent } from "matrix-js-sdk/src/matrix"; import { Playback } from "../../../audio/Playback"; import InlineSpinner from "../elements/InlineSpinner"; @@ -66,8 +67,8 @@ export default class MAudioBody extends React.PureComponent // We should have a buffer to work with now: let's set it up // Note: we don't actually need a waveform to render an audio event, but voice messages do. - const content = this.props.mxEvent.getContent(); - const waveform = content?.["org.matrix.msc1767.audio"]?.waveform?.map((p) => p / 1024); + const content = this.props.mxEvent.getContent(); + const waveform = content?.["org.matrix.msc1767.audio"]?.waveform?.map((p: number) => p / 1024); // We should have a buffer to work with now: let's set it up const playback = PlaybackManager.instance.createPlaybackInstance(buffer, waveform); diff --git a/src/components/views/messages/MFileBody.tsx b/src/components/views/messages/MFileBody.tsx index e68fe24341a..f0a80a345b4 100644 --- a/src/components/views/messages/MFileBody.tsx +++ b/src/components/views/messages/MFileBody.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef } from "react"; +import React, { AllHTMLAttributes, createRef } from "react"; import { filesize } from "filesize"; import { logger } from "matrix-js-sdk/src/logger"; @@ -30,7 +30,7 @@ import { FileDownloader } from "../../../utils/FileDownloader"; import TextWithTooltip from "../elements/TextWithTooltip"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; -export let DOWNLOAD_ICON_URL; // cached copy of the download.svg asset for the sandboxed iframe later on +export let DOWNLOAD_ICON_URL: string; // cached copy of the download.svg asset for the sandboxed iframe later on async function cacheDownloadIcon(): Promise { if (DOWNLOAD_ICON_URL) return; // cached already @@ -155,7 +155,7 @@ export default class MFileBody extends React.Component { }); } - public componentDidUpdate(prevProps, prevState): void { + public componentDidUpdate(prevProps: IProps, prevState: IState): void { if (this.props.onHeightChanged && !prevState.decryptedBlob && this.state.decryptedBlob) { this.props.onHeightChanged(); } @@ -295,7 +295,7 @@ export default class MFileBody extends React.Component { ); } else if (contentUrl) { - const downloadProps = { + const downloadProps: AllHTMLAttributes = { target: "_blank", rel: "noreferrer noopener", diff --git a/src/components/views/messages/MJitsiWidgetEvent.tsx b/src/components/views/messages/MJitsiWidgetEvent.tsx index 0796b6830e7..bb3a21c4b4d 100644 --- a/src/components/views/messages/MJitsiWidgetEvent.tsx +++ b/src/components/views/messages/MJitsiWidgetEvent.tsx @@ -29,7 +29,7 @@ interface IProps { } export default class MJitsiWidgetEvent extends React.PureComponent { - public constructor(props) { + public constructor(props: IProps) { super(props); } diff --git a/src/components/views/messages/MKeyVerificationRequest.tsx b/src/components/views/messages/MKeyVerificationRequest.tsx index 2a115572144..d12f252750b 100644 --- a/src/components/views/messages/MKeyVerificationRequest.tsx +++ b/src/components/views/messages/MKeyVerificationRequest.tsx @@ -122,9 +122,9 @@ export default class MKeyVerificationRequest extends React.Component { return null; } - let title; - let subtitle; - let stateNode; + let title: string; + let subtitle: string; + let stateNode: JSX.Element; if (!request.canAccept) { let stateLabel; diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index e94705f97bb..abad590cbf7 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -47,7 +47,7 @@ export default class MVideoBody extends React.PureComponent private videoRef = React.createRef(); private sizeWatcher: string; - public constructor(props) { + public constructor(props: IBodyProps) { super(props); this.state = { diff --git a/src/components/views/messages/ReactionsRow.tsx b/src/components/views/messages/ReactionsRow.tsx index 92612383fc9..3bb6260d701 100644 --- a/src/components/views/messages/ReactionsRow.tsx +++ b/src/components/views/messages/ReactionsRow.tsx @@ -14,7 +14,7 @@ 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 { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event"; import { Relations, RelationsEvent } from "matrix-js-sdk/src/models/relations"; @@ -52,7 +52,7 @@ const ReactButton: React.FC = ({ mxEvent, reactions }) => { })} title={_t("Add reaction")} onClick={openMenu} - onContextMenu={(e) => { + onContextMenu={(e: SyntheticEvent): void => { e.preventDefault(); openMenu(); }} diff --git a/src/components/views/messages/ReactionsRowButtonTooltip.tsx b/src/components/views/messages/ReactionsRowButtonTooltip.tsx index b875b7e27a1..600502e7ddc 100644 --- a/src/components/views/messages/ReactionsRowButtonTooltip.tsx +++ b/src/components/views/messages/ReactionsRowButtonTooltip.tsx @@ -35,6 +35,7 @@ interface IProps { export default class ReactionsRowButtonTooltip extends React.PureComponent { public static contextType = MatrixClientContext; + public context!: React.ContextType; public render(): JSX.Element { const { content, reactionEvents, mxEvent, visible } = this.props; @@ -42,7 +43,7 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent { public static contextType = RoomContext; public context!: React.ContextType; - public constructor(props) { + public constructor(props: IBodyProps) { super(props); this.state = { diff --git a/src/components/views/messages/TileErrorBoundary.tsx b/src/components/views/messages/TileErrorBoundary.tsx index 12cda9f9482..0c221a79335 100644 --- a/src/components/views/messages/TileErrorBoundary.tsx +++ b/src/components/views/messages/TileErrorBoundary.tsx @@ -37,7 +37,7 @@ interface IState { } export default class TileErrorBoundary extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/messages/ViewSourceEvent.tsx b/src/components/views/messages/ViewSourceEvent.tsx index 5f265a2c0d1..344ddd344ee 100644 --- a/src/components/views/messages/ViewSourceEvent.tsx +++ b/src/components/views/messages/ViewSourceEvent.tsx @@ -31,7 +31,7 @@ interface IState { } export default class ViewSourceEvent extends React.PureComponent { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/right_panel/HeaderButtons.tsx b/src/components/views/right_panel/HeaderButtons.tsx index 7a6eea6f23c..75542e15337 100644 --- a/src/components/views/right_panel/HeaderButtons.tsx +++ b/src/components/views/right_panel/HeaderButtons.tsx @@ -26,6 +26,7 @@ import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePha import { IRightPanelCardState } from "../../../stores/right-panel/RightPanelStoreIPanelState"; import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { NotificationColor } from "../../../stores/notifications/NotificationColor"; +import { ActionPayload } from "../../../dispatcher/payloads"; export enum HeaderKind { Room = "room", @@ -67,7 +68,7 @@ export default abstract class HeaderButtons

    extends React.Component): void { const rps = RightPanelStore.instance; diff --git a/src/components/views/room_settings/AliasSettings.tsx b/src/components/views/room_settings/AliasSettings.tsx index 9c6b1cf2dc1..d540ac61ad4 100644 --- a/src/components/views/room_settings/AliasSettings.tsx +++ b/src/components/views/room_settings/AliasSettings.tsx @@ -15,8 +15,9 @@ limitations under the License. */ import React, { ChangeEvent, ContextType, createRef, SyntheticEvent } from "react"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { IContent, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { logger } from "matrix-js-sdk/src/logger"; +import { EventType } from "matrix-js-sdk/src/@types/event"; import EditableItemList from "../elements/EditableItemList"; import { _t } from "../../../languageHandler"; @@ -52,7 +53,7 @@ class EditableAliasesList extends EditableItemList { }; protected renderNewItemField(): JSX.Element { - const onChange = (alias: string): void => this.onNewItemChanged({ target: { value: alias } }); + const onChange = (alias: string): void => this.props.onNewItemChanged?.(alias); return (
    { canSetCanonicalAlias: false, }; - public constructor(props, context: ContextType) { + public constructor(props: IProps, context: ContextType) { super(props, context); - const state = { + const state: IState = { altAliases: [], // [ #alias:domain.tld, ... ] localAliases: [], // [ #alias:my-hs.tld, ... ] canonicalAlias: null, // #canonical:domain.tld @@ -140,7 +141,7 @@ export default class AliasSettings extends React.Component { try { const mxClient = this.context; - let localAliases = []; + let localAliases: string[] = []; const response = await mxClient.getLocalAliases(this.props.roomId); if (Array.isArray(response?.aliases)) { localAliases = response.aliases; @@ -164,14 +165,14 @@ export default class AliasSettings extends React.Component { updatingCanonicalAlias: true, }); - const eventContent = { + const eventContent: IContent = { alt_aliases: this.state.altAliases, }; if (alias) eventContent["alias"] = alias; this.context - .sendStateEvent(this.props.roomId, "m.room.canonical_alias", eventContent, "") + .sendStateEvent(this.props.roomId, EventType.RoomCanonicalAlias, eventContent, "") .catch((err) => { logger.error(err); Modal.createDialog(ErrorDialog, { @@ -195,7 +196,7 @@ export default class AliasSettings extends React.Component { updatingCanonicalAlias: true, }); - const eventContent = {}; + const eventContent: IContent = {}; if (this.state.canonicalAlias) { eventContent["alias"] = this.state.canonicalAlias; @@ -205,7 +206,7 @@ export default class AliasSettings extends React.Component { } this.context - .sendStateEvent(this.props.roomId, "m.room.canonical_alias", eventContent, "") + .sendStateEvent(this.props.roomId, EventType.RoomCanonicalAlias, eventContent, "") .then(() => { this.setState({ altAliases, diff --git a/src/components/views/room_settings/RoomPublishSetting.tsx b/src/components/views/room_settings/RoomPublishSetting.tsx index 673afccb77c..d20ea9f92ea 100644 --- a/src/components/views/room_settings/RoomPublishSetting.tsx +++ b/src/components/views/room_settings/RoomPublishSetting.tsx @@ -33,15 +33,15 @@ interface IState { } export default class RoomPublishSetting extends React.PureComponent { - public constructor(props, context) { - super(props, context); + public constructor(props: IProps) { + super(props); this.state = { isRoomPublished: false, }; } - private onRoomPublishChange = (e): void => { + private onRoomPublishChange = (): void => { const valueBefore = this.state.isRoomPublished; const newValue = !valueBefore; this.setState({ isRoomPublished: newValue }); diff --git a/src/components/views/rooms/AppsDrawer.tsx b/src/components/views/rooms/AppsDrawer.tsx index 25c8684c9c8..38f119718e0 100644 --- a/src/components/views/rooms/AppsDrawer.tsx +++ b/src/components/views/rooms/AppsDrawer.tsx @@ -45,8 +45,7 @@ interface IProps { } interface IState { - // @ts-ignore - TS wants a string key, but we know better - apps: { [id: Container]: IApp[] }; + apps: Partial<{ [id in Container]: IApp[] }>; resizingVertical: boolean; // true when changing the height of the apps drawer resizingHorizontal: boolean; // true when changing the distribution of the width between widgets resizing: boolean; @@ -203,10 +202,9 @@ export default class AppsDrawer extends React.Component { break; } }; - // @ts-ignore - TS wants a string key, but we know better - private getApps = (): { [id: Container]: IApp[] } => { - // @ts-ignore - const appsDict: { [id: Container]: IApp[] } = {}; + + private getApps = (): Partial<{ [id in Container]: IApp[] }> => { + const appsDict: Partial<{ [id in Container]: IApp[] }> = {}; appsDict[Container.Top] = WidgetLayoutStore.instance.getContainerWidgets(this.props.room, Container.Top); appsDict[Container.Center] = WidgetLayoutStore.instance.getContainerWidgets(this.props.room, Container.Center); return appsDict; diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index b88f6dbdb79..5f7f88d01c8 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -57,7 +57,7 @@ export default class Autocomplete extends React.PureComponent { public static contextType = RoomContext; - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 8492f575199..ccb36091af1 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -55,7 +55,7 @@ export default class AuxPanel extends React.Component { showApps: true, }; - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { @@ -76,7 +76,7 @@ export default class AuxPanel extends React.Component { } } - public shouldComponentUpdate(nextProps, nextState): boolean { + public shouldComponentUpdate(nextProps: IProps, nextState: IState): boolean { return objectHasDiff(this.props, nextProps) || objectHasDiff(this.state, nextState); } @@ -146,9 +146,9 @@ export default class AuxPanel extends React.Component { ); } - let stateViews = null; + let stateViews: JSX.Element | null = null; if (this.state.counters && SettingsStore.getValue("feature_state_counters")) { - const counters = []; + const counters: JSX.Element[] = []; this.state.counters.forEach((counter, idx) => { const title = counter.title; diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index eaf97a39f40..00daed475cb 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -32,7 +32,7 @@ import { } from "../../../editor/operations"; import { getCaretOffsetAndText, getRangeForSelection } from "../../../editor/dom"; import Autocomplete, { generateCompletionDomId } from "../rooms/Autocomplete"; -import { getAutoCompleteCreator, Part, Type } from "../../../editor/parts"; +import { getAutoCompleteCreator, Part, SerializedPart, Type } from "../../../editor/parts"; import { parseEvent, parsePlainTextMessage } from "../../../editor/deserialize"; import { renderModel } from "../../../editor/render"; import SettingsStore from "../../../settings/SettingsStore"; @@ -107,7 +107,7 @@ interface IProps { initialCaret?: DocumentOffset; disabled?: boolean; - onChange?(); + onChange?(): void; onPaste?(event: ClipboardEvent, model: EditorModel): boolean; } @@ -140,7 +140,7 @@ export default class BasicMessageEditor extends React.Component private readonly surroundWithHandle: string; private readonly historyManager = new HistoryManager(); - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { showPillAvatar: SettingsStore.getValue("Pill.shouldShowPillAvatar"), @@ -367,7 +367,7 @@ export default class BasicMessageEditor extends React.Component let parts: Part[]; if (partsText) { const serializedTextParts = JSON.parse(partsText); - parts = serializedTextParts.map((p) => partCreator.deserializePart(p)); + parts = serializedTextParts.map((p: SerializedPart) => partCreator.deserializePart(p)); } else { parts = parsePlainTextMessage(plainText, partCreator, { shouldEscape: false }); } diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index a5e11564372..af734d25a7c 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -29,7 +29,7 @@ import { getCaretOffsetAndText } from "../../../editor/dom"; import { htmlSerializeIfNeeded, textSerialize, containsEmote, stripEmoteCommand } from "../../../editor/serialize"; import { findEditableEvent } from "../../../utils/EventUtils"; import { parseEvent } from "../../../editor/deserialize"; -import { CommandPartCreator, Part, PartCreator } from "../../../editor/parts"; +import { CommandPartCreator, Part, PartCreator, SerializedPart } from "../../../editor/parts"; import EditorStateTransfer from "../../../utils/EditorStateTransfer"; import BasicMessageComposer, { REGEX_EMOTICON } from "./BasicMessageComposer"; import { CommandCategories } from "../../../SlashCommands"; @@ -59,7 +59,7 @@ function getHtmlReplyFallback(mxEvent: MatrixEvent): string { } function getTextReplyFallback(mxEvent: MatrixEvent): string { - const body = mxEvent.getContent().body; + const body: string = mxEvent.getContent().body; const lines = body.split("\n").map((l) => l.trim()); if (lines.length > 2 && lines[0].startsWith("> ") && lines[1].length === 0) { return `${lines[0]}\n\n`; @@ -253,7 +253,7 @@ class EditMessageComposer extends React.Component partCreator.deserializePart(p)); + const parts: Part[] = serializedParts.map((p: SerializedPart) => partCreator.deserializePart(p)); return parts; } catch (e) { logger.error("Error parsing editing state: ", e); diff --git a/src/components/views/rooms/EntityTile.tsx b/src/components/views/rooms/EntityTile.tsx index bfd5cece66d..7bcc60541d9 100644 --- a/src/components/views/rooms/EntityTile.tsx +++ b/src/components/views/rooms/EntityTile.tsx @@ -35,13 +35,15 @@ const PowerLabel: Record = { [PowerStatus.Moderator]: _td("Mod"), }; -const PRESENCE_CLASS = { +export type PresenceState = "offline" | "online" | "unavailable"; + +const PRESENCE_CLASS: Record = { offline: "mx_EntityTile_offline", online: "mx_EntityTile_online", unavailable: "mx_EntityTile_unavailable", }; -function presenceClassForMember(presenceState: string, lastActiveAgo: number, showPresence: boolean): string { +function presenceClassForMember(presenceState: PresenceState, lastActiveAgo: number, showPresence: boolean): string { if (showPresence === false) { return "mx_EntityTile_online_beenactive"; } @@ -67,7 +69,7 @@ interface IProps { title?: string; avatarJsx?: JSX.Element; // className?: string; - presenceState?: string; + presenceState?: PresenceState; presenceLastActiveAgo?: number; presenceLastTs?: number; presenceCurrentlyActive?: boolean; @@ -104,7 +106,7 @@ export default class EntityTile extends React.PureComponent { } public render(): JSX.Element { - const mainClassNames = { + const mainClassNames: Record = { mx_EntityTile: true, mx_EntityTile_noHover: this.props.suppressOnHover, }; diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index d6c2052d089..e0780251c56 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -617,8 +617,8 @@ export class UnwrappedEventTile extends React.Component } private propsEqual(objA: EventTileProps, objB: EventTileProps): boolean { - const keysA = Object.keys(objA); - const keysB = Object.keys(objB); + const keysA = Object.keys(objA) as Array; + const keysB = Object.keys(objB) as Array; if (keysA.length !== keysB.length) { return false; diff --git a/src/components/views/rooms/LinkPreviewWidget.tsx b/src/components/views/rooms/LinkPreviewWidget.tsx index 23bd19ff4dc..3de8d68a894 100644 --- a/src/components/views/rooms/LinkPreviewWidget.tsx +++ b/src/components/views/rooms/LinkPreviewWidget.tsx @@ -37,7 +37,7 @@ interface IProps { export default class LinkPreviewWidget extends React.Component { private image = createRef(); - private onImageClick = (ev): void => { + private onImageClick = (ev: React.MouseEvent): void => { const p = this.props.preview; if (ev.button != 0 || ev.metaKey) return; ev.preventDefault(); diff --git a/src/components/views/rooms/MemberList.tsx b/src/components/views/rooms/MemberList.tsx index 396e0939867..0d48d0b2675 100644 --- a/src/components/views/rooms/MemberList.tsx +++ b/src/components/views/rooms/MemberList.tsx @@ -68,7 +68,8 @@ interface IState { } export default class MemberList extends React.Component { - private showPresence = true; + // XXX: exported for tests + public showPresence = true; private mounted = false; public static contextType = SDKContext; @@ -195,7 +196,8 @@ export default class MemberList extends React.Component { { leading: true, trailing: true }, ); - private async updateListNow(showLoadingSpinner: boolean): Promise { + // XXX: exported for tests + public async updateListNow(showLoadingSpinner?: boolean): Promise { if (!this.mounted) { return; } diff --git a/src/components/views/rooms/MemberTile.tsx b/src/components/views/rooms/MemberTile.tsx index 94b4771007f..de544cd929d 100644 --- a/src/components/views/rooms/MemberTile.tsx +++ b/src/components/views/rooms/MemberTile.tsx @@ -28,10 +28,11 @@ import dis from "../../../dispatcher/dispatcher"; import { _t } from "../../../languageHandler"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { Action } from "../../../dispatcher/actions"; -import EntityTile, { PowerStatus } from "./EntityTile"; +import EntityTile, { PowerStatus, PresenceState } from "./EntityTile"; import MemberAvatar from "./../avatars/MemberAvatar"; import DisambiguatedProfile from "../messages/DisambiguatedProfile"; import UserIdentifierCustomisations from "../../../customisations/UserIdentifier"; +import { E2EState } from "./E2EIcon"; interface IProps { member: RoomMember; @@ -40,7 +41,7 @@ interface IProps { interface IState { isRoomEncrypted: boolean; - e2eStatus: string; + e2eStatus: E2EState; } export default class MemberTile extends React.Component { @@ -51,7 +52,7 @@ export default class MemberTile extends React.Component { showPresence: true, }; - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { @@ -121,7 +122,7 @@ export default class MemberTile extends React.Component { const userTrust = cli.checkUserTrust(userId); if (!userTrust.isCrossSigningVerified()) { this.setState({ - e2eStatus: userTrust.wasCrossSigningVerified() ? "warning" : "normal", + e2eStatus: userTrust.wasCrossSigningVerified() ? E2EState.Warning : E2EState.Normal, }); return; } @@ -138,7 +139,7 @@ export default class MemberTile extends React.Component { return isMe ? !deviceTrust.isCrossSigningVerified() : !deviceTrust.isVerified(); }); this.setState({ - e2eStatus: anyDeviceUnverified ? "warning" : "verified", + e2eStatus: anyDeviceUnverified ? E2EState.Warning : E2EState.Verified, }); } @@ -186,7 +187,7 @@ export default class MemberTile extends React.Component { public render(): JSX.Element { const member = this.props.member; const name = this.getDisplayName(); - const presenceState = member.user ? member.user.presence : null; + const presenceState = member.user?.presence ?? null; const av =

    {_t( - "Warning: Upgrading a room will not automatically migrate room members " + + "Warning: upgrading a room will not automatically migrate room members " + "to the new version of the room. We'll post a link to the new room in the old " + "version of the room - room members will have to click this link to join the new room.", {}, diff --git a/src/components/views/rooms/SearchResultTile.tsx b/src/components/views/rooms/SearchResultTile.tsx index 3ec68b989f4..3992553d20b 100644 --- a/src/components/views/rooms/SearchResultTile.tsx +++ b/src/components/views/rooms/SearchResultTile.tsx @@ -48,7 +48,7 @@ export default class SearchResultTile extends React.Component { // A map of private callEventGroupers = new Map(); - public constructor(props, context) { + public constructor(props: IProps, context: React.ContextType) { super(props, context); this.buildLegacyCallEventGroupers(this.props.timeline); diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 11db1ed4d85..f066c1005d8 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -306,7 +306,8 @@ export class SendMessageComposer extends React.Component(); const myReactionKeys = [...myReactionEvents] .filter((event) => !event.isRedacted()) .map((event) => event.getRelation().key); @@ -490,7 +491,7 @@ export class SendMessageComposer extends React.Component partCreator.deserializePart(p)); + const parts: Part[] = serializedParts.map((p: SerializedPart) => partCreator.deserializePart(p)); if (replyEventId) { dis.dispatch({ action: "reply_to_event", diff --git a/src/components/views/rooms/Stickerpicker.tsx b/src/components/views/rooms/Stickerpicker.tsx index 181ee487e99..8f72690621f 100644 --- a/src/components/views/rooms/Stickerpicker.tsx +++ b/src/components/views/rooms/Stickerpicker.tsx @@ -58,11 +58,11 @@ interface IState { } export default class Stickerpicker extends React.PureComponent { - public static defaultProps = { + public static defaultProps: Partial = { threadId: null, }; - public static currentWidget; + public static currentWidget?: IWidgetEvent; private dispatcherRef: string; diff --git a/src/components/views/rooms/ThirdPartyMemberInfo.tsx b/src/components/views/rooms/ThirdPartyMemberInfo.tsx index 36512d564b5..5d5b942882d 100644 --- a/src/components/views/rooms/ThirdPartyMemberInfo.tsx +++ b/src/components/views/rooms/ThirdPartyMemberInfo.tsx @@ -47,7 +47,7 @@ interface IState { export default class ThirdPartyMemberInfo extends React.Component { private room: Room; - public constructor(props) { + public constructor(props: IProps) { super(props); this.room = MatrixClientPeg.get().getRoom(this.props.event.getRoomId()); @@ -85,7 +85,7 @@ export default class ThirdPartyMemberInfo extends React.Component { whoIsTypingLimit: 3, }; - public state = { + public state: IState = { usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room), delayedStopTypingTimers: {}, }; @@ -61,7 +61,7 @@ export default class WhoIsTypingTile extends React.Component { MatrixClientPeg.get().on(RoomEvent.Timeline, this.onRoomTimeline); } - public componentDidUpdate(_, prevState): void { + public componentDidUpdate(prevProps: IProps, prevState: IState): void { const wasVisible = WhoIsTypingTile.isVisible(prevState); const isVisible = WhoIsTypingTile.isVisible(this.state); if (this.props.onShown && !wasVisible && isVisible) { diff --git a/src/components/views/settings/ChangePassword.tsx b/src/components/views/settings/ChangePassword.tsx index 02af020e6ad..2089c7a167d 100644 --- a/src/components/views/settings/ChangePassword.tsx +++ b/src/components/views/settings/ChangePassword.tsx @@ -33,6 +33,7 @@ import QuestionDialog from "../dialogs/QuestionDialog"; const FIELD_OLD_PASSWORD = "field_old_password"; const FIELD_NEW_PASSWORD = "field_new_password"; const FIELD_NEW_PASSWORD_CONFIRM = "field_new_password_confirm"; +type FieldType = typeof FIELD_OLD_PASSWORD | typeof FIELD_NEW_PASSWORD | typeof FIELD_NEW_PASSWORD_CONFIRM; enum Phase { Edit = "edit", @@ -59,7 +60,7 @@ interface IProps { } interface IState { - fieldValid: {}; + fieldValid: Partial>; phase: Phase; oldPassword: string; newPassword: string; @@ -67,6 +68,10 @@ interface IState { } export default class ChangePassword extends React.Component { + private [FIELD_OLD_PASSWORD]: Field; + private [FIELD_NEW_PASSWORD]: Field; + private [FIELD_NEW_PASSWORD_CONFIRM]: Field; + public static defaultProps: Partial = { onFinished() {}, onError() {}, @@ -221,7 +226,7 @@ export default class ChangePassword extends React.Component { ); }; - private markFieldValid(fieldID: string, valid: boolean): void { + private markFieldValid(fieldID: FieldType, valid: boolean): void { const { fieldValid } = this.state; fieldValid[fieldID] = valid; this.setState({ @@ -317,7 +322,11 @@ export default class ChangePassword extends React.Component { activeElement.blur(); } - const fieldIDsInDisplayOrder = [FIELD_OLD_PASSWORD, FIELD_NEW_PASSWORD, FIELD_NEW_PASSWORD_CONFIRM]; + const fieldIDsInDisplayOrder: FieldType[] = [ + FIELD_OLD_PASSWORD, + FIELD_NEW_PASSWORD, + FIELD_NEW_PASSWORD_CONFIRM, + ]; // Run all fields with stricter validation that no longer allows empty // values for required fields. @@ -358,7 +367,7 @@ export default class ChangePassword extends React.Component { return Object.values(this.state.fieldValid).every(Boolean); } - private findFirstInvalidField(fieldIDs: string[]): Field { + private findFirstInvalidField(fieldIDs: FieldType[]): Field { for (const fieldID of fieldIDs) { if (!this.state.fieldValid[fieldID] && this[fieldID]) { return this[fieldID]; diff --git a/src/components/views/settings/CrossSigningPanel.tsx b/src/components/views/settings/CrossSigningPanel.tsx index d8eaa44050f..ad322cf6ef3 100644 --- a/src/components/views/settings/CrossSigningPanel.tsx +++ b/src/components/views/settings/CrossSigningPanel.tsx @@ -43,7 +43,7 @@ interface IState { export default class CrossSigningPanel extends React.PureComponent<{}, IState> { private unmounted = false; - public constructor(props) { + public constructor(props: {}) { super(props); this.state = {}; diff --git a/src/components/views/settings/CryptographyPanel.tsx b/src/components/views/settings/CryptographyPanel.tsx index cd984355f35..1d5345655ea 100644 --- a/src/components/views/settings/CryptographyPanel.tsx +++ b/src/components/views/settings/CryptographyPanel.tsx @@ -114,7 +114,7 @@ export default class CryptographyPanel extends React.Component { ); }; - private updateBlacklistDevicesFlag = (checked): void => { + private updateBlacklistDevicesFlag = (checked: boolean): void => { MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked); }; } diff --git a/src/components/views/settings/EventIndexPanel.tsx b/src/components/views/settings/EventIndexPanel.tsx index efb05981964..14d63050b16 100644 --- a/src/components/views/settings/EventIndexPanel.tsx +++ b/src/components/views/settings/EventIndexPanel.tsx @@ -35,7 +35,7 @@ interface IState { } export default class EventIndexPanel extends React.Component<{}, IState> { - public constructor(props) { + public constructor(props: {}) { super(props); this.state = { diff --git a/src/components/views/settings/Notifications.tsx b/src/components/views/settings/Notifications.tsx index e491716ab79..db981402655 100644 --- a/src/components/views/settings/Notifications.tsx +++ b/src/components/views/settings/Notifications.tsx @@ -214,7 +214,7 @@ export default class Notifications extends React.PureComponent { private async refreshRules(): Promise> { const ruleSets = await MatrixClientPeg.get().getPushRules(); - const categories = { + const categories: Record = { [RuleId.Master]: RuleClass.Master, [RuleId.DM]: RuleClass.VectorGlobal, diff --git a/src/components/views/settings/SetIdServer.tsx b/src/components/views/settings/SetIdServer.tsx index 799cb93b823..351d5124db9 100644 --- a/src/components/views/settings/SetIdServer.tsx +++ b/src/components/views/settings/SetIdServer.tsx @@ -83,7 +83,7 @@ interface IState { export default class SetIdServer extends React.Component { private dispatcherRef: string; - public constructor(props) { + public constructor(props: IProps) { super(props); let defaultIdServer = ""; @@ -288,8 +288,8 @@ export default class SetIdServer extends React.Component { let message; let danger = false; const messageElements = { - idserver: (sub) => {abbreviateUrl(currentClientIdServer)}, - b: (sub) => {sub}, + idserver: (sub: string) => {abbreviateUrl(currentClientIdServer)}, + b: (sub: string) => {sub}, }; if (!currentServerReachable) { message = ( diff --git a/src/components/views/settings/SpellCheckSettings.tsx b/src/components/views/settings/SpellCheckSettings.tsx index 745815e21eb..9ee3214fdf3 100644 --- a/src/components/views/settings/SpellCheckSettings.tsx +++ b/src/components/views/settings/SpellCheckSettings.tsx @@ -17,17 +17,17 @@ limitations under the License. import React from "react"; import SpellCheckLanguagesDropdown from "../../../components/views/elements/SpellCheckLanguagesDropdown"; -import AccessibleButton from "../../../components/views/elements/AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "../../../components/views/elements/AccessibleButton"; import { _t } from "../../../languageHandler"; interface ExistingSpellCheckLanguageIProps { language: string; - onRemoved(language: string); + onRemoved(language: string): void; } interface SpellCheckLanguagesIProps { languages: Array; - onLanguagesChange(languages: Array); + onLanguagesChange(languages: Array): void; } interface SpellCheckLanguagesIState { @@ -35,7 +35,7 @@ interface SpellCheckLanguagesIState { } export class ExistingSpellCheckLanguage extends React.Component { - private onRemove = (e): void => { + private onRemove = (e: ButtonEvent): void => { e.stopPropagation(); e.preventDefault(); @@ -55,7 +55,7 @@ export class ExistingSpellCheckLanguage extends React.Component { - public constructor(props) { + public constructor(props: SpellCheckLanguagesIProps) { super(props); this.state = { newLanguage: "", @@ -67,7 +67,7 @@ export default class SpellCheckLanguages extends React.Component { + private onAddClick = (e: ButtonEvent): void => { e.stopPropagation(); e.preventDefault(); diff --git a/src/components/views/settings/account/EmailAddresses.tsx b/src/components/views/settings/account/EmailAddresses.tsx index 63184562885..daab0dd4cba 100644 --- a/src/components/views/settings/account/EmailAddresses.tsx +++ b/src/components/views/settings/account/EmailAddresses.tsx @@ -133,7 +133,7 @@ interface IProps { interface IState { verifying: boolean; - addTask: any; // FIXME: When AddThreepid is TSfied + addTask: AddThreepid; continueDisabled: boolean; newEmailAddress: string; } @@ -150,7 +150,7 @@ export default class EmailAddresses extends React.Component { }; } - private onRemoved = (address): void => { + private onRemoved = (address: IThreepid): void => { const emails = this.props.emails.filter((e) => e !== address); this.props.onEmailsChange(emails); }; diff --git a/src/components/views/settings/account/PhoneNumbers.tsx b/src/components/views/settings/account/PhoneNumbers.tsx index 00af94648e5..adbb5e2be96 100644 --- a/src/components/views/settings/account/PhoneNumbers.tsx +++ b/src/components/views/settings/account/PhoneNumbers.tsx @@ -131,7 +131,7 @@ interface IState { verifying: boolean; verifyError: string; verifyMsisdn: string; - addTask: any; // FIXME: When AddThreepid is TSfied + addTask: AddThreepid; continueDisabled: boolean; phoneCountry: string; newPhoneNumber: string; diff --git a/src/components/views/settings/discovery/EmailAddresses.tsx b/src/components/views/settings/discovery/EmailAddresses.tsx index e8f620e524b..f936c984dd2 100644 --- a/src/components/views/settings/discovery/EmailAddresses.tsx +++ b/src/components/views/settings/discovery/EmailAddresses.tsx @@ -22,7 +22,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { _t } from "../../../../languageHandler"; import { MatrixClientPeg } from "../../../../MatrixClientPeg"; import Modal from "../../../../Modal"; -import AddThreepid from "../../../../AddThreepid"; +import AddThreepid, { Binding } from "../../../../AddThreepid"; import ErrorDialog from "../../dialogs/ErrorDialog"; import AccessibleButton from "../../elements/AccessibleButton"; @@ -74,7 +74,7 @@ export class EmailAddress extends React.Component { + private async changeBinding({ bind, label, errorTitle }: Binding): Promise { if (!(await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind())) { return this.changeBindingTangledAddBind({ bind, label, errorTitle }); } @@ -111,7 +111,7 @@ export class EmailAddress extends React.Component { + private async changeBindingTangledAddBind({ bind, label, errorTitle }: Binding): Promise { const { medium, address } = this.props.email; const task = new AddThreepid(); diff --git a/src/components/views/settings/discovery/PhoneNumbers.tsx b/src/components/views/settings/discovery/PhoneNumbers.tsx index 798576364a3..ee4fb379a9d 100644 --- a/src/components/views/settings/discovery/PhoneNumbers.tsx +++ b/src/components/views/settings/discovery/PhoneNumbers.tsx @@ -22,7 +22,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { _t } from "../../../../languageHandler"; import { MatrixClientPeg } from "../../../../MatrixClientPeg"; import Modal from "../../../../Modal"; -import AddThreepid from "../../../../AddThreepid"; +import AddThreepid, { Binding } from "../../../../AddThreepid"; import ErrorDialog from "../../dialogs/ErrorDialog"; import Field from "../../elements/Field"; import AccessibleButton from "../../elements/AccessibleButton"; @@ -70,7 +70,7 @@ export class PhoneNumber extends React.Component { + private async changeBinding({ bind, label, errorTitle }: Binding): Promise { if (!(await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind())) { return this.changeBindingTangledAddBind({ bind, label, errorTitle }); } @@ -111,7 +111,7 @@ export class PhoneNumber extends React.Component { + private async changeBindingTangledAddBind({ bind, label, errorTitle }: Binding): Promise { const { medium, address } = this.props.msisdn; const task = new AddThreepid(); diff --git a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx index d9acba8524a..4588298445c 100644 --- a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx @@ -47,8 +47,8 @@ interface IState { } export default class AdvancedRoomSettingsTab extends React.Component { - public constructor(props: IProps, context: any) { - super(props, context); + public constructor(props: IProps) { + super(props); const msc3946ProcessDynamicPredecessor = SettingsStore.getValue("feature_dynamic_room_predecessors"); @@ -113,7 +113,7 @@ export default class AdvancedRoomSettingsTab extends React.Component

    {_t( - "Warning: Upgrading a room will not automatically migrate room members " + + "Warning: upgrading a room will not automatically migrate room members " + "to the new version of the room. We'll post a link to the new room in the old " + "version of the room - room members will have to click this link to join the new room.", {}, diff --git a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx index 6800fbffbb1..87f320236c3 100644 --- a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx @@ -245,7 +245,7 @@ export default class RolesRoomSettingsTab extends React.Component { const plContent = plEvent ? plEvent.getContent() || {} : {}; const canChangeLevels = room.currentState.mayClientSendStateEvent(EventType.RoomPowerLevels, client); - const plEventsToLabels = { + const plEventsToLabels: Record = { // These will be translated for us later. [EventType.RoomAvatar]: isSpaceRoom ? _td("Change space avatar") : _td("Change room avatar"), [EventType.RoomName]: isSpaceRoom ? _td("Change space name") : _td("Change room name"), diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx index 836fdac6299..bcff00392f5 100644 --- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx @@ -58,7 +58,7 @@ export default class SecurityRoomSettingsTab extends React.Component; - public constructor(props, context) { + public constructor(props: IProps, context: React.ContextType) { super(props, context); const state = context.getRoom(this.props.roomId).currentState; diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index 8852fb50878..abdce2ce88f 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ChangeEvent } from "react"; import { _t } from "../../../../../languageHandler"; import SdkConfig from "../../../../../SdkConfig"; @@ -116,7 +116,7 @@ export default class AppearanceUserSettingsTab extends React.Component { + onChange={(value: ChangeEvent) => { this.setState({ systemFont: value.target.value, }); diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx index b1744e80cca..5b03e8da876 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx @@ -21,6 +21,7 @@ import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types"; import { IThreepid } from "matrix-js-sdk/src/@types/threepids"; import { logger } from "matrix-js-sdk/src/logger"; import { IDelegatedAuthConfig, M_AUTHENTICATION } from "matrix-js-sdk/src/matrix"; +import { MatrixError } from "matrix-js-sdk/src/matrix"; import { _t } from "../../../../../languageHandler"; import ProfileSettings from "../../ProfileSettings"; @@ -34,7 +35,7 @@ import PlatformPeg from "../../../../../PlatformPeg"; import { MatrixClientPeg } from "../../../../../MatrixClientPeg"; import Modal from "../../../../../Modal"; import dis from "../../../../../dispatcher/dispatcher"; -import { Policies, Service, startTermsFlow } from "../../../../../Terms"; +import { Service, ServicePolicyPair, startTermsFlow } from "../../../../../Terms"; import IdentityAuthClient from "../../../../../IdentityAuthClient"; import { abbreviateUrl } from "../../../../../utils/UrlUtils"; import { getThreepidsWithBindStatus } from "../../../../../boundThreepids"; @@ -68,10 +69,7 @@ interface IState { requiredPolicyInfo: { // This object is passed along to a component for handling hasTerms: boolean; - policiesAndServices: { - service: Service; - policies: Policies; - }[]; // From the startTermsFlow callback + policiesAndServices: ServicePolicyPair[]; // From the startTermsFlow callback agreedUrls: string[]; // From the startTermsFlow callback resolve: (values: string[]) => void; // Promise resolve function for startTermsFlow callback }; @@ -258,7 +256,7 @@ export default class GeneralUserSettingsTab extends React.Component { + private onPasswordChangeError = (err: { error: string } & MatrixError): void => { // TODO: Figure out a design that doesn't involve replacing the current dialog let errMsg = err.error || err.message || ""; if (err.httpStatus === 403) { diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx index 295e3ecfd7e..0b4898510fe 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx @@ -42,7 +42,7 @@ interface IState { } export default class HelpUserSettingsTab extends React.Component { - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { @@ -80,7 +80,7 @@ export default class HelpUserSettingsTab extends React.Component }; } - private onClearCacheAndReload = (e): void => { + private onClearCacheAndReload = (): void => { if (!PlatformPeg.get()) return; // Dev note: please keep this log line, it's useful when troubleshooting a MatrixClient suddenly @@ -94,11 +94,11 @@ export default class HelpUserSettingsTab extends React.Component }); }; - private onBugReport = (e): void => { + private onBugReport = (): void => { Modal.createDialog(BugReportDialog, {}); }; - private onStartBotChat = (e): void => { + private onStartBotChat = (): void => { this.props.closeSettingsFn(); createRoom({ dmUserId: SdkConfig.get("welcome_user_id"), diff --git a/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx b/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx index c6df215fc54..cf9a41a5540 100644 --- a/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx @@ -17,7 +17,7 @@ limitations under the License. import React from "react"; -import { ICategory, CATEGORIES, CategoryName } from "../../../../../accessibility/KeyboardShortcuts"; +import { ICategory, CATEGORIES, CategoryName, KeyBindingAction } from "../../../../../accessibility/KeyboardShortcuts"; import SdkConfig from "../../../../../SdkConfig"; import { _t } from "../../../../../languageHandler"; import { @@ -27,7 +27,7 @@ import { import { KeyboardShortcut } from "../../KeyboardShortcut"; interface IKeyboardShortcutRowProps { - name: string; + name: KeyBindingAction; } // Filter out the labs section if labs aren't enabled. diff --git a/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.tsx b/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.tsx index c918a337a3c..4a4743f83bc 100644 --- a/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.tsx @@ -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 { _t } from "../../../../../languageHandler"; @@ -36,7 +36,7 @@ interface IState { } export default class MjolnirUserSettingsTab extends React.Component<{}, IState> { - public constructor(props) { + public constructor(props: {}) { super(props); this.state = { @@ -46,15 +46,15 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState> }; } - private onPersonalRuleChanged = (e): void => { + private onPersonalRuleChanged = (e: ChangeEvent): void => { this.setState({ newPersonalRule: e.target.value }); }; - private onNewListChanged = (e): void => { + private onNewListChanged = (e: ChangeEvent): void => { this.setState({ newList: e.target.value }); }; - private onAddPersonalRule = async (e): Promise => { + private onAddPersonalRule = async (e: SyntheticEvent): Promise => { e.preventDefault(); e.stopPropagation(); @@ -80,7 +80,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState> } }; - private onSubscribeList = async (e): Promise => { + private onSubscribeList = async (e: SyntheticEvent): Promise => { e.preventDefault(); e.stopPropagation(); @@ -279,8 +279,11 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState> {_t( "Your personal ban list holds all the users/servers you personally don't " + "want to see messages from. After ignoring your first user/server, a new room " + - "will show up in your room list named 'My Ban List' - stay in this room to keep " + + "will show up in your room list named '%(myBanList)s' - stay in this room to keep " + "the ban list in effect.", + { + myBanList: _t("My Ban List"), + }, )}

    {this.renderPersonalBanListRules()}
    diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index dd0989ccd83..6048b71c215 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -95,7 +95,7 @@ export default class PreferencesUserSettingsTab extends React.Component = ({ label={_t("Name")} autoFocus={true} value={name} - onChange={(ev) => setName(ev.target.value)} + onChange={(ev: ChangeEvent) => setName(ev.target.value)} disabled={nameDisabled} /> @@ -141,7 +141,7 @@ const SpaceBasicSettings: React.FC = ({ element="textarea" label={_t("Description")} value={topic} - onChange={(ev) => setTopic(ev.target.value)} + onChange={(ev: ChangeEvent) => setTopic(ev.target.value)} rows={3} disabled={topicDisabled} /> diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx index e30baa4dc0e..19eeae94870 100644 --- a/src/components/views/spaces/SpaceCreateMenu.tsx +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -14,7 +14,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ComponentProps, RefObject, SyntheticEvent, KeyboardEvent, useContext, useRef, useState } from "react"; +import React, { + ComponentProps, + RefObject, + SyntheticEvent, + KeyboardEvent, + useContext, + useRef, + useState, + ChangeEvent, +} from "react"; import classNames from "classnames"; import { RoomType } from "matrix-js-sdk/src/@types/event"; import { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests"; @@ -190,7 +199,7 @@ export const SpaceCreateForm: React.FC = ({ label={_t("Name")} autoFocus={true} value={name} - onChange={(ev) => { + onChange={(ev: ChangeEvent) => { const newName = ev.target.value; if (!alias || alias === `#${nameToLocalpart(name)}:${domain}`) { setAlias(`#${nameToLocalpart(newName)}:${domain}`); diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index 958dd1a0d1a..0307a94b672 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -199,7 +199,7 @@ export class SpaceItem extends React.PureComponent { private buttonRef = createRef(); - public constructor(props) { + public constructor(props: IItemProps) { super(props); const collapsed = SpaceTreeLevelLayoutStore.instance.getSpaceCollapsedState( diff --git a/src/components/views/terms/InlineTermsAgreement.tsx b/src/components/views/terms/InlineTermsAgreement.tsx index bfd559df8ac..26880ce1cee 100644 --- a/src/components/views/terms/InlineTermsAgreement.tsx +++ b/src/components/views/terms/InlineTermsAgreement.tsx @@ -20,10 +20,11 @@ import { _t, pickBestLanguage } from "../../../languageHandler"; import { objectClone } from "../../../utils/objects"; import StyledCheckbox from "../elements/StyledCheckbox"; import AccessibleButton from "../elements/AccessibleButton"; +import { ServicePolicyPair } from "../../../Terms"; interface IProps { - policiesAndServicePairs: any[]; - onFinished: (string) => void; + policiesAndServicePairs: ServicePolicyPair[]; + onFinished: (accepted: string[]) => void; agreedUrls: string[]; // array of URLs the user has accepted introElement: React.ReactNode; } diff --git a/src/components/views/toasts/GenericExpiringToast.tsx b/src/components/views/toasts/GenericExpiringToast.tsx index 8ecb848804f..150aaff4008 100644 --- a/src/components/views/toasts/GenericExpiringToast.tsx +++ b/src/components/views/toasts/GenericExpiringToast.tsx @@ -24,7 +24,7 @@ interface IProps extends IGenericToastProps { toastKey: string; numSeconds: number; dismissLabel: string; - onDismiss?(); + onDismiss?(): void; } const SECOND = 1000; diff --git a/src/components/views/toasts/GenericToast.tsx b/src/components/views/toasts/GenericToast.tsx index debd48cbdbb..c40eaab8e88 100644 --- a/src/components/views/toasts/GenericToast.tsx +++ b/src/components/views/toasts/GenericToast.tsx @@ -24,12 +24,12 @@ export interface IProps { detail?: ReactNode; acceptLabel: string; - onAccept(); + onAccept(): void; } interface IPropsExtended extends IProps { rejectLabel: string; - onReject(); + onReject(): void; } const GenericToast: React.FC> = ({ diff --git a/src/components/views/toasts/VerificationRequestToast.tsx b/src/components/views/toasts/VerificationRequestToast.tsx index 4816237bc9e..b9e36e4ec08 100644 --- a/src/components/views/toasts/VerificationRequestToast.tsx +++ b/src/components/views/toasts/VerificationRequestToast.tsx @@ -49,7 +49,7 @@ interface IState { export default class VerificationRequestToast extends React.PureComponent { private intervalHandle: number; - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { counter: Math.ceil(props.request.timeout / 1000) }; } diff --git a/src/components/views/voip/DialPadModal.tsx b/src/components/views/voip/DialPadModal.tsx index 0eea034d24a..810b7f100b7 100644 --- a/src/components/views/voip/DialPadModal.tsx +++ b/src/components/views/voip/DialPadModal.tsx @@ -14,8 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as React from "react"; -import { createRef } from "react"; +import React, { ChangeEvent } from "react"; +import { createRef, SyntheticEvent } from "react"; import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import Field from "../elements/Field"; @@ -24,7 +24,7 @@ import DialPadBackspaceButton from "../elements/DialPadBackspaceButton"; import LegacyCallHandler from "../../../LegacyCallHandler"; interface IProps { - onFinished: (boolean) => void; + onFinished: (dialled: boolean) => void; } interface IState { @@ -34,7 +34,7 @@ interface IState { export default class DialpadModal extends React.PureComponent { private numberEntryFieldRef: React.RefObject = createRef(); - public constructor(props) { + public constructor(props: IProps) { super(props); this.state = { value: "", @@ -45,11 +45,11 @@ export default class DialpadModal extends React.PureComponent { this.props.onFinished(false); }; - public onChange = (ev): void => { + public onChange = (ev: ChangeEvent): void => { this.setState({ value: ev.target.value }); }; - public onFormSubmit = (ev): void => { + public onFormSubmit = (ev: SyntheticEvent): void => { ev.preventDefault(); this.onDialPress(); }; diff --git a/src/components/views/voip/LegacyCallView.tsx b/src/components/views/voip/LegacyCallView.tsx index 19da9bbcf15..15c7fce252d 100644 --- a/src/components/views/voip/LegacyCallView.tsx +++ b/src/components/views/voip/LegacyCallView.tsx @@ -311,7 +311,7 @@ export default class LegacyCallView extends React.Component { // we register global shortcuts here, they *must not conflict* with local shortcuts elsewhere or both will fire // Note that this assumes we always have a LegacyCallView on screen at any given time // LegacyCallHandler would probably be a better place for this - private onNativeKeyDown = (ev): void => { + private onNativeKeyDown = (ev: KeyboardEvent): void => { let handled = false; const callAction = getKeyBindingsManager().getCallAction(ev); diff --git a/src/components/views/voip/VideoFeed.tsx b/src/components/views/voip/VideoFeed.tsx index e0a1fcee282..05d7de3cfd5 100644 --- a/src/components/views/voip/VideoFeed.tsx +++ b/src/components/views/voip/VideoFeed.tsx @@ -171,7 +171,7 @@ export default class VideoFeed extends React.PureComponent { }); }; - private onResize = (e): void => { + private onResize = (e: Event): void => { if (this.props.onResize && !this.props.feed.isLocal()) { this.props.onResize(e); } diff --git a/src/createRoom.ts b/src/createRoom.ts index f4d9769d90e..25c7b10fe2c 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -29,7 +29,7 @@ import { import { logger } from "matrix-js-sdk/src/logger"; import { MatrixClientPeg } from "./MatrixClientPeg"; -import Modal from "./Modal"; +import Modal, { IHandle } from "./Modal"; import { _t } from "./languageHandler"; import dis from "./dispatcher/dispatcher"; import * as Rooms from "./Rooms"; @@ -267,7 +267,7 @@ export default async function createRoom(opts: IOpts): Promise { }); } - let modal; + let modal: IHandle | undefined; if (opts.spinner) modal = Modal.createDialog(Spinner, undefined, "mx_Dialog_spinner"); let roomId: string; diff --git a/src/dispatcher/payloads/ViewRoomPayload.ts b/src/dispatcher/payloads/ViewRoomPayload.ts index e497939ff04..a2f999dddd6 100644 --- a/src/dispatcher/payloads/ViewRoomPayload.ts +++ b/src/dispatcher/payloads/ViewRoomPayload.ts @@ -21,6 +21,7 @@ import { ActionPayload } from "../payloads"; import { Action } from "../actions"; import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore"; import { IOpts } from "../../createRoom"; +import { JoinRoomPayload } from "./JoinRoomPayload"; /* eslint-disable camelcase */ export interface ViewRoomPayload extends Pick { @@ -48,6 +49,7 @@ export interface ViewRoomPayload extends Pick { show_room_tile?: boolean; // Whether to ensure that the room tile is visible in the room list clear_search?: boolean; // Whether to clear the room list search view_call?: boolean; // Whether to view the call or call lobby for the room + opts?: JoinRoomPayload["opts"]; deferred_action?: ActionPayload; // Action to fire after MatrixChat handles this ViewRoom action diff --git a/src/editor/deserialize.ts b/src/editor/deserialize.ts index d4d0de6ed8d..0c57ff4b0c3 100644 --- a/src/editor/deserialize.ts +++ b/src/editor/deserialize.ts @@ -149,7 +149,7 @@ function prefixLines(parts: Part[], prefix: string, pc: PartCreator): void { } function parseChildren(n: Node, pc: PartCreator, opts: IParseOptions, mkListItem?: (li: Node) => Part[]): Part[] { - let prev; + let prev: ChildNode | undefined; return Array.from(n.childNodes).flatMap((c) => { const parsed = parseNode(c, pc, opts, mkListItem); if (parsed.length && prev && (checkBlockNode(prev) || checkBlockNode(c))) { diff --git a/src/editor/history.ts b/src/editor/history.ts index 2f0260025dc..ec0b84ef18c 100644 --- a/src/editor/history.ts +++ b/src/editor/history.ts @@ -47,7 +47,7 @@ export default class HistoryManager { this.removedSinceLastPush = false; } - private shouldPush(inputType, diff): boolean { + private shouldPush(inputType: string, diff: IDiff): boolean { // right now we can only push a step after // the input has been applied to the model, // so we can't push the state before something happened. diff --git a/src/editor/model.ts b/src/editor/model.ts index f4c59e36653..975e63aa4cb 100644 --- a/src/editor/model.ts +++ b/src/editor/model.ts @@ -272,11 +272,11 @@ export default class EditorModel { }; private mergeAdjacentParts(): void { - let prevPart; + let prevPart: Part | undefined; for (let i = 0; i < this._parts.length; ++i) { let part = this._parts[i]; const isEmpty = !part.text.length; - const isMerged = !isEmpty && prevPart && prevPart.merge(part); + const isMerged = !isEmpty && prevPart && prevPart.merge?.(part); if (isEmpty || isMerged) { // remove empty or merged part part = prevPart; diff --git a/src/editor/parts.ts b/src/editor/parts.ts index 8202f765d33..74203b586ef 100644 --- a/src/editor/parts.ts +++ b/src/editor/parts.ts @@ -70,6 +70,8 @@ interface IBasePart { updateDOMNode(node: Node): void; canUpdateDOMNode(node: Node): boolean; toDOMNode(): Node; + + merge?(part: Part): boolean; } interface IPillCandidatePart extends Omit { @@ -227,7 +229,7 @@ abstract class PlainBasePart extends BasePart { return document.createTextNode(this.text); } - public merge(part): boolean { + public merge(part: Part): boolean { if (part.type === this.type) { this._text = this.text + part.text; return true; @@ -254,7 +256,7 @@ export class PlainPart extends PlainBasePart implements IBasePart { } export abstract class PillPart extends BasePart implements IPillPart { - public constructor(public resourceId: string, label) { + public constructor(public resourceId: string, label: string) { super(label); } @@ -455,7 +457,7 @@ class AtRoomPillPart extends RoomPillPart { } class UserPillPart extends PillPart { - public constructor(userId, displayName, private member?: RoomMember) { + public constructor(userId: string, displayName: string, private member?: RoomMember) { super(userId, displayName); } diff --git a/src/editor/render.ts b/src/editor/render.ts index bb7b0887805..a5c90a90777 100644 --- a/src/editor/render.ts +++ b/src/editor/render.ts @@ -27,7 +27,7 @@ export function needsCaretNodeAfter(part: Part, isLastOfLine: boolean): boolean return !part.acceptsCaret && isLastOfLine; } -function insertAfter(node: HTMLElement, nodeToInsert: HTMLElement): void { +function insertAfter(node: ChildNode, nodeToInsert: ChildNode): void { const next = node.nextSibling; if (next) { node.parentElement!.insertBefore(nodeToInsert, next); @@ -51,7 +51,7 @@ function createCaretNode(): HTMLElement { return span; } -function updateCaretNode(node: HTMLElement): void { +function updateCaretNode(node: ChildNode): void { // ensure the caret node contains only a zero-width space if (node.textContent !== CARET_NODE_CHAR) { node.textContent = CARET_NODE_CHAR; @@ -92,7 +92,7 @@ function reconcileLine(lineContainer: ChildNode, parts: Part[]): void { currentNode = isFirst ? lineContainer.firstChild : currentNode!.nextSibling; if (needsCaretNodeBefore(part, prevPart)) { - if (isCaretNode(currentNode)) { + if (isCaretNode(currentNode as Element)) { updateCaretNode(currentNode); currentNode = currentNode.nextSibling; } else { @@ -115,7 +115,7 @@ function reconcileLine(lineContainer: ChildNode, parts: Part[]): void { } if (needsCaretNodeAfter(part, part === lastPart)) { - if (isCaretNode(currentNode?.nextSibling)) { + if (isCaretNode(currentNode?.nextSibling as Element)) { currentNode = currentNode!.nextSibling; updateCaretNode(currentNode as HTMLElement); } else { diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 43a262c371d..eb27c122dc6 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -77,8 +77,8 @@ export function htmlSerializeFromMdIfNeeded(md: string, { forceHTML = false } = const orig = md; if (SettingsStore.getValue("feature_latex_maths")) { - const patternNames = ["tex", "latex"]; - const patternTypes = ["display", "inline"]; + const patternNames = ["tex", "latex"] as const; + const patternTypes = ["display", "inline"] as const; const patternDefaults = { tex: { // detect math with tex delimiters, inline: $...$, display $$...$$ @@ -118,7 +118,7 @@ export function htmlSerializeFromMdIfNeeded(md: string, { forceHTML = false } = patternTypes.forEach(function (patternType) { // get the regex replace pattern from config or use the default const pattern = - (((SdkConfig.get("latex_maths_delims") || {})[patternType] || {})["pattern"] || {})[patternName] || + SdkConfig.get("latex_maths_delims")?.[patternType]?.["pattern"]?.[patternName] || patternDefaults[patternName][patternType]; md = md.replace(RegExp(pattern, "gms"), function (m, p1, p2) { diff --git a/src/emoji.ts b/src/emoji.ts index df1c341681b..8dafd8fbf1c 100644 --- a/src/emoji.ts +++ b/src/emoji.ts @@ -61,7 +61,7 @@ const EMOJIBASE_GROUP_ID_TO_CATEGORY = [ "flags", ]; -export const DATA_BY_CATEGORY = { +export const DATA_BY_CATEGORY: Record = { people: [], nature: [], foods: [], diff --git a/src/hooks/spotlight/useRecentSearches.ts b/src/hooks/spotlight/useRecentSearches.ts index f646ef3705d..f16f9dc54bd 100644 --- a/src/hooks/spotlight/useRecentSearches.ts +++ b/src/hooks/spotlight/useRecentSearches.ts @@ -24,7 +24,7 @@ import SettingsStore from "../../settings/SettingsStore"; export const useRecentSearches = (): [Room[], () => void] => { const [rooms, setRooms] = useState(() => { const cli = MatrixClientPeg.get(); - const recents = SettingsStore.getValue("SpotlightSearch.recentSearches", null); + const recents = SettingsStore.getValue("SpotlightSearch.recentSearches", null); return recents.map((r) => cli.getRoom(r)).filter(Boolean); }); diff --git a/src/hooks/useEventEmitter.ts b/src/hooks/useEventEmitter.ts index ab25a0db728..317182188c7 100644 --- a/src/hooks/useEventEmitter.ts +++ b/src/hooks/useEventEmitter.ts @@ -47,7 +47,7 @@ export function useEventEmitter(emitter: EventEmitter | undefined, eventName: st if (!emitter) return; // Create event listener that calls handler function stored in ref - const eventListener = (...args): void => savedHandler.current(...args); + const eventListener = (...args: any[]): void => savedHandler.current(...args); // Add event listener emitter.on(eventName, eventListener); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 46207ded9ef..382fb3e926f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -458,7 +458,7 @@ "Verifies a user, session, and pubkey tuple": "Verifies a user, session, and pubkey tuple", "Unknown (user, session) pair: (%(userId)s, %(deviceId)s)": "Unknown (user, session) pair: (%(userId)s, %(deviceId)s)", "Session already verified!": "Session already verified!", - "WARNING: Session already verified, but keys do NOT MATCH!": "WARNING: Session already verified, but keys do NOT MATCH!", + "WARNING: session already verified, but keys do NOT MATCH!": "WARNING: session already verified, but keys do NOT MATCH!", "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!", "Verified key": "Verified key", "The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.": "The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.", @@ -1602,7 +1602,7 @@ "Add users and servers you want to ignore here. Use asterisks to have %(brand)s match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Add users and servers you want to ignore here. Use asterisks to have %(brand)s match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.", "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.", "Personal ban list": "Personal ban list", - "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.", + "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named '%(myBanList)s' - stay in this room to keep the ban list in effect.": "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named '%(myBanList)s' - stay in this room to keep the ban list in effect.", "Server or user ID to ignore": "Server or user ID to ignore", "eg: @bot:* or example.org": "eg: @bot:* or example.org", "Ignore": "Ignore", @@ -1669,7 +1669,7 @@ "Voice processing": "Voice processing", "Connection": "Connection", "This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers", - "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.", + "Warning: upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Warning: upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.", "Upgrade this space to the recommended room version": "Upgrade this space to the recommended room version", "Upgrade this room to the recommended room version": "Upgrade this room to the recommended room version", "View older version of %(spaceName)s.": "View older version of %(spaceName)s.", @@ -2848,7 +2848,7 @@ "Waiting for partner to confirm...": "Waiting for partner to confirm...", "Incoming Verification Request": "Incoming Verification Request", "Integrations are disabled": "Integrations are disabled", - "Enable 'Manage Integrations' in Settings to do this.": "Enable 'Manage Integrations' in Settings to do this.", + "Enable '%(manageIntegrations)s' in Settings to do this.": "Enable '%(manageIntegrations)s' in Settings to do this.", "Integrations not allowed": "Integrations not allowed", "Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.": "Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.", "To continue, use Single Sign On to prove your identity.": "To continue, use Single Sign On to prove your identity.", @@ -3141,7 +3141,6 @@ "Enter Security Key": "Enter Security Key", "This looks like a valid Security Key!": "This looks like a valid Security Key!", "Not a valid Security Key": "Not a valid Security Key", - "Warning: You should only set up key backup from a trusted computer.": "Warning: You should only set up key backup from a trusted computer.", "Access your secure message history and set up secure messaging by entering your Security Key.": "Access your secure message history and set up secure messaging by entering your Security Key.", "If you've forgotten your Security Key you can ": "If you've forgotten your Security Key you can ", "There are no active polls in this room": "There are no active polls in this room", @@ -3323,7 +3322,8 @@ "By approving access for this device, it will have full access to your account.": "By approving access for this device, it will have full access to your account.", "Scan the QR code below with your device that's signed out.": "Scan the QR code below with your device that's signed out.", "Start at the sign in screen": "Start at the sign in screen", - "Select 'Scan QR code'": "Select 'Scan QR code'", + "Select '%(scanQRCode)s'": "Select '%(scanQRCode)s'", + "Scan QR code": "Scan QR code", "Review and approve the sign in": "Review and approve the sign in", "Connecting...": "Connecting...", "Waiting for device to sign in": "Waiting for device to sign in", @@ -3571,7 +3571,7 @@ "You cannot sign in to your account. Please contact your homeserver admin for more information.": "You cannot sign in to your account. Please contact your homeserver admin for more information.", "You're signed out": "You're signed out", "Clear personal data": "Clear personal data", - "Warning: Your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.": "Warning: Your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.", + "Warning: your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.": "Warning: your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.", "Follow the instructions sent to %(email)s": "Follow the instructions sent to %(email)s", "Wrong email address?": "Wrong email address?", "Re-enter email address": "Re-enter email address", @@ -3584,7 +3584,7 @@ "The email address doesn't appear to be valid.": "The email address doesn't appear to be valid.", "Sign in instead": "Sign in instead", "Verify your email to continue": "Verify your email to continue", - "We need to know it’s you before resetting your password.\n Click the link in the email we just sent to %(email)s": "We need to know it’s you before resetting your password.\n Click the link in the email we just sent to %(email)s", + "We need to know it’s you before resetting your password. Click the link in the email we just sent to %(email)s": "We need to know it’s you before resetting your password. Click the link in the email we just sent to %(email)s", "Commands": "Commands", "Command Autocomplete": "Command Autocomplete", "Emoji Autocomplete": "Emoji Autocomplete", diff --git a/src/indexing/EventIndex.ts b/src/indexing/EventIndex.ts index a93f4ad6ec2..e3407e73b04 100644 --- a/src/indexing/EventIndex.ts +++ b/src/indexing/EventIndex.ts @@ -95,7 +95,7 @@ export default class EventIndex extends EventEmitter { const client = MatrixClientPeg.get(); const rooms = client.getRooms(); - const isRoomEncrypted = (room): boolean => { + const isRoomEncrypted = (room: Room): boolean => { return client.isRoomEncrypted(room.roomId); }; diff --git a/src/integrations/IntegrationManagerInstance.ts b/src/integrations/IntegrationManagerInstance.ts index 40418a7f6f6..68ca528e773 100644 --- a/src/integrations/IntegrationManagerInstance.ts +++ b/src/integrations/IntegrationManagerInstance.ts @@ -15,6 +15,7 @@ limitations under the License. */ import url from "url"; +import { ComponentProps } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import type { Room } from "matrix-js-sdk/src/models/room"; @@ -76,7 +77,7 @@ export class IntegrationManagerInstance { return dialogTermsInteractionCallback(policyInfo, agreedUrls, "mx_TermsDialog_forIntegrationManager"); }); - const newProps = {}; + const newProps: Partial> = {}; try { await client.connect(); if (!client.hasCredentials()) { diff --git a/src/integrations/IntegrationManagers.ts b/src/integrations/IntegrationManagers.ts index b2e9dc4fda6..7aeaf4ab474 100644 --- a/src/integrations/IntegrationManagers.ts +++ b/src/integrations/IntegrationManagers.ts @@ -16,7 +16,7 @@ limitations under the License. import url from "url"; import { logger } from "matrix-js-sdk/src/logger"; -import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client"; +import { ClientEvent, IClientWellKnown, MatrixClient } from "matrix-js-sdk/src/client"; import { compare } from "matrix-js-sdk/src/utils"; import type { MatrixEvent } from "matrix-js-sdk/src/models/event"; @@ -36,7 +36,7 @@ const KIND_PREFERENCE = [ ]; export class IntegrationManagers { - private static instance; + private static instance?: IntegrationManagers; private managers: IntegrationManagerInstance[] = []; private client: MatrixClient; @@ -83,7 +83,7 @@ export class IntegrationManagers { } } - private setupHomeserverManagers = async (discoveryResponse): Promise => { + private setupHomeserverManagers = async (discoveryResponse: IClientWellKnown): Promise => { logger.log("Updating homeserver-configured integration managers..."); if (discoveryResponse && discoveryResponse["m.integrations"]) { let managers = discoveryResponse["m.integrations"]["managers"]; @@ -202,7 +202,7 @@ export class IntegrationManagers { domainName = url.parse(domainName).host; } - let wkConfig: object; + let wkConfig: IClientWellKnown; try { const result = await fetch(`https://${domainName}/.well-known/matrix/integrations`); wkConfig = await result.json(); diff --git a/src/linkify-matrix.ts b/src/linkify-matrix.ts index 9ac9d8ed95d..64a66345ed2 100644 --- a/src/linkify-matrix.ts +++ b/src/linkify-matrix.ts @@ -230,7 +230,7 @@ export const options: Opts = { }; // Run the plugins -registerPlugin(Type.RoomAlias, ({ scanner, parser, utils }) => { +registerPlugin(Type.RoomAlias, ({ scanner, parser, utils }: any) => { const token = scanner.tokens.POUND as "#"; matrixOpaqueIdLinkifyParser({ scanner, @@ -241,7 +241,7 @@ registerPlugin(Type.RoomAlias, ({ scanner, parser, utils }) => { }); }); -registerPlugin(Type.UserId, ({ scanner, parser, utils }) => { +registerPlugin(Type.UserId, ({ scanner, parser, utils }: any) => { const token = scanner.tokens.AT as "@"; matrixOpaqueIdLinkifyParser({ scanner, diff --git a/src/modules/ModuleComponents.tsx b/src/modules/ModuleComponents.tsx index 7d9e1d2b591..c99d2307c26 100644 --- a/src/modules/ModuleComponents.tsx +++ b/src/modules/ModuleComponents.tsx @@ -16,7 +16,7 @@ limitations under the License. import { TextInputField } from "@matrix-org/react-sdk-module-api/lib/components/TextInputField"; import { Spinner as ModuleSpinner } from "@matrix-org/react-sdk-module-api/lib/components/Spinner"; -import React from "react"; +import React, { ChangeEvent } from "react"; import Field from "../components/views/elements/Field"; import Spinner from "../components/views/elements/Spinner"; @@ -32,7 +32,7 @@ TextInputField.renderFactory = (props) => ( props.onChange(e.target.value)} + onChange={(e: ChangeEvent) => props.onChange(e.target.value)} label={props.label} autoComplete="off" /> diff --git a/src/modules/ProxiedModuleApi.ts b/src/modules/ProxiedModuleApi.ts index 1d3cd1d7935..03374a7df0b 100644 --- a/src/modules/ProxiedModuleApi.ts +++ b/src/modules/ProxiedModuleApi.ts @@ -22,6 +22,7 @@ import React from "react"; import { AccountAuthInfo } from "@matrix-org/react-sdk-module-api/lib/types/AccountAuthInfo"; import { PlainSubstitution } from "@matrix-org/react-sdk-module-api/lib/types/translations"; import * as Matrix from "matrix-js-sdk/src/matrix"; +import { IRegisterRequestParams } from "matrix-js-sdk/src/matrix"; import Modal from "../Modal"; import { _t } from "../languageHandler"; @@ -104,7 +105,7 @@ export class ProxiedModuleApi implements ModuleApi { const client = Matrix.createClient({ baseUrl: hsUrl }); const deviceName = SdkConfig.get("default_device_display_name") || PlatformPeg.get().getDefaultDeviceDisplayName(); - const req = { + const req: IRegisterRequestParams = { username, password, initial_device_display_name: deviceName, diff --git a/src/notifications/ContentRules.ts b/src/notifications/ContentRules.ts index b25de424df5..6060033b813 100644 --- a/src/notifications/ContentRules.ts +++ b/src/notifications/ContentRules.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { IAnnotatedPushRule, IPushRules, PushRuleKind } from "matrix-js-sdk/src/@types/PushRules"; +import { IAnnotatedPushRule, IPushRules, PushRuleKind, PushRuleSet } from "matrix-js-sdk/src/@types/PushRules"; import { PushRuleVectorState, VectorState } from "./PushRuleVectorState"; @@ -107,8 +107,8 @@ export class ContentRules { }; for (const kind in rulesets.global) { - for (let i = 0; i < Object.keys(rulesets.global[kind]).length; ++i) { - const r = rulesets.global[kind][i]; + for (let i = 0; i < Object.keys(rulesets.global[kind as keyof PushRuleSet]).length; ++i) { + const r = rulesets.global[kind as keyof PushRuleSet][i] as IAnnotatedPushRule; // check it's not a default rule if (r.rule_id[0] === "." || kind !== PushRuleKind.ContentSpecific) { diff --git a/src/notifications/StandardActions.ts b/src/notifications/StandardActions.ts index 9eff3ea5977..f3a9be71b75 100644 --- a/src/notifications/StandardActions.ts +++ b/src/notifications/StandardActions.ts @@ -15,6 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { PushRuleAction } from "matrix-js-sdk/src/@types/PushRules"; + import { NotificationUtils } from "./NotificationUtils"; const encodeActions = NotificationUtils.encodeActions; @@ -26,5 +28,5 @@ export class StandardActions { public static ACTION_HIGHLIGHT = encodeActions({ notify: true, highlight: true }); public static ACTION_HIGHLIGHT_DEFAULT_SOUND = encodeActions({ notify: true, sound: "default", highlight: true }); public static ACTION_DONT_NOTIFY = encodeActions({ notify: false }); - public static ACTION_DISABLED = null; + public static ACTION_DISABLED: PushRuleAction[] | null = null; } diff --git a/src/notifications/VectorPushRulesDefinitions.ts b/src/notifications/VectorPushRulesDefinitions.ts index 8cfb75022dd..d1c78047087 100644 --- a/src/notifications/VectorPushRulesDefinitions.ts +++ b/src/notifications/VectorPushRulesDefinitions.ts @@ -47,9 +47,7 @@ class VectorPushRuleDefinition { enabled = rule.enabled; } - for (const stateKey in PushRuleVectorState.states) { - // eslint-disable-line guard-for-in - const state: VectorState = PushRuleVectorState.states[stateKey]; + for (const state of Object.values(PushRuleVectorState.states)) { const vectorStateToActions = this.vectorStateToActions[state]; if (!vectorStateToActions) { @@ -85,7 +83,7 @@ export type { VectorPushRuleDefinition }; /** * The descriptions of rules managed by the Vector UI. */ -export const VectorPushRulesDefinitions = { +export const VectorPushRulesDefinitions: Record = { // Messages containing user's display name ".m.rule.contains_display_name": new VectorPushRuleDefinition({ description: _td("Messages containing my display name"), // passed through _t() translation in src/components/views/settings/Notifications.js diff --git a/src/rageshake/rageshake.ts b/src/rageshake/rageshake.ts index 0a30d02dea4..c0b538f6fb3 100644 --- a/src/rageshake/rageshake.ts +++ b/src/rageshake/rageshake.ts @@ -63,8 +63,8 @@ export class ConsoleLogger { info: "I", warn: "W", error: "E", - }; - Object.keys(consoleFunctionsToLevels).forEach((fnName) => { + } as const; + Object.keys(consoleFunctionsToLevels).forEach((fnName: keyof typeof consoleFunctionsToLevels) => { const level = consoleFunctionsToLevels[fnName]; const originalFn = consoleObj[fnName].bind(consoleObj); this.originalFunctions[fnName] = originalFn; @@ -130,9 +130,9 @@ export class ConsoleLogger { export class IndexedDBLogStore { private id: string; private index = 0; - private db = null; - private flushPromise = null; - private flushAgainPromise = null; + private db: IDBDatabase | null = null; + private flushPromise: Promise | null = null; + private flushAgainPromise: Promise | null = null; public constructor(private indexedDB: IDBFactory, private logger: ConsoleLogger) { this.id = "instance-" + randomString(16); @@ -144,26 +144,22 @@ export class IndexedDBLogStore { public connect(): Promise { const req = this.indexedDB.open("logs"); return new Promise((resolve, reject) => { - req.onsuccess = (event: Event) => { - // @ts-ignore - this.db = event.target.result; + req.onsuccess = () => { + this.db = req.result; // Periodically flush logs to local storage / indexeddb window.setInterval(this.flush.bind(this), FLUSH_RATE_MS); resolve(); }; - req.onerror = (event) => { - const err = - // @ts-ignore - "Failed to open log database: " + event.target.error.name; + req.onerror = () => { + const err = "Failed to open log database: " + req.error.name; logger.error(err); reject(new Error(err)); }; // First time: Setup the object store - req.onupgradeneeded = (event) => { - // @ts-ignore - const db = event.target.result; + req.onupgradeneeded = () => { + const db = req.result; const logObjStore = db.createObjectStore("logs", { keyPath: ["id", "index"], }); @@ -236,9 +232,9 @@ export class IndexedDBLogStore { txn.oncomplete = (event) => { resolve(); }; - txn.onerror = (event) => { - logger.error("Failed to flush logs : ", event); - reject(new Error("Failed to write logs: " + event.target.errorCode)); + txn.onerror = () => { + logger.error("Failed to flush logs : ", txn.error); + reject(new Error("Failed to write logs: " + txn.error.message)); }; objStore.add(this.generateLogEntry(lines)); const lastModStore = txn.objectStore("logslastmod"); @@ -270,11 +266,11 @@ export class IndexedDBLogStore { return new Promise((resolve, reject) => { const query = objectStore.index("id").openCursor(IDBKeyRange.only(id), "prev"); let lines = ""; - query.onerror = (event) => { - reject(new Error("Query failed: " + event.target.errorCode)); + query.onerror = () => { + reject(new Error("Query failed: " + query.error.message)); }; - query.onsuccess = (event) => { - const cursor = event.target.result; + query.onsuccess = () => { + const cursor = query.result; if (!cursor) { resolve(lines); return; // end of results @@ -308,14 +304,14 @@ export class IndexedDBLogStore { }); } - function deleteLogs(id: number): Promise { + function deleteLogs(id: string): Promise { return new Promise((resolve, reject) => { const txn = db.transaction(["logs", "logslastmod"], "readwrite"); const o = txn.objectStore("logs"); // only load the key path, not the data which may be huge const query = o.index("id").openKeyCursor(IDBKeyRange.only(id)); - query.onsuccess = (event) => { - const cursor = event.target.result; + query.onsuccess = () => { + const cursor = query.result; if (!cursor) { return; } @@ -325,8 +321,8 @@ export class IndexedDBLogStore { txn.oncomplete = () => { resolve(); }; - txn.onerror = (event) => { - reject(new Error("Failed to delete logs for " + `'${id}' : ${event.target.errorCode}`)); + txn.onerror = () => { + reject(new Error("Failed to delete logs for " + `'${id}' : ${query.error.message}`)); }; // delete last modified entries const lastModStore = txn.objectStore("logslastmod"); @@ -335,8 +331,11 @@ export class IndexedDBLogStore { } const allLogIds = await fetchLogIds(); - let removeLogIds = []; - const logs = []; + let removeLogIds: string[] = []; + const logs: { + lines: string; + id: string; + }[] = []; let size = 0; for (let i = 0; i < allLogIds.length; i++) { const lines = await fetchLogs(allLogIds[i], MAX_LOG_SIZE - size); @@ -344,7 +343,7 @@ export class IndexedDBLogStore { // always add the log file: fetchLogs will truncate once the maxSize we give it is // exceeded, so we'll go over the max but only by one fragment's worth. logs.push({ - lines: lines, + lines, id: allLogIds[i], }); size += lines.length; @@ -401,21 +400,19 @@ export class IndexedDBLogStore { * resultMapper. */ function selectQuery( - store: IDBIndex, + store: IDBIndex | IDBObjectStore, keyRange: IDBKeyRange, resultMapper: (cursor: IDBCursorWithValue) => T, ): Promise { const query = store.openCursor(keyRange); return new Promise((resolve, reject) => { - const results = []; - query.onerror = (event) => { - // @ts-ignore - reject(new Error("Query failed: " + event.target.errorCode)); + const results: T[] = []; + query.onerror = () => { + reject(new Error("Query failed: " + query.error.message)); }; // collect results - query.onsuccess = (event) => { - // @ts-ignore - const cursor = event.target.result; + query.onsuccess = () => { + const cursor = query.result; if (!cursor) { resolve(results); return; // end of results diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts index 23ea23ff656..ff84ebf31f7 100644 --- a/src/rageshake/submit-rageshake.ts +++ b/src/rageshake/submit-rageshake.ts @@ -173,7 +173,9 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true): Promise window.Modernizr[key] === false); + const missingFeatures = Object.keys(window.Modernizr).filter( + (key: keyof ModernizrStatic) => window.Modernizr[key] === false, + ); if (missingFeatures.length > 0) { body.append("modernizr_missing_features", missingFeatures.join(", ")); } @@ -276,7 +278,7 @@ export async function downloadBugReport(opts: IOpts = {}): Promise { } // Source: https://github.com/beatgammit/tar-js/blob/master/examples/main.js -function uint8ToString(buf: Buffer): string { +function uint8ToString(buf: Uint8Array): string { let out = ""; for (let i = 0; i < buf.length; i += 1) { out += String.fromCharCode(buf[i]); diff --git a/src/sentry.ts b/src/sentry.ts index f4fb1781449..1322def0d68 100644 --- a/src/sentry.ts +++ b/src/sentry.ts @@ -68,7 +68,7 @@ type Contexts = { /* eslint-enable camelcase */ async function getStorageContext(): Promise { - const result = {}; + const result: StorageContext = {}; // add storage persistence/quota information if (navigator.storage && navigator.storage.persisted) { @@ -87,7 +87,7 @@ async function getStorageContext(): Promise { result["storageManager_quota"] = String(estimate.quota); result["storageManager_usage"] = String(estimate.usage); if (estimate.usageDetails) { - const usageDetails = []; + const usageDetails: string[] = []; Object.keys(estimate.usageDetails).forEach((k) => { usageDetails.push(`${k}: ${String(estimate.usageDetails[k])}`); }); @@ -150,13 +150,15 @@ async function getCryptoContext(client: MatrixClient): Promise { } function getDeviceContext(client: MatrixClient): DeviceContext { - const result = { + const result: DeviceContext = { device_id: client?.deviceId, mx_local_settings: localStorage.getItem("mx_local_settings"), }; if (window.Modernizr) { - const missingFeatures = Object.keys(window.Modernizr).filter((key) => window.Modernizr[key] === false); + const missingFeatures = Object.keys(window.Modernizr).filter( + (key) => window.Modernizr[key as keyof ModernizrStatic] === false, + ); if (missingFeatures.length > 0) { result["modernizr_missing_features"] = missingFeatures.join(", "); } diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index fc4c772d69f..8fd10429f45 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -118,10 +118,9 @@ export interface IBaseSetting { // Display name can also be an object for different levels. displayName?: | string - | { - // @ts-ignore - TS wants the key to be a string, but we know better - [level: SettingLevel]: string; - }; + | Partial<{ + [level in SettingLevel]: string; + }>; // Optional description which will be shown as microCopy under SettingsFlags description?: string | (() => ReactNode); @@ -486,9 +485,9 @@ export const SETTINGS: { [setting: string]: ISetting } = { title: _td("New session manager"), caption: () => ( <> -

    {_td("Have greater visibility and control over all your sessions.")}

    +

    {_t("Have greater visibility and control over all your sessions.")}

    - {_td( + {_t( "Our new sessions manager provides better visibility of all your sessions, " + "and greater control over them including the ability to remotely toggle push notifications.", )} diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 9b712a8c3cc..9b5e05327cd 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -42,9 +42,9 @@ import { MatrixClientPeg } from "../MatrixClientPeg"; const defaultWatchManager = new WatchManager(); // Convert the settings to easier to manage objects for the handlers -const defaultSettings = {}; -const invertedDefaultSettings = {}; -const featureNames = []; +const defaultSettings: Record = {}; +const invertedDefaultSettings: Record = {}; +const featureNames: string[] = []; for (const key of Object.keys(SETTINGS)) { defaultSettings[key] = SETTINGS[key].default; if (SETTINGS[key].isFeature) featureNames.push(key); @@ -56,7 +56,7 @@ for (const key of Object.keys(SETTINGS)) { } // Only wrap the handlers with async setters in a local echo wrapper -const LEVEL_HANDLERS = { +const LEVEL_HANDLERS: Record = { [SettingLevel.DEVICE]: new DeviceSettingsHandler(featureNames, defaultWatchManager), [SettingLevel.ROOM_DEVICE]: new RoomDeviceSettingsHandler(defaultWatchManager), [SettingLevel.ROOM_ACCOUNT]: new LocalEchoWrapper( @@ -97,10 +97,9 @@ export type CallbackFn = ( newVal: any, ) => void; -interface IHandlerMap { - // @ts-ignore - TS wants this to be a string key but we know better - [level: SettingLevel]: SettingsHandler; -} +type HandlerMap = Partial<{ + [level in SettingLevel]: SettingsHandler; +}>; /** * Controls and manages application settings by providing varying levels at which the @@ -646,15 +645,17 @@ export default class SettingsStore { logger.log(`--- default level order: ${JSON.stringify(LEVEL_ORDER)}`); logger.log(`--- registered handlers: ${JSON.stringify(Object.keys(LEVEL_HANDLERS))}`); - const doChecks = (settingName): void => { + const doChecks = (settingName: string): void => { for (const handlerName of Object.keys(LEVEL_HANDLERS)) { - const handler = LEVEL_HANDLERS[handlerName]; + const handler = LEVEL_HANDLERS[handlerName as SettingLevel]; try { const value = handler.getValue(settingName, roomId); logger.log(`--- ${handlerName}@${roomId || ""} = ${JSON.stringify(value)}`); } catch (e) { - logger.log(`--- ${handler}@${roomId || ""} THREW ERROR: ${e.message}`); + logger.log( + `--- ${handler.constructor.name}@${roomId || ""} THREW ERROR: ${e.message}`, + ); logger.error(e); } @@ -663,7 +664,7 @@ export default class SettingsStore { const value = handler.getValue(settingName, null); logger.log(`--- ${handlerName}@ = ${JSON.stringify(value)}`); } catch (e) { - logger.log(`--- ${handler}@ THREW ERROR: ${e.message}`); + logger.log(`--- ${handler.constructor.name}@ THREW ERROR: ${e.message}`); logger.error(e); } } @@ -728,10 +729,10 @@ export default class SettingsStore { return handlers[level]; } - private static getHandlers(settingName: string): IHandlerMap { + private static getHandlers(settingName: string): HandlerMap { if (!SETTINGS[settingName]) return {}; - const handlers = {}; + const handlers: Partial> = {}; for (const level of SETTINGS[settingName].supportedLevels) { if (!LEVEL_HANDLERS[level]) throw new Error("Unexpected level " + level); if (SettingsStore.isLevelSupported(level)) handlers[level] = LEVEL_HANDLERS[level]; diff --git a/src/settings/handlers/MatrixClientBackedSettingsHandler.ts b/src/settings/handlers/MatrixClientBackedSettingsHandler.ts index c25c53e00b7..c5f385c2b70 100644 --- a/src/settings/handlers/MatrixClientBackedSettingsHandler.ts +++ b/src/settings/handlers/MatrixClientBackedSettingsHandler.ts @@ -48,5 +48,5 @@ export default abstract class MatrixClientBackedSettingsHandler extends Settings return MatrixClientBackedSettingsHandler._matrixClient; } - protected abstract initMatrixClient(oldClient: MatrixClient, newClient: MatrixClient); + protected abstract initMatrixClient(oldClient: MatrixClient, newClient: MatrixClient): void; } diff --git a/src/settings/handlers/PlatformSettingsHandler.ts b/src/settings/handlers/PlatformSettingsHandler.ts index c5d496a44af..2ebc5091d99 100644 --- a/src/settings/handlers/PlatformSettingsHandler.ts +++ b/src/settings/handlers/PlatformSettingsHandler.ts @@ -41,7 +41,7 @@ export default class PlatformSettingsHandler extends SettingsHandler { // Load setting values as they are async and `getValue` must be synchronous Object.entries(SETTINGS).forEach(([key, setting]) => { if (setting.supportedLevels.includes(SettingLevel.PLATFORM) && payload.platform.supportsSetting(key)) { - payload.platform.getSettingValue(key).then((value) => { + payload.platform.getSettingValue(key).then((value: any) => { this.store[key] = value; }); } diff --git a/src/shouldHideEvent.ts b/src/shouldHideEvent.ts index 3c13ce0733c..9c32e62adb1 100644 --- a/src/shouldHideEvent.ts +++ b/src/shouldHideEvent.ts @@ -58,7 +58,9 @@ function memberEventDiff(ev: MatrixEvent): IDiff { export default function shouldHideEvent(ev: MatrixEvent, ctx?: IRoomState): boolean { // Accessing the settings store directly can be expensive if done frequently, // so we should prefer using cached values if a RoomContext is available - const isEnabled = ctx ? (name) => ctx[name] : (name) => SettingsStore.getValue(name, ev.getRoomId()); + const isEnabled = ctx + ? (name: keyof IRoomState) => ctx[name] + : (name: string) => SettingsStore.getValue(name, ev.getRoomId()); // Hide redacted events // Deleted events with a thread are always shown regardless of user preference diff --git a/src/stores/AsyncStore.ts b/src/stores/AsyncStore.ts index 6f1658009be..8873b0845c1 100644 --- a/src/stores/AsyncStore.ts +++ b/src/stores/AsyncStore.ts @@ -106,5 +106,5 @@ export abstract class AsyncStore extends EventEmitter { * Called when the dispatcher broadcasts a dispatch event. * @param {ActionPayload} payload The event being dispatched. */ - protected abstract onDispatch(payload: ActionPayload); + protected abstract onDispatch(payload: ActionPayload): void; } diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts index 349daa255d0..c469a7ac576 100644 --- a/src/stores/BreadcrumbsStore.ts +++ b/src/stores/BreadcrumbsStore.ts @@ -126,7 +126,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { }; private async updateRooms(): Promise { - let roomIds = SettingsStore.getValue("breadcrumb_rooms"); + let roomIds = SettingsStore.getValue("breadcrumb_rooms"); if (!roomIds || roomIds.length === 0) roomIds = []; const rooms = roomIds.map((r) => this.matrixClient.getRoom(r)).filter((r) => !!r); diff --git a/src/stores/LifecycleStore.ts b/src/stores/LifecycleStore.ts index 98d5e6923a3..26fe328ff31 100644 --- a/src/stores/LifecycleStore.ts +++ b/src/stores/LifecycleStore.ts @@ -22,10 +22,10 @@ import { ActionPayload } from "../dispatcher/payloads"; import { DoAfterSyncPreparedPayload } from "../dispatcher/payloads/DoAfterSyncPreparedPayload"; interface IState { - deferredAction: any; + deferredAction: ActionPayload; } -const INITIAL_STATE = { +const INITIAL_STATE: IState = { deferredAction: null, }; diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index e9f61afe8c9..6dca6dce248 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -60,6 +60,7 @@ import { import { IRoomStateEventsActionPayload } from "../actions/MatrixActionCreators"; import { showCantStartACallDialog } from "../voice-broadcast/utils/showCantStartACallDialog"; import { pauseNonLiveBroadcastFromOtherRoom } from "../voice-broadcast/utils/pauseNonLiveBroadcastFromOtherRoom"; +import { ActionPayload } from "../dispatcher/payloads"; const NUM_JOIN_RETRY = 5; @@ -248,7 +249,7 @@ export class RoomViewStore extends EventEmitter { } } - private onDispatch(payload): void { + private onDispatch(payload: ActionPayload): void { // eslint-disable-line @typescript-eslint/naming-convention switch (payload.action) { // view_room: @@ -258,10 +259,10 @@ export class RoomViewStore extends EventEmitter { // - event_offset: 100 // - highlighted: true case Action.ViewRoom: - this.viewRoom(payload); + this.viewRoom(payload as ViewRoomPayload); break; case Action.ViewThread: - this.viewThread(payload); + this.viewThread(payload as ThreadPayload); break; // for these events blank out the roomId as we are no longer in the RoomView case "view_welcome_page": @@ -279,7 +280,7 @@ export class RoomViewStore extends EventEmitter { this.onRoomStateEvents((payload as IRoomStateEventsActionPayload).event); break; case Action.ViewRoomError: - this.viewRoomError(payload); + this.viewRoomError(payload as ViewRoomErrorPayload); break; case "will_join": this.setState({ @@ -294,10 +295,10 @@ export class RoomViewStore extends EventEmitter { // join_room: // - opts: options for joinRoom case Action.JoinRoom: - this.joinRoom(payload); + this.joinRoom(payload as JoinRoomPayload); break; case Action.JoinRoomError: - this.joinRoomError(payload); + this.joinRoomError(payload as JoinRoomErrorPayload); break; case Action.JoinRoomReady: { if (this.state.roomId === payload.roomId) { diff --git a/src/stores/local-echo/EchoTransaction.ts b/src/stores/local-echo/EchoTransaction.ts index 26cf14c255b..9f5563d5d78 100644 --- a/src/stores/local-echo/EchoTransaction.ts +++ b/src/stores/local-echo/EchoTransaction.ts @@ -30,7 +30,7 @@ export class EchoTransaction extends Whenable { public readonly startTime = new Date(); - public constructor(public readonly auditName, public runFn: RunFn) { + public constructor(public readonly auditName: string, public runFn: RunFn) { super(); } diff --git a/src/stores/local-echo/GenericEchoChamber.ts b/src/stores/local-echo/GenericEchoChamber.ts index bae73777bde..00b9fe28433 100644 --- a/src/stores/local-echo/GenericEchoChamber.ts +++ b/src/stores/local-echo/GenericEchoChamber.ts @@ -40,7 +40,7 @@ export abstract class GenericEchoChamber extends Ev this.onClientChanged(oldClient, client); } - protected abstract onClientChanged(oldClient: MatrixClient, newClient: MatrixClient); + protected abstract onClientChanged(oldClient: MatrixClient, newClient: MatrixClient): void; /** * Gets a value. If the key is in flight, the cached value will be returned. If diff --git a/src/stores/local-echo/RoomEchoChamber.ts b/src/stores/local-echo/RoomEchoChamber.ts index d58ebc61c4c..88a3b84c83b 100644 --- a/src/stores/local-echo/RoomEchoChamber.ts +++ b/src/stores/local-echo/RoomEchoChamber.ts @@ -16,6 +16,7 @@ limitations under the License. import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { EventType } from "matrix-js-sdk/src/@types/event"; +import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/matrix"; import { GenericEchoChamber, implicitlyReverted, PROPERTY_UPDATED } from "./GenericEchoChamber"; import { getRoomNotifsState, RoomNotifState, setRoomNotifsState } from "../../RoomNotifs"; @@ -33,14 +34,12 @@ export class RoomEchoChamber extends GenericEchoChamber this.properties.get(k)); } - protected onClientChanged(oldClient, newClient): void { + protected onClientChanged(oldClient: MatrixClient, newClient: MatrixClient): void { this.properties.clear(); - if (oldClient) { - oldClient.removeListener("accountData", this.onAccountData); - } + oldClient?.removeListener(ClientEvent.AccountData, this.onAccountData); if (newClient) { // Register the listeners first - newClient.on("accountData", this.onAccountData); + newClient.on(ClientEvent.AccountData, this.onAccountData); // Then populate the properties map this.updateNotificationVolume(); diff --git a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts index 187a45cc03d..35826c6497d 100644 --- a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts @@ -25,15 +25,13 @@ import { OrderingAlgorithm } from "./OrderingAlgorithm"; import { NotificationColor } from "../../../notifications/NotificationColor"; import { RoomNotificationStateStore } from "../../../notifications/RoomNotificationStateStore"; -interface ICategorizedRoomMap { - // @ts-ignore - TS wants this to be a string, but we know better - [category: NotificationColor]: Room[]; -} +type CategorizedRoomMap = Partial<{ + [category in NotificationColor]: Room[]; +}>; -interface ICategoryIndex { - // @ts-ignore - TS wants this to be a string, but we know better - [category: NotificationColor]: number; // integer -} +type CategoryIndex = Partial<{ + [category in NotificationColor]: number; // integer +}>; // Caution: changing this means you'll need to update a bunch of assumptions and // comments! Check the usage of Category carefully to figure out what needs changing @@ -69,15 +67,15 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { // tracks when rooms change categories and splices the orderedRooms array as // needed, preventing many ordering operations. - private indices: ICategoryIndex = {}; + private indices: CategoryIndex = {}; public constructor(tagId: TagID, initialSortingAlgorithm: SortAlgorithm) { super(tagId, initialSortingAlgorithm); } // noinspection JSMethodCanBeStatic - private categorizeRooms(rooms: Room[]): ICategorizedRoomMap { - const map: ICategorizedRoomMap = { + private categorizeRooms(rooms: Room[]): CategorizedRoomMap { + const map: CategorizedRoomMap = { [NotificationColor.Unsent]: [], [NotificationColor.Red]: [], [NotificationColor.Grey]: [], @@ -106,12 +104,18 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { // Every other sorting type affects the categories, not the whole tag. const categorized = this.categorizeRooms(rooms); for (const category of Object.keys(categorized)) { - const roomsToOrder = categorized[category]; - categorized[category] = sortRoomsWithAlgorithm(roomsToOrder, this.tagId, this.sortingAlgorithm); + if (!isNaN(Number(category))) continue; + const notificationColor = category as unknown as NotificationColor; + const roomsToOrder = categorized[notificationColor]; + categorized[notificationColor] = sortRoomsWithAlgorithm( + roomsToOrder, + this.tagId, + this.sortingAlgorithm, + ); } const newlyOrganized: Room[] = []; - const newIndices: ICategoryIndex = {}; + const newIndices: CategoryIndex = {}; for (const category of CATEGORY_ORDER) { newIndices[category] = newlyOrganized.length; @@ -206,7 +210,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { } // noinspection JSMethodCanBeStatic - private getCategoryFromIndices(index: number, indices: ICategoryIndex): NotificationColor { + private getCategoryFromIndices(index: number, indices: CategoryIndex): NotificationColor { for (let i = 0; i < CATEGORY_ORDER.length; i++) { const category = CATEGORY_ORDER[i]; const isLast = i === CATEGORY_ORDER.length - 1; @@ -226,7 +230,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { nRooms: number, fromCategory: NotificationColor, toCategory: NotificationColor, - indices: ICategoryIndex, + indices: CategoryIndex, ): void { // We have to update the index of the category *after* the from/toCategory variables // in order to update the indices correctly. Because the room is moving from/to those @@ -238,7 +242,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { this.alterCategoryPositionBy(toCategory, +nRooms, indices); } - private alterCategoryPositionBy(category: NotificationColor, n: number, indices: ICategoryIndex): void { + private alterCategoryPositionBy(category: NotificationColor, n: number, indices: CategoryIndex): void { // Note: when we alter a category's index, we actually have to modify the ones following // the target and not the target itself. diff --git a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts index 3d333efa173..61a3d29dd2b 100644 --- a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts @@ -35,7 +35,7 @@ export class NaturalAlgorithm extends OrderingAlgorithm { this.cachedOrderedRooms = sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm); } - public handleRoomUpdate(room, cause): boolean { + public handleRoomUpdate(room: Room, cause: RoomUpdateCause): boolean { const isSplice = cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved; const isInPlace = cause === RoomUpdateCause.Timeline || cause === RoomUpdateCause.ReadReceipt; if (!isSplice && !isInPlace) { diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts index 4aa88c8d5a8..be842049b23 100644 --- a/src/stores/widgets/StopGapWidgetDriver.ts +++ b/src/stores/widgets/StopGapWidgetDriver.ts @@ -185,7 +185,14 @@ export class StopGapWidgetDriver extends WidgetDriver { let rememberApproved = false; if (missing.size > 0) { try { - const [result] = await Modal.createDialog(WidgetCapabilitiesPromptDialog, { + const [result] = await Modal.createDialog< + [ + { + approved: Capability[]; + remember: boolean; + }, + ] + >(WidgetCapabilitiesPromptDialog, { requestedCapabilities: missing, widget: this.forWidget, widgetKind: this.forWidgetKind, diff --git a/src/stores/widgets/WidgetLayoutStore.ts b/src/stores/widgets/WidgetLayoutStore.ts index ef5f62de739..992b6d76655 100644 --- a/src/stores/widgets/WidgetLayoutStore.ts +++ b/src/stores/widgets/WidgetLayoutStore.ts @@ -96,14 +96,13 @@ export class WidgetLayoutStore extends ReadyWatchingStore { private static internalInstance: WidgetLayoutStore; private byRoom: { - [roomId: string]: { - // @ts-ignore - TS wants a string key, but we know better - [container: Container]: { + [roomId: string]: Partial<{ + [container in Container]: { ordered: IApp[]; height?: number; distributions?: number[]; }; - }; + }>; } = {}; private pinnedRef: string; @@ -391,7 +390,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { if (numbers.length === 2) numbers.splice(1, 0, remaining); if (numbers.length === 1) numbers.push(remaining); - const localLayout = {}; + const localLayout: Record = {}; widgets.forEach((w, i) => { localLayout[w.id] = { container: container, @@ -410,7 +409,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { public setContainerHeight(room: Room, container: Container, height: number): void { const widgets = this.getContainerWidgets(room, container); const widths = this.byRoom[room.roomId]?.[container]?.distributions; - const localLayout = {}; + const localLayout: Record = {}; widgets.forEach((w, i) => { localLayout[w.id] = { container: container, @@ -433,7 +432,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { const widths = this.byRoom[room.roomId]?.[container]?.distributions; const height = this.byRoom[room.roomId]?.[container]?.height; - const localLayout = {}; + const localLayout: Record = {}; widgets.forEach((w, i) => { localLayout[w.id] = { container: container, @@ -449,7 +448,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { const allWidgets = this.getAllWidgets(room); if (!allWidgets.some(([w]) => w.id === widget.id)) return; // invalid // Prepare other containers (potentially move widgets to obey the following rules) - const newLayout = {}; + const newLayout: Record = {}; switch (toContainer) { case Container.Right: // new "right" widget @@ -516,11 +515,11 @@ export class WidgetLayoutStore extends ReadyWatchingStore { const containers = this.byRoom[room.roomId]; if (!containers) return []; - const ret = []; + const ret: [IApp, Container][] = []; for (const container of Object.keys(containers)) { - const widgets = containers[container].ordered; + const widgets = containers[container as Container].ordered; for (const widget of widgets) { - ret.push([widget, container]); + ret.push([widget, container as Container]); } } return ret; diff --git a/src/stores/widgets/WidgetPermissionStore.ts b/src/stores/widgets/WidgetPermissionStore.ts index da77f612c32..b0458494567 100644 --- a/src/stores/widgets/WidgetPermissionStore.ts +++ b/src/stores/widgets/WidgetPermissionStore.ts @@ -61,7 +61,10 @@ export class WidgetPermissionStore { public setOIDCState(widget: Widget, kind: WidgetKind, roomId: string, newState: OIDCState): void { const settingsKey = this.packSettingKey(widget, kind, roomId); - let currentValues = SettingsStore.getValue("widgetOpenIDPermissions"); + let currentValues = SettingsStore.getValue<{ + allow?: string[]; + deny?: string[]; + }>("widgetOpenIDPermissions"); if (!currentValues) { currentValues = {}; } diff --git a/src/theme.ts b/src/theme.ts index 30f7a8e7bd2..7de8689c693 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -22,11 +22,11 @@ import SettingsStore from "./settings/SettingsStore"; import ThemeWatcher from "./settings/watchers/ThemeWatcher"; export const DEFAULT_THEME = "light"; -const HIGH_CONTRAST_THEMES = { +const HIGH_CONTRAST_THEMES: Record = { light: "light-high-contrast", }; -interface IFontFaces { +interface IFontFaces extends Omit, "src"> { src: { format: string; url: string; @@ -80,7 +80,7 @@ export function enumerateThemes(): { [key: string]: string } { "dark": _t("Dark"), }; const customThemes = SettingsStore.getValue("custom_themes"); - const customThemeNames = {}; + const customThemeNames: Record = {}; for (const { name } of customThemes) { customThemeNames[`custom-${name}`] = name; } @@ -126,31 +126,31 @@ const allowedFontFaceProps = [ "font-variation-settings", "src", "unicode-range", -]; +] as const; function generateCustomFontFaceCSS(faces: IFontFaces[]): string { return faces .map((face) => { - const src = - face.src && - face.src - .map((srcElement) => { - let format; - if (srcElement.format) { - format = `format("${srcElement.format}")`; - } - if (srcElement.url) { - return `url("${srcElement.url}") ${format}`; - } else if (srcElement.local) { - return `local("${srcElement.local}") ${format}`; - } - return ""; - }) - .join(", "); - const props = Object.keys(face).filter((prop) => allowedFontFaceProps.includes(prop)); + const src = face.src + ?.map((srcElement) => { + let format: string; + if (srcElement.format) { + format = `format("${srcElement.format}")`; + } + if (srcElement.url) { + return `url("${srcElement.url}") ${format}`; + } else if (srcElement.local) { + return `local("${srcElement.local}") ${format}`; + } + return ""; + }) + .join(", "); + const props = Object.keys(face).filter((prop: typeof allowedFontFaceProps[number]) => + allowedFontFaceProps.includes(prop), + ) as Array; const body = props .map((prop) => { - let value; + let value: string; if (prop === "src") { value = src; } else if (prop === "font-family") { @@ -169,7 +169,7 @@ function generateCustomFontFaceCSS(faces: IFontFaces[]): string { function setCustomThemeVars(customTheme: ICustomTheme): void { const { style } = document.body; - function setCSSColorVariable(name, hexColor, doPct = true): void { + function setCSSColorVariable(name: string, hexColor: string, doPct = true): void { style.setProperty(`--${name}`, hexColor); if (doPct) { // uses #rrggbbaa to define the color with alpha values at 0%, 15% and 50% @@ -215,9 +215,9 @@ export function getCustomTheme(themeName: string): ICustomTheme { if (!customThemes) { throw new Error(`No custom themes set, can't set custom theme "${themeName}"`); } - const customTheme = customThemes.find((t) => t.name === themeName); + const customTheme = customThemes.find((t: ITheme) => t.name === themeName); if (!customTheme) { - const knownNames = customThemes.map((t) => t.name).join(", "); + const knownNames = customThemes.map((t: ITheme) => t.name).join(", "); throw new Error(`Can't find custom theme "${themeName}", only know ${knownNames}`); } return customTheme; @@ -248,7 +248,7 @@ export async function setTheme(theme?: string): Promise { const styleElements = new Map(); const themes = Array.from(document.querySelectorAll("[data-mx-theme]")); themes.forEach((theme) => { - styleElements.set(theme.attributes["data-mx-theme"].value.toLowerCase(), theme); + styleElements.set(theme.dataset.mxTheme.toLowerCase(), theme); }); if (!styleElements.has(stylesheetName)) { diff --git a/src/utils/AutoDiscoveryUtils.tsx b/src/utils/AutoDiscoveryUtils.tsx index 4a07dc0372f..4297950b42b 100644 --- a/src/utils/AutoDiscoveryUtils.tsx +++ b/src/utils/AutoDiscoveryUtils.tsx @@ -15,8 +15,9 @@ limitations under the License. */ import React, { ReactNode } from "react"; -import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery"; +import { AutoDiscovery, ClientConfig } from "matrix-js-sdk/src/autodiscovery"; import { logger } from "matrix-js-sdk/src/logger"; +import { IClientWellKnown } from "matrix-js-sdk/src/matrix"; import { _t, _td, newTranslatableError } from "../languageHandler"; import { makeType } from "./TypeUtils"; @@ -149,7 +150,7 @@ export default class AutoDiscoveryUtils { throw newTranslatableError(_td("No homeserver URL provided")); } - const wellknownConfig = { + const wellknownConfig: IClientWellKnown = { "m.homeserver": { base_url: homeserverUrl, }, @@ -190,7 +191,7 @@ export default class AutoDiscoveryUtils { */ public static buildValidatedConfigFromDiscovery( serverName: string, - discoveryResult, + discoveryResult: ClientConfig, syntaxOnly = false, isSynthetic = false, ): ValidatedServerConfig { @@ -219,8 +220,8 @@ export default class AutoDiscoveryUtils { } else if (isResult && isResult.state !== AutoDiscovery.PROMPT) { logger.error("Error determining preferred identity server URL:", isResult); if (isResult.state === AutoDiscovery.FAIL_ERROR) { - if (AutoDiscovery.ALL_ERRORS.indexOf(isResult.error) !== -1) { - throw newTranslatableError(isResult.error); + if (AutoDiscovery.ALL_ERRORS.indexOf(isResult.error as string) !== -1) { + throw newTranslatableError(isResult.error as string); } throw newTranslatableError(_td("Unexpected error resolving identity server configuration")); } // else the error is not related to syntax - continue anyways. @@ -235,8 +236,8 @@ export default class AutoDiscoveryUtils { if (hsResult.state !== AutoDiscovery.SUCCESS) { logger.error("Error processing homeserver config:", hsResult); if (!syntaxOnly || !AutoDiscoveryUtils.isLivelinessError(hsResult.error)) { - if (AutoDiscovery.ALL_ERRORS.indexOf(hsResult.error) !== -1) { - throw newTranslatableError(hsResult.error); + if (AutoDiscovery.ALL_ERRORS.indexOf(hsResult.error as string) !== -1) { + throw newTranslatableError(hsResult.error as string); } throw newTranslatableError(_td("Unexpected error resolving homeserver configuration")); } // else the error is not related to syntax - continue anyways. diff --git a/src/utils/DMRoomMap.ts b/src/utils/DMRoomMap.ts index 24f67c2ff26..71bf1856a6e 100644 --- a/src/utils/DMRoomMap.ts +++ b/src/utils/DMRoomMap.ts @@ -189,7 +189,7 @@ export default class DMRoomMap { return Object.keys(this.roomToUser) .map((r) => ({ userId: this.getUserIdForRoomId(r), room: this.matrixClient.getRoom(r) })) .filter((r) => r.userId && r.room && r.room.getInvitedAndJoinedMemberCount() === 2) - .reduce((obj, r) => (obj[r.userId] = r.room) && obj, {}); + .reduce((obj, r) => (obj[r.userId] = r.room) && obj, {} as Record); } /** diff --git a/src/utils/DecryptFile.ts b/src/utils/DecryptFile.ts index 1d993ba5f0a..be984787a35 100644 --- a/src/utils/DecryptFile.ts +++ b/src/utils/DecryptFile.ts @@ -23,7 +23,7 @@ import { IEncryptedFile, IMediaEventInfo } from "../customisations/models/IMedia import { getBlobSafeMimeType } from "./blobs"; export class DownloadError extends Error { - public constructor(e) { + public constructor(e: Error) { super(e.message); this.name = "DownloadError"; this.stack = e.stack; @@ -31,7 +31,7 @@ export class DownloadError extends Error { } export class DecryptError extends Error { - public constructor(e) { + public constructor(e: Error) { super(e.message); this.name = "DecryptError"; this.stack = e.stack; diff --git a/src/utils/FileDownloader.ts b/src/utils/FileDownloader.ts index cc54e77ecef..b7831590730 100644 --- a/src/utils/FileDownloader.ts +++ b/src/utils/FileDownloader.ts @@ -18,7 +18,7 @@ export type getIframeFn = () => HTMLIFrameElement; // eslint-disable-line @types export const DEFAULT_STYLES = { imgSrc: "", - imgStyle: null, // css props + imgStyle: null as string | null, // css props style: "", textContent: "", }; diff --git a/src/utils/FormattingUtils.ts b/src/utils/FormattingUtils.ts index 5ad8ecbd41a..334a8659fe7 100644 --- a/src/utils/FormattingUtils.ts +++ b/src/utils/FormattingUtils.ts @@ -15,6 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { ReactElement, ReactNode } from "react"; + import { _t } from "../languageHandler"; import { jsxJoin } from "./ReactUtils"; @@ -105,9 +107,9 @@ export function getUserNameColorClass(userId: string): string { * between each item, but with the last item appended as " and [lastItem]". */ export function formatCommaSeparatedList(items: string[], itemLimit?: number): string; -export function formatCommaSeparatedList(items: JSX.Element[], itemLimit?: number): JSX.Element; -export function formatCommaSeparatedList(items: Array, itemLimit?: number): JSX.Element | string; -export function formatCommaSeparatedList(items: Array, itemLimit?: number): JSX.Element | string { +export function formatCommaSeparatedList(items: ReactElement[], itemLimit?: number): ReactElement; +export function formatCommaSeparatedList(items: ReactNode[], itemLimit?: number): ReactNode; +export function formatCommaSeparatedList(items: ReactNode[], itemLimit?: number): ReactNode { const remaining = itemLimit === undefined ? 0 : Math.max(items.length - itemLimit, 0); if (items.length === 0) { return ""; diff --git a/src/utils/MultiInviter.ts b/src/utils/MultiInviter.ts index 204fc595d92..bdb25b54234 100644 --- a/src/utils/MultiInviter.ts +++ b/src/utils/MultiInviter.ts @@ -81,7 +81,7 @@ export default class MultiInviter { * @param {boolean} sendSharedHistoryKeys whether to share e2ee keys with the invitees if applicable. * @returns {Promise} Resolved when all invitations in the queue are complete */ - public invite(addresses, reason?: string, sendSharedHistoryKeys = false): Promise { + public invite(addresses: string[], reason?: string, sendSharedHistoryKeys = false): Promise { if (this.addresses.length > 0) { throw new Error("Already inviting/invited"); } diff --git a/src/utils/ReactUtils.tsx b/src/utils/ReactUtils.tsx index 59fa7a3b911..ac256438383 100644 --- a/src/utils/ReactUtils.tsx +++ b/src/utils/ReactUtils.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactNode } from "react"; /** * Joins an array into one value with a joiner. E.g. join(["hello", "world"], " ") -> hello world @@ -22,8 +22,8 @@ import React from "react"; * @param joiner the string/JSX.Element to join with * @returns the joined array */ -export function jsxJoin(array: Array, joiner?: string | JSX.Element): JSX.Element { - const newArray = []; +export function jsxJoin(array: ReactNode[], joiner?: string | JSX.Element): JSX.Element { + const newArray: ReactNode[] = []; array.forEach((element, index) => { newArray.push(element, index === array.length - 1 ? null : joiner); }); diff --git a/src/utils/Reply.ts b/src/utils/Reply.ts index 7bafd9f66ad..514587bb1b2 100644 --- a/src/utils/Reply.ts +++ b/src/utils/Reply.ts @@ -69,7 +69,15 @@ export function getNestedReplyText( ): { body: string; html: string } | null { if (!ev) return null; - let { body, formatted_body: html, msgtype } = ev.getContent(); + let { + body, + formatted_body: html, + msgtype, + } = ev.getContent<{ + body: string; + msgtype?: string; + formatted_body?: string; + }>(); if (getParentEventId(ev)) { if (body) body = stripPlainReply(body); } diff --git a/src/utils/SnakedObject.ts b/src/utils/SnakedObject.ts index 0340002fa0e..1f6770c4879 100644 --- a/src/utils/SnakedObject.ts +++ b/src/utils/SnakedObject.ts @@ -25,7 +25,7 @@ export class SnakedObject> { const val = this.obj[key]; if (val !== undefined) return val; - return this.obj[altCaseName ?? snakeToCamel(key)]; + return this.obj[(altCaseName ?? snakeToCamel(key))]; } // Make JSON.stringify() pretend that everything is fine diff --git a/src/utils/Timer.ts b/src/utils/Timer.ts index 975a498e962..f42d2bd99f2 100644 --- a/src/utils/Timer.ts +++ b/src/utils/Timer.ts @@ -30,7 +30,7 @@ export default class Timer { private startTs?: number; private promise: Promise; private resolve: () => void; - private reject: (Error) => void; + private reject: (err: Error) => void; public constructor(private timeout: number) { this.setNotStarted(); diff --git a/src/utils/UserInteractiveAuth.ts b/src/utils/UserInteractiveAuth.ts index 5c7568ee6ea..042fe67effe 100644 --- a/src/utils/UserInteractiveAuth.ts +++ b/src/utils/UserInteractiveAuth.ts @@ -40,7 +40,7 @@ export function wrapRequestWithDialog( Modal.createDialog(InteractiveAuthDialog, { ...opts, authData: error.data, - makeRequest: (authData) => boundFunction(authData, ...args), + makeRequest: (authData?: IAuthData) => boundFunction(authData, ...args), onFinished: (success, result) => { if (success) { resolve(result); diff --git a/src/utils/ValidatedServerConfig.ts b/src/utils/ValidatedServerConfig.ts index 5e0957275a5..7d2325d5105 100644 --- a/src/utils/ValidatedServerConfig.ts +++ b/src/utils/ValidatedServerConfig.ts @@ -25,5 +25,5 @@ export class ValidatedServerConfig { // when the server config is based on static URLs the hsName is not resolvable and things may wish to use hsUrl public isNameResolvable: boolean; - public warning: string; + public warning: string | Error; } diff --git a/src/utils/WidgetUtils.ts b/src/utils/WidgetUtils.ts index ad591303e2d..4382b621267 100644 --- a/src/utils/WidgetUtils.ts +++ b/src/utils/WidgetUtils.ts @@ -143,7 +143,7 @@ export default class WidgetUtils { return new Promise((resolve, reject) => { // Tests an account data event, returning true if it's in the state // we're waiting for it to be in - function eventInIntendedState(ev): boolean { + function eventInIntendedState(ev: MatrixEvent): boolean { if (!ev || !ev.getContent()) return false; if (add) { return ev.getContent()[widgetId] !== undefined; @@ -158,7 +158,7 @@ export default class WidgetUtils { return; } - function onAccountData(ev): void { + function onAccountData(ev: MatrixEvent): void { const currentAccountDataEvent = MatrixClientPeg.get().getAccountData("m.widgets"); if (eventInIntendedState(currentAccountDataEvent)) { MatrixClientPeg.get().removeListener(ClientEvent.AccountData, onAccountData); diff --git a/src/utils/beacon/geolocation.ts b/src/utils/beacon/geolocation.ts index bfa63d98be1..1fa3367b6f4 100644 --- a/src/utils/beacon/geolocation.ts +++ b/src/utils/beacon/geolocation.ts @@ -37,7 +37,7 @@ const GeolocationOptions = { }; const isGeolocationPositionError = (error: unknown): error is GeolocationPositionError => - typeof error === "object" && !!error["PERMISSION_DENIED"]; + typeof error === "object" && !!(error as GeolocationPositionError)["PERMISSION_DENIED"]; /** * Maps GeolocationPositionError to our GeolocationError enum */ @@ -132,7 +132,7 @@ export const watchPosition = ( onWatchPositionError: (error: GeolocationError) => void, ): ClearWatchCallback => { try { - const onError = (error): void => onWatchPositionError(mapGeolocationError(error)); + const onError = (error: GeolocationPositionError): void => onWatchPositionError(mapGeolocationError(error)); const watchId = getGeolocation().watchPosition(onWatchPosition, onError, GeolocationOptions); const clearWatch = (): void => { getGeolocation().clearWatch(watchId); diff --git a/src/utils/createMatrixClient.ts b/src/utils/createMatrixClient.ts index 389d7269b4f..cf2e22abb10 100644 --- a/src/utils/createMatrixClient.ts +++ b/src/utils/createMatrixClient.ts @@ -31,7 +31,7 @@ const localStorage = window.localStorage; // just *accessing* indexedDB throws an exception in firefox with // indexeddb disabled. -let indexedDB; +let indexedDB: IDBFactory; try { indexedDB = window.indexedDB; } catch (e) {} diff --git a/src/utils/exportUtils/exportUtils.ts b/src/utils/exportUtils/exportUtils.ts index 139e3a37407..c6f3d4a6c75 100644 --- a/src/utils/exportUtils/exportUtils.ts +++ b/src/utils/exportUtils/exportUtils.ts @@ -22,6 +22,8 @@ export enum ExportFormat { Json = "Json", } +export type ExportFormatKey = "Html" | "PlainText" | "Json"; + export enum ExportType { Timeline = "Timeline", Beginning = "Beginning", @@ -29,6 +31,8 @@ export enum ExportType { // START_DATE = "START_DATE", } +export type ExportTypeKey = "Timeline" | "Beginning" | "LastNMessages"; + export const textForFormat = (format: ExportFormat): string => { switch (format) { case ExportFormat.Html: diff --git a/src/utils/image-media.ts b/src/utils/image-media.ts index 02de0799282..003f6d8a379 100644 --- a/src/utils/image-media.ts +++ b/src/utils/image-media.ts @@ -15,13 +15,13 @@ limitations under the License. */ import { BlurhashEncoder } from "../BlurhashEncoder"; +import { IEncryptedFile } from "../customisations/models/IMediaEventContent"; type ThumbnailableElement = HTMLImageElement | HTMLVideoElement; interface IThumbnail { info: { - // eslint-disable-next-line camelcase - thumbnail_info: { + thumbnail_info?: { w: number; h: number; mimetype: string; @@ -30,6 +30,8 @@ interface IThumbnail { w: number; h: number; [BLURHASH_FIELD]: string; + thumbnail_url?: string; + thumbnail_file?: IEncryptedFile; }; thumbnail: Blob; } diff --git a/src/utils/location/map.ts b/src/utils/location/map.ts index a43b6d80346..8ba3d68c48f 100644 --- a/src/utils/location/map.ts +++ b/src/utils/location/map.ts @@ -83,7 +83,7 @@ export const makeMapSiteLink = (coords: GeolocationCoordinates): string => { }; export const createMapSiteLinkFromEvent = (event: MatrixEvent): string => { - const content: Object = event.getContent(); + const content = event.getContent(); const mLocation = content[M_LOCATION.name]; if (mLocation !== undefined) { const uri = mLocation["uri"]; diff --git a/src/utils/membership.ts b/src/utils/membership.ts index fc56f85a679..b272b7d37ee 100644 --- a/src/utils/membership.ts +++ b/src/utils/membership.ts @@ -44,10 +44,9 @@ export enum EffectiveMembership { Leave = "LEAVE", } -export interface MembershipSplit { - // @ts-ignore - TS wants this to be a string key, but we know better. - [state: EffectiveMembership]: Room[]; -} +export type MembershipSplit = Partial<{ + [state in EffectiveMembership]: Room[]; +}>; export function splitRoomsByMembership(rooms: Room[]): MembershipSplit { const split: MembershipSplit = { diff --git a/src/utils/objects.ts b/src/utils/objects.ts index f3bc8e93f1b..c2496b4c7c4 100644 --- a/src/utils/objects.ts +++ b/src/utils/objects.ts @@ -90,7 +90,7 @@ export function objectHasDiff(a: O, b: O): boolean { const aKeys = Object.keys(a); const bKeys = Object.keys(b); if (aKeys.length !== bKeys.length) return true; - const possibleChanges = arrayIntersection(aKeys, bKeys); + const possibleChanges = arrayIntersection(aKeys, bKeys) as Array; // if the amalgamation of both sets of keys has the a different length to the inputs then there must be a change if (possibleChanges.length !== aKeys.length) return true; diff --git a/src/utils/permalinks/Permalinks.ts b/src/utils/permalinks/Permalinks.ts index eb72a873622..a209e9dfc4e 100644 --- a/src/utils/permalinks/Permalinks.ts +++ b/src/utils/permalinks/Permalinks.ts @@ -208,7 +208,7 @@ export class RoomPermalinkCreator { } private updateAllowedServers(): void { - const bannedHostsRegexps = []; + const bannedHostsRegexps: RegExp[] = []; let allowedHostsRegexps = [ANY_REGEX]; // default allow everyone if (this.room.currentState) { const aclEvent = this.room.currentState.getStateEvents(EventType.RoomServerAcl, ""); @@ -216,10 +216,10 @@ export class RoomPermalinkCreator { const getRegex = (hostname: string): RegExp => new RegExp("^" + utils.globToRegexp(hostname, false) + "$"); - const denied = aclEvent.getContent().deny || []; + const denied = aclEvent.getContent<{ deny: string[] }>().deny || []; denied.forEach((h) => bannedHostsRegexps.push(getRegex(h))); - const allowed = aclEvent.getContent().allow || []; + const allowed = aclEvent.getContent<{ allow: string[] }>().allow || []; allowedHostsRegexps = []; // we don't want to use the default rule here allowed.forEach((h) => allowedHostsRegexps.push(getRegex(h))); } diff --git a/src/utils/pillify.tsx b/src/utils/pillify.tsx index ed95b5ca48d..ff21567b9c0 100644 --- a/src/utils/pillify.tsx +++ b/src/utils/pillify.tsx @@ -106,7 +106,7 @@ export function pillifyLinks(nodes: ArrayLike, mxEvent: MatrixEvent, pi // we're adding now, since we've just inserted nodes into the structure // we're iterating over. // Note we've checked roomNotifTextNodes.length > 0 so we'll do this at least once - node = roomNotifTextNode.nextSibling; + node = roomNotifTextNode.nextSibling as Element; const pillContainer = document.createElement("span"); const pill = ( diff --git a/src/widgets/CapabilityText.tsx b/src/widgets/CapabilityText.tsx index dbc16e004ac..d39c2171b24 100644 --- a/src/widgets/CapabilityText.tsx +++ b/src/widgets/CapabilityText.tsx @@ -37,24 +37,9 @@ import TextWithTooltip from "../components/views/elements/TextWithTooltip"; type GENERIC_WIDGET_KIND = "generic"; // eslint-disable-line @typescript-eslint/naming-convention const GENERIC_WIDGET_KIND: GENERIC_WIDGET_KIND = "generic"; -interface ISendRecvStaticCapText { - // @ts-ignore - TS wants the key to be a string, but we know better - [eventType: EventType]: { - // @ts-ignore - TS wants the key to be a string, but we know better - [widgetKind: WidgetKind | GENERIC_WIDGET_KIND]: { - // @ts-ignore - TS wants the key to be a string, but we know better - [direction: EventDirection]: string; - }; - }; -} - -interface IStaticCapText { - // @ts-ignore - TS wants the key to be a string, but we know better - [capability: Capability]: { - // @ts-ignore - TS wants the key to be a string, but we know better - [widgetKind: WidgetKind | GENERIC_WIDGET_KIND]: string; - }; -} +type SendRecvStaticCapText = Partial< + Record>>> +>; export interface TranslatedCapabilityText { primary: TranslatedString; @@ -62,7 +47,7 @@ export interface TranslatedCapabilityText { } export class CapabilityText { - private static simpleCaps: IStaticCapText = { + private static simpleCaps: Record>> = { [MatrixCapabilities.AlwaysOnScreen]: { [WidgetKind.Room]: _td("Remain on your screen when viewing another room, when running"), [GENERIC_WIDGET_KIND]: _td("Remain on your screen while running"), @@ -79,7 +64,7 @@ export class CapabilityText { }, }; - private static stateSendRecvCaps: ISendRecvStaticCapText = { + private static stateSendRecvCaps: SendRecvStaticCapText = { [EventType.RoomTopic]: { [WidgetKind.Room]: { [EventDirection.Send]: _td("Change the topic of this room"), @@ -122,7 +107,7 @@ export class CapabilityText { }, }; - private static nonStateSendRecvCaps: ISendRecvStaticCapText = { + private static nonStateSendRecvCaps: SendRecvStaticCapText = { [EventType.Sticker]: { [WidgetKind.Room]: { [EventDirection.Send]: _td("Send stickers to this room as you"), diff --git a/test/Notifier-test.ts b/test/Notifier-test.ts index e2b867ee7f5..3bd120c2747 100644 --- a/test/Notifier-test.ts +++ b/test/Notifier-test.ts @@ -266,7 +266,7 @@ describe("Notifier", () => { }); }); - describe("_displayPopupNotification", () => { + describe("displayPopupNotification", () => { const testCases: { event: IContent | undefined; count: number }[] = [ { event: { is_silenced: true }, count: 0 }, { event: { is_silenced: false }, count: 1 }, @@ -274,13 +274,13 @@ describe("Notifier", () => { ]; it.each(testCases)("does not dispatch when notifications are silenced", ({ event, count }) => { mockClient.setAccountData(accountDataEventKey, event!); - Notifier._displayPopupNotification(testEvent, testRoom); + Notifier.displayPopupNotification(testEvent, testRoom); expect(MockPlatform.displayNotification).toHaveBeenCalledTimes(count); }); it("should display a notification for a voice message", () => { const audioEvent = mkAudioEvent(); - Notifier._displayPopupNotification(audioEvent, testRoom); + Notifier.displayPopupNotification(audioEvent, testRoom); expect(MockPlatform.displayNotification).toHaveBeenCalledWith( "@user:example.com (!room1:server)", "@user:example.com: test audio message", @@ -292,7 +292,7 @@ describe("Notifier", () => { it("should display the expected notification for a broadcast chunk with sequence = 1", () => { const audioEvent = mkAudioEvent({ sequence: 1 }); - Notifier._displayPopupNotification(audioEvent, testRoom); + Notifier.displayPopupNotification(audioEvent, testRoom); expect(MockPlatform.displayNotification).toHaveBeenCalledWith( "@user:example.com (!room1:server)", "@user:example.com started a voice broadcast", @@ -304,7 +304,7 @@ describe("Notifier", () => { it("should display the expected notification for a broadcast chunk with sequence = 1", () => { const audioEvent = mkAudioEvent({ sequence: 2 }); - Notifier._displayPopupNotification(audioEvent, testRoom); + Notifier.displayPopupNotification(audioEvent, testRoom); expect(MockPlatform.displayNotification).not.toHaveBeenCalled(); }); }); @@ -330,7 +330,7 @@ describe("Notifier", () => { Notifier.getSoundForRoom = jest.fn(); mockClient.setAccountData(accountDataEventKey, event!); - Notifier._playAudioNotification(testEvent, testRoom); + Notifier.playAudioNotification(testEvent, testRoom); expect(Notifier.getSoundForRoom).toHaveBeenCalledTimes(count); }); }); @@ -445,7 +445,7 @@ describe("Notifier", () => { }); }); - describe("_evaluateEvent", () => { + describe("evaluateEvent", () => { beforeEach(() => { jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockReturnValue(testRoom.roomId); @@ -453,7 +453,7 @@ describe("Notifier", () => { jest.spyOn(Modal, "hasDialogs").mockReturnValue(false); - jest.spyOn(Notifier, "_displayPopupNotification").mockReset(); + jest.spyOn(Notifier, "displayPopupNotification").mockReset(); jest.spyOn(Notifier, "isEnabled").mockReturnValue(true); mockClient.getPushActionsForEvent.mockReturnValue({ @@ -465,9 +465,9 @@ describe("Notifier", () => { }); it("should show a pop-up", () => { - expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(0); - Notifier._evaluateEvent(testEvent); - expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(0); + expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(0); + Notifier.evaluateEvent(testEvent); + expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(0); const eventFromOtherRoom = mkEvent({ event: true, @@ -477,8 +477,8 @@ describe("Notifier", () => { content: {}, }); - Notifier._evaluateEvent(eventFromOtherRoom); - expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(1); + Notifier.evaluateEvent(eventFromOtherRoom); + expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(1); }); it("should a pop-up for thread event", async () => { @@ -489,13 +489,13 @@ describe("Notifier", () => { participantUserIds: ["@bob:example.org"], }); - expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(0); + expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(0); - Notifier._evaluateEvent(rootEvent); - expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(0); + Notifier.evaluateEvent(rootEvent); + expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(0); - Notifier._evaluateEvent(events[1]); - expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(1); + Notifier.evaluateEvent(events[1]); + expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(1); dis.dispatch({ action: Action.ViewThread, @@ -504,13 +504,13 @@ describe("Notifier", () => { await waitFor(() => expect(SdkContextClass.instance.roomViewStore.getThreadId()).toBe(rootEvent.getId())); - Notifier._evaluateEvent(events[1]); - expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(1); + Notifier.evaluateEvent(events[1]); + expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(1); }); it("should show a pop-up for an audio message", () => { - Notifier._evaluateEvent(mkAudioEvent()); - expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(1); + Notifier.evaluateEvent(mkAudioEvent()); + expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(1); }); it("should not show a notification for broadcast info events in any case", () => { @@ -527,8 +527,8 @@ describe("Notifier", () => { "ABC123", ); - Notifier._evaluateEvent(broadcastStartedEvent); - expect(Notifier._displayPopupNotification).not.toHaveBeenCalled(); + Notifier.evaluateEvent(broadcastStartedEvent); + expect(Notifier.displayPopupNotification).not.toHaveBeenCalled(); }); }); diff --git a/test/PosthogAnalytics-test.ts b/test/PosthogAnalytics-test.ts index b0404dff875..07f3f51dfb3 100644 --- a/test/PosthogAnalytics-test.ts +++ b/test/PosthogAnalytics-test.ts @@ -42,7 +42,7 @@ export interface ITestEvent extends IPosthogEvent { describe("PosthogAnalytics", () => { let fakePosthog: PostHog; - const shaHashes = { + const shaHashes: Record = { "42": "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049", "some": "a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b", "pii": "bd75b3e080945674c0351f75e0db33d1e90986fa07b318ea7edf776f5eef38d4", @@ -54,7 +54,7 @@ describe("PosthogAnalytics", () => { window.crypto = { subtle: { - digest: async (_, encodedMessage) => { + digest: async (_: AlgorithmIdentifier, encodedMessage: BufferSource) => { const message = new TextDecoder().decode(encodedMessage); const hexHash = shaHashes[message]; const bytes = []; diff --git a/test/ScalarAuthClient-test.ts b/test/ScalarAuthClient-test.ts index 8641486c5cb..00b1d0644bb 100644 --- a/test/ScalarAuthClient-test.ts +++ b/test/ScalarAuthClient-test.ts @@ -16,6 +16,7 @@ limitations under the License. import { mocked } from "jest-mock"; import fetchMock from "fetch-mock-jest"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; import ScalarAuthClient from "../src/ScalarAuthClient"; import { stubClient } from "./test-utils"; @@ -32,7 +33,7 @@ describe("ScalarAuthClient", function () { expires_in: 999, }; - let client; + let client: MatrixClient; beforeEach(function () { jest.clearAllMocks(); client = stubClient(); diff --git a/test/TextForEvent-test.ts b/test/TextForEvent-test.ts index e05ae0b339a..526fa6acdd3 100644 --- a/test/TextForEvent-test.ts +++ b/test/TextForEvent-test.ts @@ -17,7 +17,7 @@ limitations under the License. import { EventType, MatrixClient, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix"; import TestRenderer from "react-test-renderer"; import { ReactElement } from "react"; -import { mocked } from "jest-mock"; +import { Mocked, mocked } from "jest-mock"; import { textForEvent } from "../src/TextForEvent"; import SettingsStore from "../src/settings/SettingsStore"; @@ -49,8 +49,14 @@ function mockPinnedEvent(pinnedMessageIds?: string[], prevPinnedMessageIds?: str // Helper function that renders a component to a plain text string. // Once snapshots are introduced in tests, this function will no longer be necessary, // and should be replaced with snapshots. -function renderComponent(component): string { - const serializeObject = (object): string => { +function renderComponent(component: TestRenderer.ReactTestRenderer): string { + const serializeObject = ( + object: + | TestRenderer.ReactTestRendererJSON + | TestRenderer.ReactTestRendererJSON[] + | TestRenderer.ReactTestRendererNode + | TestRenderer.ReactTestRendererNode[], + ): string => { if (typeof object === "string") { return object === " " ? "" : object; } @@ -59,7 +65,7 @@ function renderComponent(component): string { return object[0]; } - if (object["type"] !== undefined && typeof object["children"] !== undefined) { + if (!Array.isArray(object) && object["type"] !== undefined && typeof object["children"] !== undefined) { return serializeObject(object.children); } @@ -168,26 +174,26 @@ describe("TextForEvent", () => { }); describe("textForPowerEvent()", () => { - let mockClient; + let mockClient: Mocked; const mockRoom = { getMember: jest.fn(), - }; + } as unknown as Mocked; const userA = { - id: "@a", + userId: "@a", name: "Alice", rawDisplayName: "Alice", - }; + } as RoomMember; const userB = { - id: "@b", + userId: "@b", name: "Bob (@b)", rawDisplayName: "Bob", - }; + } as RoomMember; const userC = { - id: "@c", + userId: "@c", name: "Bob (@c)", rawDisplayName: "Bob", - }; + } as RoomMember; interface PowerEventProps { usersDefault?: number; prevDefault?: number; @@ -197,7 +203,7 @@ describe("TextForEvent", () => { const mockPowerEvent = ({ usersDefault, prevDefault, users, prevUsers }: PowerEventProps): MatrixEvent => { const mxEvent = new MatrixEvent({ type: EventType.RoomPowerLevels, - sender: userA.id, + sender: userA.userId, state_key: "", content: { users_default: usersDefault, @@ -218,7 +224,7 @@ describe("TextForEvent", () => { mockClient.getRoom.mockClear().mockReturnValue(mockRoom); mockRoom.getMember .mockClear() - .mockImplementation((userId) => [userA, userB, userC].find((u) => u.id === userId)); + .mockImplementation((userId) => [userA, userB, userC].find((u) => u.userId === userId)); (SettingsStore.getValue as jest.Mock).mockReturnValue(true); }); @@ -231,10 +237,10 @@ describe("TextForEvent", () => { it("returns falsy when no users have changed power level", () => { const event = mockPowerEvent({ users: { - [userA.id]: 100, + [userA.userId]: 100, }, prevUsers: { - [userA.id]: 100, + [userA.userId]: 100, }, }); expect(textForEvent(event)).toBeFalsy(); @@ -245,10 +251,10 @@ describe("TextForEvent", () => { usersDefault: 100, prevDefault: 50, users: { - [userA.id]: 100, + [userA.userId]: 100, }, prevUsers: { - [userA.id]: 50, + [userA.userId]: 50, }, }); expect(textForEvent(event)).toBeFalsy(); @@ -257,10 +263,10 @@ describe("TextForEvent", () => { it("returns correct message for a single user with changed power level", () => { const event = mockPowerEvent({ users: { - [userB.id]: 100, + [userB.userId]: 100, }, prevUsers: { - [userB.id]: 50, + [userB.userId]: 50, }, }); const expectedText = "Alice changed the power level of Bob (@b) from Moderator to Admin."; @@ -272,10 +278,10 @@ describe("TextForEvent", () => { usersDefault: 20, prevDefault: 101, users: { - [userB.id]: 20, + [userB.userId]: 20, }, prevUsers: { - [userB.id]: 50, + [userB.userId]: 50, }, }); const expectedText = "Alice changed the power level of Bob (@b) from Moderator to Default."; @@ -285,10 +291,10 @@ describe("TextForEvent", () => { it("returns correct message for a single user with power level changed to a custom level", () => { const event = mockPowerEvent({ users: { - [userB.id]: -1, + [userB.userId]: -1, }, prevUsers: { - [userB.id]: 50, + [userB.userId]: 50, }, }); const expectedText = "Alice changed the power level of Bob (@b) from Moderator to Custom (-1)."; @@ -298,12 +304,12 @@ describe("TextForEvent", () => { it("returns correct message for a multiple power level changes", () => { const event = mockPowerEvent({ users: { - [userB.id]: 100, - [userC.id]: 50, + [userB.userId]: 100, + [userC.userId]: 50, }, prevUsers: { - [userB.id]: 50, - [userC.id]: 101, + [userB.userId]: 50, + [userC.userId]: 101, }, }); const expectedText = @@ -315,7 +321,7 @@ describe("TextForEvent", () => { describe("textForCanonicalAliasEvent()", () => { const userA = { - id: "@a", + userId: "@a", name: "Alice", }; @@ -328,7 +334,7 @@ describe("TextForEvent", () => { const mockEvent = ({ alias, prevAlias, altAliases, prevAltAliases }: AliasEventProps): MatrixEvent => new MatrixEvent({ type: EventType.RoomCanonicalAlias, - sender: userA.id, + sender: userA.userId, state_key: "", content: { alias, @@ -418,7 +424,7 @@ describe("TextForEvent", () => { }); describe("textForPollStartEvent()", () => { - let pollEvent; + let pollEvent: MatrixEvent; beforeEach(() => { pollEvent = new MatrixEvent({ @@ -449,7 +455,7 @@ describe("TextForEvent", () => { }); describe("textForMessageEvent()", () => { - let messageEvent; + let messageEvent: MatrixEvent; beforeEach(() => { messageEvent = new MatrixEvent({ diff --git a/test/UserActivity-test.ts b/test/UserActivity-test.ts index 8703b6e34d9..44f39b0fd7a 100644 --- a/test/UserActivity-test.ts +++ b/test/UserActivity-test.ts @@ -20,25 +20,25 @@ import EventEmitter from "events"; import UserActivity from "../src/UserActivity"; class FakeDomEventEmitter extends EventEmitter { - addEventListener(what, l) { + addEventListener(what: string, l: (...args: any[]) => void) { this.on(what, l); } - removeEventListener(what, l) { + removeEventListener(what: string, l: (...args: any[]) => void) { this.removeListener(what, l); } } describe("UserActivity", function () { - let fakeWindow; - let fakeDocument; - let userActivity; - let clock; + let fakeWindow: FakeDomEventEmitter; + let fakeDocument: FakeDomEventEmitter & { hasFocus?(): boolean }; + let userActivity: UserActivity; + let clock: FakeTimers.InstalledClock; beforeEach(function () { fakeWindow = new FakeDomEventEmitter(); fakeDocument = new FakeDomEventEmitter(); - userActivity = new UserActivity(fakeWindow, fakeDocument); + userActivity = new UserActivity(fakeWindow as unknown as Window, fakeDocument as unknown as Document); userActivity.start(); clock = FakeTimers.install(); }); @@ -65,7 +65,7 @@ describe("UserActivity", function () { it("should not consider user active after activity if no window focus", function () { fakeDocument.hasFocus = jest.fn().mockReturnValue(false); - userActivity.onUserActivity({}); + userActivity.onUserActivity({ type: "event" } as Event); expect(userActivity.userActiveNow()).toBe(false); expect(userActivity.userActiveRecently()).toBe(false); }); @@ -73,7 +73,7 @@ describe("UserActivity", function () { it("should consider user active shortly after activity", function () { fakeDocument.hasFocus = jest.fn().mockReturnValue(true); - userActivity.onUserActivity({}); + userActivity.onUserActivity({ type: "event" } as Event); expect(userActivity.userActiveNow()).toBe(true); expect(userActivity.userActiveRecently()).toBe(true); clock.tick(200); @@ -84,7 +84,7 @@ describe("UserActivity", function () { it("should consider user not active after 10s of no activity", function () { fakeDocument.hasFocus = jest.fn().mockReturnValue(true); - userActivity.onUserActivity({}); + userActivity.onUserActivity({ type: "event" } as Event); clock.tick(10000); expect(userActivity.userActiveNow()).toBe(false); }); @@ -92,7 +92,7 @@ describe("UserActivity", function () { it("should consider user passive after 10s of no activity", function () { fakeDocument.hasFocus = jest.fn().mockReturnValue(true); - userActivity.onUserActivity({}); + userActivity.onUserActivity({ type: "event" } as Event); clock.tick(10000); expect(userActivity.userActiveRecently()).toBe(true); }); @@ -100,7 +100,7 @@ describe("UserActivity", function () { it("should not consider user passive after 10s if window un-focused", function () { fakeDocument.hasFocus = jest.fn().mockReturnValue(true); - userActivity.onUserActivity({}); + userActivity.onUserActivity({ type: "event" } as Event); clock.tick(10000); fakeDocument.hasFocus = jest.fn().mockReturnValue(false); @@ -112,7 +112,7 @@ describe("UserActivity", function () { it("should not consider user passive after 3 mins", function () { fakeDocument.hasFocus = jest.fn().mockReturnValue(true); - userActivity.onUserActivity({}); + userActivity.onUserActivity({ type: "event" } as Event); clock.tick(3 * 60 * 1000); expect(userActivity.userActiveRecently()).toBe(false); @@ -121,11 +121,11 @@ describe("UserActivity", function () { it("should extend timer on activity", function () { fakeDocument.hasFocus = jest.fn().mockReturnValue(true); - userActivity.onUserActivity({}); + userActivity.onUserActivity({ type: "event" } as Event); clock.tick(1 * 60 * 1000); - userActivity.onUserActivity({}); + userActivity.onUserActivity({ type: "event" } as Event); clock.tick(1 * 60 * 1000); - userActivity.onUserActivity({}); + userActivity.onUserActivity({ type: "event" } as Event); clock.tick(1 * 60 * 1000); expect(userActivity.userActiveRecently()).toBe(true); diff --git a/test/accessibility/KeyboardShortcutUtils-test.ts b/test/accessibility/KeyboardShortcutUtils-test.ts index 842e90bac18..cca7b55085c 100644 --- a/test/accessibility/KeyboardShortcutUtils-test.ts +++ b/test/accessibility/KeyboardShortcutUtils-test.ts @@ -20,7 +20,7 @@ import { mockPlatformPeg, unmockPlatformPeg } from "../test-utils"; const PATH_TO_KEYBOARD_SHORTCUTS = "../../src/accessibility/KeyboardShortcuts"; const PATH_TO_KEYBOARD_SHORTCUT_UTILS = "../../src/accessibility/KeyboardShortcutUtils"; -const mockKeyboardShortcuts = (override) => { +const mockKeyboardShortcuts = (override: Record) => { jest.doMock(PATH_TO_KEYBOARD_SHORTCUTS, () => { const original = jest.requireActual(PATH_TO_KEYBOARD_SHORTCUTS); return { diff --git a/test/accessibility/RovingTabIndex-test.tsx b/test/accessibility/RovingTabIndex-test.tsx index 3200ae1317b..54625b7ef51 100644 --- a/test/accessibility/RovingTabIndex-test.tsx +++ b/test/accessibility/RovingTabIndex-test.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as React from "react"; +import React, { HTMLAttributes } from "react"; import { render } from "@testing-library/react"; import { @@ -26,8 +26,8 @@ import { useRovingTabIndex, } from "../../src/accessibility/RovingTabIndex"; -const Button = (props) => { - const [onFocus, isActive, ref] = useRovingTabIndex(); +const Button = (props: HTMLAttributes) => { + const [onFocus, isActive, ref] = useRovingTabIndex(); return