Comply with noImplicitAny (#9940)

* Stash noImplicitAny work

* Stash

* Fix imports

* Iterate

* Fix tests

* Delint

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

View file

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

View file

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

65
src/@types/opus-recorder.d.ts vendored Normal file
View file

@ -0,0 +1,65 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
declare module "opus-recorder/dist/recorder.min.js" {
export default class Recorder {
public static isRecordingSupported(): boolean;
public constructor(config: {
bufferLength?: number;
encoderApplication?: number;
encoderFrameSize?: number;
encoderPath?: string;
encoderSampleRate?: number;
encoderBitRate?: number;
maxFramesPerPage?: number;
mediaTrackConstraints?: boolean;
monitorGain?: number;
numberOfChannels?: number;
recordingGain?: number;
resampleQuality?: number;
streamPages?: boolean;
wavBitDepth?: number;
sourceNode?: MediaStreamAudioSourceNode;
encoderComplexity?: number;
});
public ondataavailable?(data: ArrayBuffer): void;
public readonly encodedSamplePosition: number;
public start(): Promise<void>;
public stop(): Promise<void>;
public close(): void;
}
}
declare module "opus-recorder/dist/encoderWorker.min.js" {
const path: string;
export default path;
}
declare module "opus-recorder/dist/waveWorker.min.js" {
const path: string;
export default path;
}
declare module "opus-recorder/dist/decoderWorker.min.js" {
const path: string;
export default path;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -48,7 +48,7 @@ interface IProps {
tabIndex?: number; 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: // work out the full set of urls to try to load. This is formed like so:
// imageUrls: [ props.url, ...props.urls ] // imageUrls: [ props.url, ...props.urls ]
@ -66,7 +66,7 @@ const calculateUrls = (url: string, urls: string[], lowBandwidth: boolean): stri
return Array.from(new Set(_urls)); 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 // 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 // use the cached lowBandwidth value from the room context if it exists
const roomContext = useContext(RoomContext); const roomContext = useContext(RoomContext);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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