From 76b82b4b2bd328983854c1d41ec975c4d1d5022f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 24 Feb 2023 15:28:40 +0000 Subject: [PATCH] Make more code conform to strict null checks (#10219 * Make more code conform to strict null checks * Fix types * Fix tests * Fix remaining test assertions * Iterate PR --- src/AddThreepid.ts | 10 +-- src/ContentMessages.ts | 2 +- src/LegacyCallHandler.tsx | 7 +- src/Lifecycle.ts | 4 +- src/MatrixClientPeg.ts | 6 +- src/MediaDeviceHandler.ts | 2 +- src/PosthogTrackers.ts | 8 +-- src/SdkConfig.ts | 1 - src/SlashCommands.tsx | 8 +-- src/autocomplete/Autocompleter.ts | 11 +-- src/autocomplete/EmojiProvider.tsx | 3 +- src/components/structures/EmbeddedPage.tsx | 9 ++- src/components/structures/FileDropTarget.tsx | 6 +- src/components/structures/FilePanel.tsx | 8 +-- src/components/structures/HomePage.tsx | 6 +- src/components/structures/InteractiveAuth.tsx | 11 ++- src/components/structures/LeftPanel.tsx | 11 +-- src/components/structures/ViewSource.tsx | 6 +- src/components/structures/auth/Login.tsx | 13 ++-- src/components/views/auth/CaptchaForm.tsx | 8 +-- src/components/views/auth/EmailField.tsx | 6 +- src/components/views/auth/PasswordLogin.tsx | 24 +++---- src/components/views/auth/Welcome.tsx | 2 +- src/components/views/dialogs/LogoutDialog.tsx | 15 ++-- .../dialogs/spotlight/SpotlightDialog.tsx | 5 +- .../views/elements/EditableTextContainer.tsx | 6 +- src/components/views/elements/SSOButtons.tsx | 2 +- .../views/elements/SearchWarning.tsx | 14 ++-- src/components/views/elements/Validation.tsx | 15 ++-- src/components/views/emojipicker/Category.tsx | 2 +- .../views/emojipicker/EmojiPicker.tsx | 8 +-- .../views/emojipicker/QuickReactions.tsx | 6 +- .../views/emojipicker/ReactionPicker.tsx | 10 +-- src/components/views/emojipicker/Search.tsx | 2 +- .../views/messages/DateSeparator.tsx | 6 +- .../views/rooms/MessageComposer.tsx | 14 ++-- .../StatelessNotificationBadge.tsx | 6 +- .../views/rooms/PinnedEventTile.tsx | 4 +- .../views/rooms/ReadReceiptGroup.tsx | 8 +-- .../views/rooms/ReadReceiptMarker.tsx | 8 +-- src/components/views/rooms/ReplyTile.tsx | 2 +- .../views/rooms/RoomContextDetails.tsx | 2 +- src/components/views/rooms/RoomInfoLine.tsx | 2 +- src/components/views/rooms/RoomList.tsx | 16 ++--- src/components/views/rooms/RoomListHeader.tsx | 32 +++++---- src/components/views/rooms/RoomPreviewBar.tsx | 48 ++++++------- src/components/views/rooms/RoomSublist.tsx | 30 ++++---- src/components/views/rooms/SearchBar.tsx | 4 +- .../views/rooms/SendMessageComposer.tsx | 11 ++- src/components/views/rooms/Stickerpicker.tsx | 37 ++++------ .../views/rooms/ThirdPartyMemberInfo.tsx | 14 ++-- src/components/views/rooms/ThreadSummary.tsx | 12 ++-- .../views/rooms/TopUnreadMessagesBar.tsx | 4 +- .../views/rooms/VoiceRecordComposerTile.tsx | 10 ++- .../views/rooms/WhoIsTypingTile.tsx | 2 +- .../rooms/wysiwyg_composer/hooks/utils.ts | 2 +- src/components/views/settings/BridgeTile.tsx | 10 +-- .../views/settings/ChangeDisplayName.tsx | 4 +- .../views/settings/ChangePassword.tsx | 18 ++--- .../views/settings/CrossSigningPanel.tsx | 8 +-- .../views/settings/DevicesPanel.tsx | 10 +-- .../views/settings/DevicesPanelEntry.tsx | 6 +- .../views/settings/EventIndexPanel.tsx | 11 +-- .../views/settings/FontScalingPanel.tsx | 9 +-- .../views/settings/JoinRuleSettings.tsx | 16 ++--- .../views/settings/Notifications.tsx | 6 +- .../views/settings/SecureBackupPanel.tsx | 26 +++---- src/components/views/settings/SetIdServer.tsx | 1 - .../views/settings/account/EmailAddresses.tsx | 4 +- .../settings/devices/CurrentDeviceSection.tsx | 4 +- .../views/settings/devices/deleteDevices.tsx | 2 +- .../settings/discovery/EmailAddresses.tsx | 6 +- .../views/settings/discovery/PhoneNumbers.tsx | 11 +-- .../tabs/user/GeneralUserSettingsTab.tsx | 31 ++++----- .../views/spaces/SpaceChildrenPicker.tsx | 3 +- .../views/spaces/SpaceCreateMenu.tsx | 14 ++-- .../payloads/JoinRoomErrorPayload.ts | 2 +- src/editor/commands.tsx | 2 +- src/editor/model.ts | 18 ++--- src/hooks/spotlight/useRecentSearches.ts | 3 +- src/hooks/useEventEmitter.ts | 2 +- src/hooks/usePublicRoomDirectory.ts | 2 +- src/hooks/useUserOnboardingContext.ts | 2 +- src/indexing/BaseEventIndexManager.ts | 2 +- src/indexing/EventIndex.ts | 13 +++- src/integrations/IntegrationManagers.ts | 4 +- src/linkify-matrix.ts | 8 +-- src/mjolnir/BanList.ts | 13 ++-- src/rageshake/submit-rageshake.ts | 11 ++- src/resizer/item.ts | 2 +- src/resizer/resizer.ts | 4 +- src/sentry.ts | 4 +- src/stores/ThreepidInviteStore.ts | 2 +- src/stores/right-panel/RightPanelStore.ts | 12 ++-- .../right-panel/RightPanelStoreIPanelState.ts | 12 ++-- src/stores/room-list/Interface.ts | 4 +- src/stores/room-list/RoomListLayoutStore.ts | 2 +- src/stores/room-list/RoomListStore.ts | 4 +- .../algorithms/tag-sorting/RecentAlgorithm.ts | 2 +- .../previews/LegacyCallAnswerEventPreview.ts | 2 +- .../previews/LegacyCallHangupEvent.ts | 2 +- .../previews/LegacyCallInviteEventPreview.ts | 2 +- src/stores/spaces/SpaceStore.ts | 46 ++++++------- src/stores/widgets/WidgetLayoutStore.ts | 12 ++-- src/toasts/DesktopNotificationsToast.ts | 2 +- src/utils/Reply.ts | 13 ++-- src/utils/SortMembers.ts | 10 +-- src/utils/arrays.ts | 4 ++ src/utils/beacon/getShareableLocation.ts | 2 +- src/utils/dm/startDm.ts | 2 +- src/utils/location/parseGeoUri.ts | 12 ++-- src/utils/permalinks/Permalinks.ts | 4 +- src/utils/stringOrderField.ts | 10 +-- src/utils/strings.ts | 6 +- .../useCurrentVoiceBroadcastRecording.ts | 2 +- test/accessibility/RovingTabIndex-test.tsx | 6 -- .../__snapshots__/EmbeddedPage-test.tsx.snap | 6 +- .../dialogs/InteractiveAuthDialog-test.tsx | 2 +- .../views/dialogs/SpotlightDialog-test.tsx | 2 +- .../views/dialogs/UserSettingsDialog-test.tsx | 4 +- .../views/location/LocationShareMenu-test.tsx | 4 +- .../location/LocationViewDialog-test.tsx | 2 +- .../views/rooms/ReadReceiptGroup-test.tsx | 4 +- .../views/rooms/SendMessageComposer-test.tsx | 6 +- .../rooms/VoiceRecordComposerTile-test.tsx | 11 +-- .../devices/CurrentDeviceSection-test.tsx | 1 + .../room/AdvancedRoomSettingsTab-test.tsx | 4 +- test/createRoom-test.ts | 40 ++++------- test/utils/location/parseGeoUri-test.ts | 68 +++++++++---------- test/utils/permalinks/Permalinks-test.ts | 54 +++++++-------- 130 files changed, 603 insertions(+), 603 deletions(-) diff --git a/src/AddThreepid.ts b/src/AddThreepid.ts index 5d8d947854..b1ee5795d0 100644 --- a/src/AddThreepid.ts +++ b/src/AddThreepid.ts @@ -49,7 +49,7 @@ export type Binding = { */ export default class AddThreepid { private sessionId: string; - private submitUrl: string; + private submitUrl?: string; private clientSecret: string; private bind: boolean; @@ -93,7 +93,7 @@ export default class AddThreepid { if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) { // For separate bind, request a token directly from the IS. const authClient = new IdentityAuthClient(); - const identityAccessToken = await authClient.getAccessToken(); + const identityAccessToken = (await authClient.getAccessToken()) ?? undefined; return MatrixClientPeg.get() .requestEmailToken(emailAddress, this.clientSecret, 1, undefined, identityAccessToken) .then( @@ -155,7 +155,7 @@ export default class AddThreepid { if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) { // For separate bind, request a token directly from the IS. const authClient = new IdentityAuthClient(); - const identityAccessToken = await authClient.getAccessToken(); + const identityAccessToken = (await authClient.getAccessToken()) ?? undefined; return MatrixClientPeg.get() .requestMsisdnToken(phoneCountry, phoneNumber, this.clientSecret, 1, undefined, identityAccessToken) .then( @@ -184,7 +184,7 @@ export default class AddThreepid { * with a "message" property which contains a human-readable message detailing why * the request failed. */ - public async checkEmailLinkClicked(): Promise<[boolean, IAuthData | Error | null]> { + public async checkEmailLinkClicked(): Promise<[boolean, IAuthData | Error | null] | undefined> { try { if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) { if (this.bind) { @@ -282,7 +282,7 @@ export default class AddThreepid { * with a "message" property which contains a human-readable message detailing why * the request failed. */ - public async haveMsisdnToken(msisdnToken: string): Promise { + public async haveMsisdnToken(msisdnToken: string): Promise { const authClient = new IdentityAuthClient(); const supportsSeparateAddAndBind = await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind(); diff --git a/src/ContentMessages.ts b/src/ContentMessages.ts index e05858bb16..382214a1b6 100644 --- a/src/ContentMessages.ts +++ b/src/ContentMessages.ts @@ -546,7 +546,7 @@ export default class ContentMessages { if (upload.cancelled) throw new UploadCanceledError(); const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name ? relation.event_id : null; - const response = await matrixClient.sendMessage(roomId, threadId, content); + const response = await matrixClient.sendMessage(roomId, threadId ?? null, content); if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) { sendRoundTripMetric(matrixClient, roomId, response.event_id); diff --git a/src/LegacyCallHandler.tsx b/src/LegacyCallHandler.tsx index 34fffffc50..b5358b4930 100644 --- a/src/LegacyCallHandler.tsx +++ b/src/LegacyCallHandler.tsx @@ -1024,13 +1024,12 @@ export default class LegacyCallHandler extends EventEmitter { } public answerCall(roomId: string): void { - const call = this.calls.get(roomId); - - this.stopRingingIfPossible(call.callId); - // no call to answer if (!this.calls.has(roomId)) return; + const call = this.calls.get(roomId)!; + this.stopRingingIfPossible(call.callId); + if (this.getAllActiveCalls().length > 1) { Modal.createDialog(ErrorDialog, { title: _t("Too Many Calls"), diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index b28a7f8038..04b20adc8e 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -287,7 +287,7 @@ export function handleInvalidStoreError(e: InvalidStoreError): Promise { return MatrixClientPeg.get().store.deleteAllData(); }) .then(() => { - PlatformPeg.get().reload(); + PlatformPeg.get()?.reload(); }); } } @@ -519,7 +519,7 @@ export async function setLoggedIn(credentials: IMatrixClientCreds): Promise The available media devices */ - public static async getDevices(): Promise { + public static async getDevices(): Promise { try { const devices = await navigator.mediaDevices.enumerateDevices(); const output: Record = { diff --git a/src/PosthogTrackers.ts b/src/PosthogTrackers.ts index a82b78c1dd..c03a20216c 100644 --- a/src/PosthogTrackers.ts +++ b/src/PosthogTrackers.ts @@ -54,8 +54,8 @@ export default class PosthogTrackers { } private view: Views = Views.LOADING; - private pageType?: PageType = null; - private override?: ScreenName = null; + private pageType?: PageType; + private override?: ScreenName; public trackPageChange(view: Views, pageType: PageType | undefined, durationMs: number): void { this.view = view; @@ -66,7 +66,7 @@ export default class PosthogTrackers { private trackPage(durationMs?: number): void { const screenName = - this.view === Views.LOGGED_IN ? loggedInPageTypeMap[this.pageType] : notLoggedInMap[this.view]; + this.view === Views.LOGGED_IN ? loggedInPageTypeMap[this.pageType!] : notLoggedInMap[this.view]; PosthogAnalytics.instance.trackEvent({ eventName: "$pageview", $current_url: screenName, @@ -85,7 +85,7 @@ export default class PosthogTrackers { public clearOverride(screenName: ScreenName): void { if (screenName !== this.override) return; - this.override = null; + this.override = undefined; this.trackPage(); } diff --git a/src/SdkConfig.ts b/src/SdkConfig.ts index 5aaab6e2f4..fab5cb29c6 100644 --- a/src/SdkConfig.ts +++ b/src/SdkConfig.ts @@ -26,7 +26,6 @@ export const DEFAULTS: IConfigOptions = { brand: "Element", integrations_ui_url: "https://scalar.vector.im/", integrations_rest_url: "https://scalar.vector.im/api", - bug_report_endpoint_url: null, uisi_autorageshake_app: "element-auto-uisi", jitsi: { diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 66f673445d..6628b95fc1 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -198,7 +198,7 @@ function reject(error?: any): RunResult { return { error }; } -function success(promise?: Promise): RunResult { +function success(promise: Promise = Promise.resolve()): RunResult { return { promise }; } @@ -221,7 +221,7 @@ export const Commands = [ command: "spoiler", args: "", description: _td("Sends the given message as a spoiler"), - runFn: function (roomId, message) { + runFn: function (roomId, message = "") { return successSync(ContentHelpers.makeHtmlMessage(message, `${message}`)); }, category: CommandCategories.messages, @@ -282,7 +282,7 @@ export const Commands = [ command: "plain", args: "", description: _td("Sends a message as plain text, without interpreting it as markdown"), - runFn: function (roomId, messages) { + runFn: function (roomId, messages = "") { return successSync(ContentHelpers.makeTextMessage(messages)); }, category: CommandCategories.messages, @@ -291,7 +291,7 @@ export const Commands = [ command: "html", args: "", description: _td("Sends a message as html, without interpreting it as markdown"), - runFn: function (roomId, messages) { + runFn: function (roomId, messages = "") { return successSync(ContentHelpers.makeHtmlMessage(messages, messages)); }, category: CommandCategories.messages, diff --git a/src/autocomplete/Autocompleter.ts b/src/autocomplete/Autocompleter.ts index c769faf155..73933a23a9 100644 --- a/src/autocomplete/Autocompleter.ts +++ b/src/autocomplete/Autocompleter.ts @@ -27,6 +27,7 @@ import { timeout } from "../utils/promise"; import AutocompleteProvider, { ICommand } from "./AutocompleteProvider"; import SpaceProvider from "./SpaceProvider"; import { TimelineRenderingType } from "../contexts/RoomContext"; +import { filterBoolean } from "../utils/arrays"; export interface ISelectionRange { beginning?: boolean; // whether the selection is in the first block of the editor or not @@ -55,7 +56,7 @@ const PROVIDER_COMPLETION_TIMEOUT = 3000; export interface IProviderCompletions { completions: ICompletion[]; provider: AutocompleteProvider; - command: ICommand; + command: Partial; } export default class Autocompleter { @@ -98,8 +99,8 @@ export default class Autocompleter { ); // map then filter to maintain the index for the map-operation, for this.providers to line up - return completionsList - .map((completions, i) => { + return filterBoolean( + completionsList.map((completions, i) => { if (!completions || !completions.length) return; return { @@ -112,7 +113,7 @@ export default class Autocompleter { */ command: this.providers[i].getCurrentCommand(query, selection, force), }; - }) - .filter(Boolean) as IProviderCompletions[]; + }), + ); } } diff --git a/src/autocomplete/EmojiProvider.tsx b/src/autocomplete/EmojiProvider.tsx index 9c0acff4cd..b434eb6e57 100644 --- a/src/autocomplete/EmojiProvider.tsx +++ b/src/autocomplete/EmojiProvider.tsx @@ -32,6 +32,7 @@ import SettingsStore from "../settings/SettingsStore"; import { EMOJI, IEmoji, getEmojiFromUnicode } from "../emoji"; import { TimelineRenderingType } from "../contexts/RoomContext"; import * as recent from "../emojipicker/recent"; +import { filterBoolean } from "../utils/arrays"; const LIMIT = 20; @@ -94,7 +95,7 @@ export default class EmojiProvider extends AutocompleteProvider { shouldMatchWordsOnly: true, }); - this.recentlyUsed = Array.from(new Set(recent.get().map(getEmojiFromUnicode).filter(Boolean))) as IEmoji[]; + this.recentlyUsed = Array.from(new Set(filterBoolean(recent.get().map(getEmojiFromUnicode)))); } public async getCompletions( diff --git a/src/components/structures/EmbeddedPage.tsx b/src/components/structures/EmbeddedPage.tsx index cbd7d885c7..7ee30cb3ec 100644 --- a/src/components/structures/EmbeddedPage.tsx +++ b/src/components/structures/EmbeddedPage.tsx @@ -46,7 +46,7 @@ interface IState { export default class EmbeddedPage extends React.PureComponent { public static contextType = MatrixClientContext; private unmounted = false; - private dispatcherRef: string = null; + private dispatcherRef: string | null = null; public constructor(props: IProps, context: typeof MatrixClientContext) { super(props, context); @@ -64,7 +64,7 @@ export default class EmbeddedPage extends React.PureComponent { let res: Response; try { - res = await fetch(this.props.url, { method: "GET" }); + res = await fetch(this.props.url!, { method: "GET" }); } catch (err) { if (this.unmounted) return; logger.warn(`Error loading page: ${err}`); @@ -84,7 +84,7 @@ export default class EmbeddedPage extends React.PureComponent { if (this.props.replaceMap) { Object.keys(this.props.replaceMap).forEach((key) => { - body = body.split(key).join(this.props.replaceMap[key]); + body = body.split(key).join(this.props.replaceMap![key]); }); } @@ -123,8 +123,7 @@ export default class EmbeddedPage extends React.PureComponent { const client = this.context || MatrixClientPeg.get(); const isGuest = client ? client.isGuest() : true; const className = this.props.className; - const classes = classnames({ - [className]: true, + const classes = classnames(className, { [`${className}_guest`]: isGuest, [`${className}_loggedIn`]: !!client, }); diff --git a/src/components/structures/FileDropTarget.tsx b/src/components/structures/FileDropTarget.tsx index e8a8fa5e28..2f6bc13d79 100644 --- a/src/components/structures/FileDropTarget.tsx +++ b/src/components/structures/FileDropTarget.tsx @@ -40,6 +40,7 @@ const FileDropTarget: React.FC = ({ parent, onFileDrop }) => { const onDragEnter = (ev: DragEvent): void => { ev.stopPropagation(); ev.preventDefault(); + if (!ev.dataTransfer) return; setState((state) => ({ // We always increment the counter no matter the types, because dragging is @@ -49,7 +50,8 @@ const FileDropTarget: React.FC = ({ parent, onFileDrop }) => { // https://docs.w3cub.com/dom/datatransfer/types // https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#file dragging: - ev.dataTransfer.types.includes("Files") || ev.dataTransfer.types.includes("application/x-moz-file") + ev.dataTransfer!.types.includes("Files") || + ev.dataTransfer!.types.includes("application/x-moz-file") ? true : state.dragging, })); @@ -68,6 +70,7 @@ const FileDropTarget: React.FC = ({ parent, onFileDrop }) => { const onDragOver = (ev: DragEvent): void => { ev.stopPropagation(); ev.preventDefault(); + if (!ev.dataTransfer) return; ev.dataTransfer.dropEffect = "none"; @@ -82,6 +85,7 @@ const FileDropTarget: React.FC = ({ parent, onFileDrop }) => { const onDrop = (ev: DragEvent): void => { ev.stopPropagation(); ev.preventDefault(); + if (!ev.dataTransfer) return; onFileDrop(ev.dataTransfer); setState((state) => ({ diff --git a/src/components/structures/FilePanel.tsx b/src/components/structures/FilePanel.tsx index 1b53b7d293..fa3ce1a754 100644 --- a/src/components/structures/FilePanel.tsx +++ b/src/components/structures/FilePanel.tsx @@ -66,7 +66,7 @@ class FilePanel extends React.Component { private onRoomTimeline = ( ev: MatrixEvent, - room: Room | null, + room: Room | undefined, toStartOfTimeline: boolean, removed: boolean, data: IRoomTimelineData, @@ -78,7 +78,7 @@ class FilePanel extends React.Component { client.decryptEventIfNeeded(ev); if (ev.isBeingDecrypted()) { - this.decryptingEvents.add(ev.getId()); + this.decryptingEvents.add(ev.getId()!); } else { this.addEncryptedLiveEvent(ev); } @@ -86,7 +86,7 @@ class FilePanel extends React.Component { private onEventDecrypted = (ev: MatrixEvent, err?: any): void => { if (ev.getRoomId() !== this.props.roomId) return; - const eventId = ev.getId(); + const eventId = ev.getId()!; if (!this.decryptingEvents.delete(eventId)) return; if (err) return; @@ -103,7 +103,7 @@ class FilePanel extends React.Component { return; } - if (!this.state.timelineSet.eventIdToTimeline(ev.getId())) { + if (!this.state.timelineSet.eventIdToTimeline(ev.getId()!)) { this.state.timelineSet.addEventToTimeline(ev, timeline, false); } } diff --git a/src/components/structures/HomePage.tsx b/src/components/structures/HomePage.tsx index 13fc132516..bb2fbc068d 100644 --- a/src/components/structures/HomePage.tsx +++ b/src/components/structures/HomePage.tsx @@ -56,15 +56,15 @@ const getOwnProfile = ( userId: string, ): { displayName: string; - avatarUrl: string; + avatarUrl?: string; } => ({ displayName: OwnProfileStore.instance.displayName || userId, - avatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(AVATAR_SIZE), + avatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(AVATAR_SIZE) ?? undefined, }); const UserWelcomeTop: React.FC = () => { const cli = useContext(MatrixClientContext); - const userId = cli.getUserId(); + const userId = cli.getUserId()!; const [ownProfile, setOwnProfile] = useState(getOwnProfile(userId)); useEventEmitter(OwnProfileStore.instance, UPDATE_EVENT, () => { setOwnProfile(getOwnProfile(userId)); diff --git a/src/components/structures/InteractiveAuth.tsx b/src/components/structures/InteractiveAuth.tsx index fd83f0aea2..77113052f1 100644 --- a/src/components/structures/InteractiveAuth.tsx +++ b/src/components/structures/InteractiveAuth.tsx @@ -33,7 +33,7 @@ export const ERROR_USER_CANCELLED = new Error("User cancelled auth session"); type InteractiveAuthCallbackSuccess = ( success: true, - response: IAuthData, + response?: IAuthData, extra?: { emailSid?: string; clientSecret?: string }, ) => void; type InteractiveAuthCallbackFailure = (success: false, response: IAuthData | Error) => void; @@ -94,7 +94,7 @@ interface IState { export default class InteractiveAuthComponent extends React.Component { private readonly authLogic: InteractiveAuth; - private readonly intervalId: number = null; + private readonly intervalId: number | null = null; private readonly stageComponent = createRef(); private unmounted = false; @@ -103,10 +103,7 @@ export default class InteractiveAuthComponent extends React.Component { } >(); - let lastTopHeader; - let firstBottomHeader; + let lastTopHeader: HTMLDivElement | undefined; + let firstBottomHeader: HTMLDivElement | undefined; for (const sublist of sublists) { const header = sublist.querySelector(".mx_RoomSublist_stickable"); + if (!header) continue; // this should never occur header.style.removeProperty("display"); // always clear display:none first // When an element is <=40% off screen, make it take over @@ -196,7 +197,7 @@ export default class LeftPanel extends React.Component { // cause a no-op update, as adding/removing properties that are/aren't there cause // layout updates. for (const header of targetStyles.keys()) { - const style = targetStyles.get(header); + const style = targetStyles.get(header)!; if (style.makeInvisible) { // we will have already removed the 'display: none', so add it back. @@ -324,7 +325,7 @@ export default class LeftPanel extends React.Component { } private renderSearchDialExplore(): React.ReactNode { - let dialPadButton = null; + let dialPadButton: JSX.Element | undefined; // If we have dialer support, show a button to bring up the dial pad // to start a new call @@ -338,7 +339,7 @@ export default class LeftPanel extends React.Component { ); } - let rightButton: JSX.Element; + let rightButton: JSX.Element | undefined; if (this.state.showBreadcrumbs === BreadcrumbsMode.Labs) { rightButton = ; } else if (this.state.activeSpace === MetaSpace.Home && shouldShowComponent(UIComponent.ExploreRooms)) { diff --git a/src/components/structures/ViewSource.tsx b/src/components/structures/ViewSource.tsx index faf445cef5..c9191a23a8 100644 --- a/src/components/structures/ViewSource.tsx +++ b/src/components/structures/ViewSource.tsx @@ -139,15 +139,15 @@ export default class ViewSource extends React.Component { private canSendStateEvent(mxEvent: MatrixEvent): boolean { const cli = MatrixClientPeg.get(); const room = cli.getRoom(mxEvent.getRoomId()); - return room.currentState.mayClientSendStateEvent(mxEvent.getType(), cli); + return !!room?.currentState.mayClientSendStateEvent(mxEvent.getType(), cli); } public render(): React.ReactNode { const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit const isEditing = this.state.isEditing; - const roomId = mxEvent.getRoomId(); - const eventId = mxEvent.getId(); + const roomId = mxEvent.getRoomId()!; + const eventId = mxEvent.getId()!; const canEdit = mxEvent.isState() ? this.canSendStateEvent(mxEvent) : canEditContent(this.props.mxEvent); return ( diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index 243d56cee1..c2c7a0d913 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -39,6 +39,7 @@ import AuthBody from "../../views/auth/AuthBody"; import AuthHeader from "../../views/auth/AuthHeader"; import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton"; import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig"; +import { filterBoolean } from "../../../utils/arrays"; // These are used in several places, and come from the js-sdk's autodiscovery // stuff. We define them here so that they'll be picked up by i18n. @@ -120,15 +121,11 @@ export default class LoginComponent extends React.PureComponent this.state = { busy: false, - busyLoggingIn: null, errorText: null, loginIncorrect: false, canTryLogin: true, - flows: null, - username: props.defaultUsername ? props.defaultUsername : "", - phoneCountry: null, phoneNumber: "", serverIsAlive: true, @@ -167,7 +164,7 @@ export default class LoginComponent extends React.PureComponent } } - public isBusy = (): boolean => this.state.busy || this.props.busy; + public isBusy = (): boolean => !!this.state.busy || !!this.props.busy; public onPasswordLogin: OnPasswordLogin = async ( username: string | undefined, @@ -349,7 +346,7 @@ export default class LoginComponent extends React.PureComponent ev.preventDefault(); ev.stopPropagation(); const ssoKind = ssoFlow.type === "m.login.sso" ? "sso" : "cas"; - PlatformPeg.get().startSingleSignOn( + PlatformPeg.get()?.startSingleSignOn( this.loginLogic.createTemporaryClient(), ssoKind, this.props.fragmentAfterLogin, @@ -511,13 +508,13 @@ export default class LoginComponent extends React.PureComponent return errorText; } - public renderLoginComponentForFlows(): JSX.Element { + public renderLoginComponentForFlows(): ReactNode { if (!this.state.flows) return null; // this is the ideal order we want to show the flows in const order = ["m.login.password", "m.login.sso"]; - const flows = order.map((type) => this.state.flows.find((flow) => flow.type === type)).filter(Boolean); + const flows = filterBoolean(order.map((type) => this.state.flows.find((flow) => flow.type === type))); return ( {flows.map((flow) => { diff --git a/src/components/views/auth/CaptchaForm.tsx b/src/components/views/auth/CaptchaForm.tsx index 9119743378..651921b233 100644 --- a/src/components/views/auth/CaptchaForm.tsx +++ b/src/components/views/auth/CaptchaForm.tsx @@ -65,7 +65,7 @@ export default class CaptchaForm extends React.Component{this.state.errorText}; } diff --git a/src/components/views/auth/EmailField.tsx b/src/components/views/auth/EmailField.tsx index 2731503e38..0426c08f86 100644 --- a/src/components/views/auth/EmailField.tsx +++ b/src/components/views/auth/EmailField.tsx @@ -50,12 +50,12 @@ class EmailField extends PureComponent { { key: "required", test: ({ value, allowEmpty }) => allowEmpty || !!value, - invalid: () => _t(this.props.labelRequired), + invalid: () => _t(this.props.labelRequired!), }, { key: "email", test: ({ value }) => !value || Email.looksValid(value), - invalid: () => _t(this.props.labelInvalid), + invalid: () => _t(this.props.labelInvalid!), }, ], }); @@ -80,7 +80,7 @@ class EmailField extends PureComponent { id={this.props.id} ref={this.props.fieldRef} type="text" - label={_t(this.props.label)} + label={_t(this.props.label!)} value={this.props.value} autoFocus={this.props.autoFocus} onChange={this.props.onChange} diff --git a/src/components/views/auth/PasswordLogin.tsx b/src/components/views/auth/PasswordLogin.tsx index bb630b5fc2..727505551a 100644 --- a/src/components/views/auth/PasswordLogin.tsx +++ b/src/components/views/auth/PasswordLogin.tsx @@ -36,7 +36,7 @@ interface IProps { phoneNumber: string; serverConfig: ValidatedServerConfig; - loginIncorrect?: boolean; + loginIncorrect: boolean; disableSubmit?: boolean; busy?: boolean; @@ -67,9 +67,9 @@ const enum LoginField { * The email/username/phone fields are fully-controlled, the password field is not. */ export default class PasswordLogin extends React.PureComponent { - private [LoginField.Email]: Field; - private [LoginField.Phone]: Field; - private [LoginField.MatrixId]: Field; + private [LoginField.Email]: Field | null; + private [LoginField.Phone]: Field | null; + private [LoginField.MatrixId]: Field | null; public static defaultProps = { onUsernameChanged: function () {}, @@ -93,7 +93,7 @@ export default class PasswordLogin extends React.PureComponent { private onForgotPasswordClick = (ev: ButtonEvent): void => { ev.preventDefault(); ev.stopPropagation(); - this.props.onForgotPasswordClick(); + this.props.onForgotPasswordClick?.(); }; private onSubmitForm = async (ev: SyntheticEvent): Promise => { @@ -116,25 +116,25 @@ export default class PasswordLogin extends React.PureComponent { }; private onUsernameChanged = (ev: React.ChangeEvent): void => { - this.props.onUsernameChanged(ev.target.value); + this.props.onUsernameChanged?.(ev.target.value); }; private onUsernameBlur = (ev: React.FocusEvent): void => { - this.props.onUsernameBlur(ev.target.value); + this.props.onUsernameBlur?.(ev.target.value); }; private onLoginTypeChange = (ev: React.ChangeEvent): void => { const loginType = ev.target.value as IState["loginType"]; this.setState({ loginType }); - this.props.onUsernameChanged(""); // Reset because email and username use the same state + this.props.onUsernameChanged?.(""); // Reset because email and username use the same state }; private onPhoneCountryChanged = (country: PhoneNumberCountryDefinition): void => { - this.props.onPhoneCountryChanged(country.iso2); + this.props.onPhoneCountryChanged?.(country.iso2); }; private onPhoneNumberChanged = (ev: React.ChangeEvent): void => { - this.props.onPhoneNumberChanged(ev.target.value); + this.props.onPhoneNumberChanged?.(ev.target.value); }; private onPasswordChanged = (ev: React.ChangeEvent): void => { @@ -199,7 +199,7 @@ export default class PasswordLogin extends React.PureComponent { return null; } - private markFieldValid(fieldID: LoginField, valid: boolean): void { + private markFieldValid(fieldID: LoginField, valid?: boolean): void { const { fieldValid } = this.state; fieldValid[fieldID] = valid; this.setState({ @@ -368,7 +368,7 @@ export default class PasswordLogin extends React.PureComponent { } public render(): React.ReactNode { - let forgotPasswordJsx; + let forgotPasswordJsx: JSX.Element | undefined; if (this.props.onForgotPasswordClick) { forgotPasswordJsx = ( diff --git a/src/components/views/auth/Welcome.tsx b/src/components/views/auth/Welcome.tsx index adcbdad25a..003a6cc509 100644 --- a/src/components/views/auth/Welcome.tsx +++ b/src/components/views/auth/Welcome.tsx @@ -34,7 +34,7 @@ interface IProps {} export default class Welcome extends React.PureComponent { public render(): React.ReactNode { const pagesConfig = SdkConfig.getObject("embedded_pages"); - let pageUrl = null; + let pageUrl!: string; if (pagesConfig) { pageUrl = pagesConfig.get("welcome_url"); } diff --git a/src/components/views/dialogs/LogoutDialog.tsx b/src/components/views/dialogs/LogoutDialog.tsx index af3fb65f6f..84ecc97863 100644 --- a/src/components/views/dialogs/LogoutDialog.tsx +++ b/src/components/views/dialogs/LogoutDialog.tsx @@ -36,7 +36,7 @@ interface IProps { interface IState { shouldLoadBackupStatus: boolean; loading: boolean; - backupInfo: IKeyBackupInfo; + backupInfo: IKeyBackupInfo | null; error?: string; } @@ -55,7 +55,6 @@ export default class LogoutDialog extends React.Component { shouldLoadBackupStatus: shouldLoadBackupStatus, loading: shouldLoadBackupStatus, backupInfo: null, - error: null, }; if (shouldLoadBackupStatus) { @@ -103,14 +102,20 @@ export default class LogoutDialog extends React.Component { // A key backup exists for this account, but the creating device is not // verified, so restore the backup which will give us the keys from it and // allow us to trust it (ie. upload keys to it) - Modal.createDialog(RestoreKeyBackupDialog, null, null, /* priority = */ false, /* static = */ true); + Modal.createDialog( + RestoreKeyBackupDialog, + undefined, + undefined, + /* priority = */ false, + /* static = */ true, + ); } else { Modal.createDialogAsync( import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog") as unknown as Promise< ComponentType<{}> >, - null, - null, + undefined, + undefined, /* priority = */ false, /* static = */ true, ); diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx index d8911876f5..24a0fb7b52 100644 --- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx +++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx @@ -93,6 +93,7 @@ import { isLocalRoom } from "../../../../utils/localRoom/isLocalRoom"; import { shouldShowFeedback } from "../../../../utils/Feedback"; import RoomAvatar from "../../avatars/RoomAvatar"; import { useFeatureEnabled } from "../../../../hooks/useSettings"; +import { filterBoolean } from "../../../../utils/arrays"; const MAX_RECENT_SEARCHES = 10; const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons @@ -173,13 +174,13 @@ const toPublicRoomResult = (publicRoom: IPublicRoomsChunkRoom): IPublicRoomResul publicRoom, section: Section.PublicRooms, filter: [Filter.PublicRooms], - query: [ + query: filterBoolean([ publicRoom.room_id.toLowerCase(), publicRoom.canonical_alias?.toLowerCase(), publicRoom.name?.toLowerCase(), sanitizeHtml(publicRoom.topic?.toLowerCase() ?? "", { allowedTags: [] }), ...(publicRoom.aliases?.map((it) => it.toLowerCase()) || []), - ].filter(Boolean) as string[], + ]), }); const toRoomResult = (room: Room): IRoomResult => { diff --git a/src/components/views/elements/EditableTextContainer.tsx b/src/components/views/elements/EditableTextContainer.tsx index 6e3132e226..e2f4298062 100644 --- a/src/components/views/elements/EditableTextContainer.tsx +++ b/src/components/views/elements/EditableTextContainer.tsx @@ -32,7 +32,7 @@ interface IProps { /* callback to update the value. Called with a single argument: the new * value. */ - onSubmit?: (value: string) => Promise<{} | void>; + onSubmit: (value: string) => Promise<{} | void>; /* should the input submit when focus is lost? */ blurToSubmit?: boolean; @@ -40,7 +40,7 @@ interface IProps { interface IState { busy: boolean; - errorString: string; + errorString: string | null; value: string; } @@ -72,7 +72,7 @@ export default class EditableTextContainer extends React.Component = ({ brandClass = `mx_SSOButton_brand_${brandName}`; icon = {brandName}; } else if (typeof idp?.icon === "string" && idp.icon.startsWith("mxc://")) { - const src = mediaFromMxc(idp.icon, matrixClient).getSquareThumbnailHttp(24); + const src = mediaFromMxc(idp.icon, matrixClient).getSquareThumbnailHttp(24) ?? undefined; icon = {idp.name}; } diff --git a/src/components/views/elements/SearchWarning.tsx b/src/components/views/elements/SearchWarning.tsx index fec5eee37f..14ffcbd510 100644 --- a/src/components/views/elements/SearchWarning.tsx +++ b/src/components/views/elements/SearchWarning.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactNode } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import EventIndexPeg from "../../../indexing/EventIndexPeg"; @@ -31,13 +31,13 @@ export enum WarningKind { } interface IProps { - isRoomEncrypted: boolean; + isRoomEncrypted?: boolean; kind: WarningKind; } export default function SearchWarning({ isRoomEncrypted, kind }: IProps): JSX.Element { - if (!isRoomEncrypted) return null; - if (EventIndexPeg.get()) return null; + if (!isRoomEncrypted) return <>; + if (EventIndexPeg.get()) return <>; if (EventIndexPeg.error) { return ( @@ -69,8 +69,8 @@ export default function SearchWarning({ isRoomEncrypted, kind }: IProps): JSX.El const brand = SdkConfig.get("brand"); const desktopBuilds = SdkConfig.getObject("desktop_builds"); - let text = null; - let logo = null; + let text: ReactNode | undefined; + let logo: JSX.Element | undefined; if (desktopBuilds.get("available")) { logo = ; const buildUrl = desktopBuilds.get("url"); @@ -116,7 +116,7 @@ export default function SearchWarning({ isRoomEncrypted, kind }: IProps): JSX.El // for safety if (!text) { logger.warn("Unknown desktop builds warning kind: ", kind); - return null; + return <>; } return ( diff --git a/src/components/views/elements/Validation.tsx b/src/components/views/elements/Validation.tsx index e9e98ca615..0af6417145 100644 --- a/src/components/views/elements/Validation.tsx +++ b/src/components/views/elements/Validation.tsx @@ -26,7 +26,7 @@ interface IResult { text: string; } -interface IRule { +interface IRule { key: string; final?: boolean; skip?(this: T, data: Data, derivedData: D): boolean; @@ -90,14 +90,12 @@ export default function withValidation({ { value, focused, allowEmpty = true }: IFieldState, ): Promise { if (!value && allowEmpty) { - return { - valid: null, - feedback: null, - }; + return {}; } const data = { value, allowEmpty }; - const derivedData: D | undefined = deriveData ? await deriveData.call(this, data) : undefined; + // We know that if deriveData is set then D will not be undefined + const derivedData: D = (await deriveData?.call(this, data)) as D; const results: IResult[] = []; let valid = true; @@ -149,10 +147,7 @@ export default function withValidation({ // Hide feedback when not focused if (!focused) { - return { - valid, - feedback: null, - }; + return { valid }; } let details; diff --git a/src/components/views/emojipicker/Category.tsx b/src/components/views/emojipicker/Category.tsx index 4efeb77839..f4ffce911b 100644 --- a/src/components/views/emojipicker/Category.tsx +++ b/src/components/views/emojipicker/Category.tsx @@ -38,7 +38,7 @@ interface IProps { id: string; name: string; emojis: IEmoji[]; - selectedEmojis: Set; + selectedEmojis?: Set; heightBefore: number; viewportHeight: number; scrollTop: number; diff --git a/src/components/views/emojipicker/EmojiPicker.tsx b/src/components/views/emojipicker/EmojiPicker.tsx index 2e4a6dc0de..1d8c233696 100644 --- a/src/components/views/emojipicker/EmojiPicker.tsx +++ b/src/components/views/emojipicker/EmojiPicker.tsx @@ -26,6 +26,7 @@ import Search from "./Search"; import Preview from "./Preview"; import QuickReactions from "./QuickReactions"; import Category, { ICategory, CategoryKey } from "./Category"; +import { filterBoolean } from "../../../utils/arrays"; export const CATEGORY_HEADER_HEIGHT = 20; export const EMOJI_HEIGHT = 35; @@ -62,13 +63,12 @@ class EmojiPicker extends React.Component { this.state = { filter: "", - previewEmoji: null, scrollTop: 0, viewportHeight: 280, }; // Convert recent emoji characters to emoji data, removing unknowns and duplicates - this.recentlyUsed = Array.from(new Set(recent.get().map(getEmojiFromUnicode).filter(Boolean))); + this.recentlyUsed = Array.from(new Set(filterBoolean(recent.get().map(getEmojiFromUnicode)))); this.memoizedDataByCategory = { recent: this.recentlyUsed, ...DATA_BY_CATEGORY, @@ -230,9 +230,9 @@ class EmojiPicker extends React.Component { }); }; - private onHoverEmojiEnd = (emoji: IEmoji): void => { + private onHoverEmojiEnd = (): void => { this.setState({ - previewEmoji: null, + previewEmoji: undefined, }); }; diff --git a/src/components/views/emojipicker/QuickReactions.tsx b/src/components/views/emojipicker/QuickReactions.tsx index 05460d2b70..6b14906948 100644 --- a/src/components/views/emojipicker/QuickReactions.tsx +++ b/src/components/views/emojipicker/QuickReactions.tsx @@ -42,9 +42,7 @@ interface IState { class QuickReactions extends React.Component { public constructor(props: IProps) { super(props); - this.state = { - hover: null, - }; + this.state = {}; } private onMouseEnter = (emoji: IEmoji): void => { @@ -55,7 +53,7 @@ class QuickReactions extends React.Component { private onMouseLeave = (): void => { this.setState({ - hover: null, + hover: undefined, }); }; diff --git a/src/components/views/emojipicker/ReactionPicker.tsx b/src/components/views/emojipicker/ReactionPicker.tsx index 6dceaf2e40..207767ecf9 100644 --- a/src/components/views/emojipicker/ReactionPicker.tsx +++ b/src/components/views/emojipicker/ReactionPicker.tsx @@ -77,8 +77,8 @@ class ReactionPicker extends React.Component { if (!this.props.reactions) { return {}; } - const userId = MatrixClientPeg.get().getUserId(); - const myAnnotations = this.props.reactions.getAnnotationsBySender()[userId] || new Set(); + const userId = MatrixClientPeg.get().getUserId()!; + const myAnnotations = this.props.reactions.getAnnotationsBySender()?.[userId] ?? new Set(); return Object.fromEntries( [...myAnnotations] .filter((event) => !event.isRedacted()) @@ -97,9 +97,9 @@ class ReactionPicker extends React.Component { this.props.onFinished(); const myReactions = this.getReactions(); if (myReactions.hasOwnProperty(reaction)) { - if (this.props.mxEvent.isRedacted() || !this.context.canSelfRedact) return; + if (this.props.mxEvent.isRedacted() || !this.context.canSelfRedact) return false; - MatrixClientPeg.get().redactEvent(this.props.mxEvent.getRoomId(), myReactions[reaction]); + MatrixClientPeg.get().redactEvent(this.props.mxEvent.getRoomId()!, myReactions[reaction]); dis.dispatch({ action: Action.FocusAComposer, context: this.context.timelineRenderingType, @@ -107,7 +107,7 @@ class ReactionPicker extends React.Component { // Tell the emoji picker not to bump this in the more frequently used list. return false; } else { - MatrixClientPeg.get().sendEvent(this.props.mxEvent.getRoomId(), EventType.Reaction, { + MatrixClientPeg.get().sendEvent(this.props.mxEvent.getRoomId()!, EventType.Reaction, { "m.relates_to": { rel_type: RelationType.Annotation, event_id: this.props.mxEvent.getId(), diff --git a/src/components/views/emojipicker/Search.tsx b/src/components/views/emojipicker/Search.tsx index ae0636aeec..edd6b2c4fc 100644 --- a/src/components/views/emojipicker/Search.tsx +++ b/src/components/views/emojipicker/Search.tsx @@ -32,7 +32,7 @@ class Search extends React.PureComponent { public componentDidMount(): void { // For some reason, neither the autoFocus nor just calling focus() here worked, so here's a window.setTimeout - window.setTimeout(() => this.inputRef.current.focus(), 0); + window.setTimeout(() => this.inputRef.current?.focus(), 0); } private onKeyDown = (ev: React.KeyboardEvent): void => { diff --git a/src/components/views/messages/DateSeparator.tsx b/src/components/views/messages/DateSeparator.tsx index 54d8033dd0..b9cb345680 100644 --- a/src/components/views/messages/DateSeparator.tsx +++ b/src/components/views/messages/DateSeparator.tsx @@ -73,7 +73,7 @@ export default class DateSeparator extends React.Component { } public componentWillUnmount(): void { - SettingsStore.unwatchSetting(this.settingWatcherRef); + if (this.settingWatcherRef) SettingsStore.unwatchSetting(this.settingWatcherRef); } private onContextMenuOpenClick = (e: React.MouseEvent): void => { @@ -89,7 +89,7 @@ export default class DateSeparator extends React.Component { private closeMenu = (): void => { this.setState({ - contextMenuPosition: null, + contextMenuPosition: undefined, }); }; @@ -181,7 +181,7 @@ export default class DateSeparator extends React.Component { }; private renderJumpToDateMenu(): React.ReactElement { - let contextMenu: JSX.Element; + let contextMenu: JSX.Element | undefined; if (this.state.contextMenuPosition) { contextMenu = ( { private waitForOwnMember(): void { // If we have the member already, do that - const me = this.props.room.getMember(MatrixClientPeg.get().getUserId()); + const me = this.props.room.getMember(MatrixClientPeg.get().getUserId()!); if (me) { this.setState({ me }); return; @@ -250,14 +250,14 @@ export class MessageComposer extends React.Component { // The members should already be loading, and loadMembersIfNeeded // will return the promise for the existing operation this.props.room.loadMembersIfNeeded().then(() => { - const me = this.props.room.getMember(MatrixClientPeg.get().getUserId()); + const me = this.props.room.getMember(MatrixClientPeg.get().getUserId()!); this.setState({ me }); }); } public componentWillUnmount(): void { VoiceRecordingStore.instance.off(UPDATE_EVENT, this.onVoiceStoreUpdate); - dis.unregister(this.dispatcherRef); + if (this.dispatcherRef) dis.unregister(this.dispatcherRef); UIStore.instance.stopTrackingElementDimensions(`MessageComposer${this.instanceId}`); UIStore.instance.removeListener(`MessageComposer${this.instanceId}`, this.onResize); @@ -268,12 +268,12 @@ export class MessageComposer extends React.Component { private onTombstoneClick = (ev: ButtonEvent): void => { ev.preventDefault(); - const replacementRoomId = this.context.tombstone.getContent()["replacement_room"]; + const replacementRoomId = this.context.tombstone?.getContent()["replacement_room"]; const replacementRoom = MatrixClientPeg.get().getRoom(replacementRoomId); - let createEventId = null; + let createEventId: string | undefined; if (replacementRoom) { const createEvent = replacementRoom.currentState.getStateEvents(EventType.RoomCreate, ""); - if (createEvent && createEvent.getId()) createEventId = createEvent.getId(); + if (createEvent?.getId()) createEventId = createEvent.getId(); } const viaServers = [this.context.tombstone.getSender().split(":").slice(1).join(":")]; @@ -408,7 +408,7 @@ export class MessageComposer extends React.Component { private onRecordingEndingSoon = ({ secondsLeft }: { secondsLeft: number }): void => { this.setState({ recordingTimeLeftSeconds: secondsLeft }); - window.setTimeout(() => this.setState({ recordingTimeLeftSeconds: null }), 3000); + window.setTimeout(() => this.setState({ recordingTimeLeftSeconds: undefined }), 3000); }; private setStickerPickerOpen = (isStickerPickerOpen: boolean): void => { diff --git a/src/components/views/rooms/NotificationBadge/StatelessNotificationBadge.tsx b/src/components/views/rooms/NotificationBadge/StatelessNotificationBadge.tsx index af7b5fc85d..319787109d 100644 --- a/src/components/views/rooms/NotificationBadge/StatelessNotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge/StatelessNotificationBadge.tsx @@ -38,7 +38,7 @@ export function StatelessNotificationBadge({ symbol, count, color, ...props }: P // Don't show a badge if we don't need to if (color === NotificationColor.None || (hideBold && color == NotificationColor.Bold)) { - return null; + return <>; } const hasUnreadCount = color >= NotificationColor.Grey && (!!count || !!symbol); @@ -54,8 +54,8 @@ export function StatelessNotificationBadge({ symbol, count, color, ...props }: P mx_NotificationBadge_visible: isEmptyBadge ? true : hasUnreadCount, mx_NotificationBadge_highlighted: color >= NotificationColor.Red, mx_NotificationBadge_dot: isEmptyBadge, - mx_NotificationBadge_2char: symbol?.length > 0 && symbol?.length < 3, - mx_NotificationBadge_3char: symbol?.length > 2, + mx_NotificationBadge_2char: symbol && symbol.length > 0 && symbol.length < 3, + mx_NotificationBadge_3char: symbol && symbol.length > 2, }); if (props.onClick) { diff --git a/src/components/views/rooms/PinnedEventTile.tsx b/src/components/views/rooms/PinnedEventTile.tsx index 006b0670df..29ca93eaf4 100644 --- a/src/components/views/rooms/PinnedEventTile.tsx +++ b/src/components/views/rooms/PinnedEventTile.tsx @@ -62,7 +62,7 @@ export default class PinnedEventTile extends React.Component { eventId: string, relationType: RelationType | string, eventType: EventType | string, - ): Relations => { + ): Relations | undefined => { if (eventId === this.props.event.getId()) { return this.relations.get(relationType)?.get(eventType); } @@ -71,7 +71,7 @@ export default class PinnedEventTile extends React.Component { public render(): React.ReactNode { const sender = this.props.event.getSender(); - let unpinButton = null; + let unpinButton: JSX.Element | undefined; if (this.props.onUnpinClicked) { unpinButton = ( 0) { remText = ( diff --git a/src/components/views/rooms/ReadReceiptMarker.tsx b/src/components/views/rooms/ReadReceiptMarker.tsx index 1ba0e9a19f..1a47719f58 100644 --- a/src/components/views/rooms/ReadReceiptMarker.tsx +++ b/src/components/views/rooms/ReadReceiptMarker.tsx @@ -133,7 +133,7 @@ export default class ReadReceiptMarker extends React.PureComponent ); } else if (plusMenuDisplayed) { - let newRoomOpts: JSX.Element; - let joinRoomOpt: JSX.Element; + let newRoomOpts: JSX.Element | undefined; + let joinRoomOpt: JSX.Element | undefined; if (canCreateRooms) { newRoomOpts = ( @@ -366,7 +370,7 @@ const RoomListHeader: React.FC = ({ onVisibilityChange }) => { } let title: string; - if (activeSpace) { + if (activeSpace && spaceName) { title = spaceName; } else { title = getMetaSpaceName(spaceKey as MetaSpace, allRoomsInHome); diff --git a/src/components/views/rooms/RoomPreviewBar.tsx b/src/components/views/rooms/RoomPreviewBar.tsx index b209e26024..f9359bc5a5 100644 --- a/src/components/views/rooms/RoomPreviewBar.tsx +++ b/src/components/views/rooms/RoomPreviewBar.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactNode } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import { MatrixError } from "matrix-js-sdk/src/http-api"; import { EventType, RoomType } from "matrix-js-sdk/src/@types/event"; @@ -221,25 +221,27 @@ export default class RoomPreviewBar extends React.Component { return { memberName, reason }; } - private joinRule(): JoinRule { - return this.props.room?.currentState - .getStateEvents(EventType.RoomJoinRules, "") - ?.getContent().join_rule; + private joinRule(): JoinRule | null { + return ( + this.props.room?.currentState + .getStateEvents(EventType.RoomJoinRules, "") + ?.getContent().join_rule ?? null + ); } - private getMyMember(): RoomMember { - return this.props.room?.getMember(MatrixClientPeg.get().getUserId()); + private getMyMember(): RoomMember | null { + return this.props.room?.getMember(MatrixClientPeg.get().getUserId()!) ?? null; } - private getInviteMember(): RoomMember { + private getInviteMember(): RoomMember | null { const { room } = this.props; if (!room) { - return; + return null; } - const myUserId = MatrixClientPeg.get().getUserId(); + const myUserId = MatrixClientPeg.get().getUserId()!; const inviteEvent = room.currentState.getMember(myUserId); if (!inviteEvent) { - return; + return null; } const inviterUserId = inviteEvent.events.member.getSender(); return room.currentState.getMember(inviterUserId); @@ -282,15 +284,15 @@ export default class RoomPreviewBar extends React.Component { const isSpace = this.props.room?.isSpaceRoom() ?? this.props.oobData?.roomType === RoomType.Space; let showSpinner = false; - let title; - let subTitle; - let reasonElement; - let primaryActionHandler; - let primaryActionLabel; - let secondaryActionHandler; - let secondaryActionLabel; - let footer; - const extraComponents = []; + let title: string | undefined; + let subTitle: string | ReactNode[] | undefined; + let reasonElement: JSX.Element | undefined; + let primaryActionHandler: (() => void) | undefined; + let primaryActionLabel: string | undefined; + let secondaryActionHandler: (() => void) | undefined; + let secondaryActionLabel: string | undefined; + let footer: JSX.Element | undefined; + const extraComponents: JSX.Element[] = []; const messageCase = this.getMessageCase(); switch (messageCase) { @@ -351,7 +353,7 @@ export default class RoomPreviewBar extends React.Component { } else { title = _t("You were removed by %(memberName)s", { memberName }); } - subTitle = reason ? _t("Reason: %(reason)s", { reason }) : null; + subTitle = reason ? _t("Reason: %(reason)s", { reason }) : undefined; if (isSpace) { primaryActionLabel = _t("Forget this space"); @@ -376,7 +378,7 @@ export default class RoomPreviewBar extends React.Component { } else { title = _t("You were banned by %(memberName)s", { memberName }); } - subTitle = reason ? _t("Reason: %(reason)s", { reason }) : null; + subTitle = reason ? _t("Reason: %(reason)s", { reason }) : undefined; if (isSpace) { primaryActionLabel = _t("Forget this space"); } else { @@ -499,7 +501,7 @@ export default class RoomPreviewBar extends React.Component { primaryActionLabel = _t("Accept"); } - const myUserId = MatrixClientPeg.get().getUserId(); + const myUserId = MatrixClientPeg.get().getUserId()!; const member = this.props.room?.currentState.getMember(myUserId); const memberEventContent = member?.events.member?.getContent(); diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx index 9adf1d7e20..bf41002afb 100644 --- a/src/components/views/rooms/RoomSublist.tsx +++ b/src/components/views/rooms/RoomSublist.tsx @@ -22,7 +22,7 @@ import { Dispatcher } from "flux"; import { Enable, Resizable } from "re-resizable"; import { Direction } from "re-resizable/lib/resizer"; import * as React from "react"; -import { ComponentType, createRef, ReactComponentElement } from "react"; +import { ComponentType, createRef, ReactComponentElement, ReactNode } from "react"; import { polyfillTouchEvent } from "../../../@types/polyfill"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; @@ -95,7 +95,7 @@ interface ResizeDelta { type PartialDOMRect = Pick; interface IState { - contextMenuPosition: PartialDOMRect; + contextMenuPosition?: PartialDOMRect; isResizing: boolean; isExpanded: boolean; // used for the for expand of the sublist when the room list is being filtered height: number; @@ -123,7 +123,6 @@ export default class RoomSublist extends React.Component { this.heightAtStart = 0; this.notificationState = RoomNotificationStateStore.instance.getListState(this.props.tagId); this.state = { - contextMenuPosition: null, isResizing: false, isExpanded: !this.layout.isCollapsed, height: 0, // to be fixed in a moment, we need `rooms` to calculate this. @@ -160,10 +159,7 @@ export default class RoomSublist extends React.Component { } private get extraTiles(): ReactComponentElement[] | null { - if (this.props.extraTiles) { - return this.props.extraTiles; - } - return null; + return this.props.extraTiles ?? null; } private get numTiles(): number { @@ -390,7 +386,7 @@ export default class RoomSublist extends React.Component { }; private onCloseMenu = (): void => { - this.setState({ contextMenuPosition: null }); + this.setState({ contextMenuPosition: undefined }); }; private onUnreadFirstChanged = (): void => { @@ -506,7 +502,7 @@ export default class RoomSublist extends React.Component { // On ArrowLeft go to the sublist header case KeyBindingAction.ArrowLeft: ev.stopPropagation(); - this.headerButton.current.focus(); + this.headerButton.current?.focus(); break; // Consume ArrowRight so it doesn't cause focus to get sent to composer case KeyBindingAction.ArrowRight: @@ -557,10 +553,10 @@ export default class RoomSublist extends React.Component { return tiles; } - private renderMenu(): React.ReactElement { + private renderMenu(): ReactNode { if (this.props.tagId === DefaultTagID.Suggested || this.props.tagId === DefaultTagID.SavedItems) return null; // not sortable - let contextMenu = null; + let contextMenu: JSX.Element | undefined; if (this.state.contextMenuPosition) { let isAlphabetical = RoomListStore.instance.getTagSorting(this.props.tagId) === SortAlgorithm.Alphabetic; let isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance; @@ -571,7 +567,7 @@ export default class RoomSublist extends React.Component { } // Invites don't get some nonsense options, so only add them if we have to. - let otherSections = null; + let otherSections: JSX.Element | undefined; if (this.props.tagId !== DefaultTagID.Invite) { otherSections = ( @@ -665,7 +661,7 @@ export default class RoomSublist extends React.Component { /> ); - let addRoomButton = null; + let addRoomButton: JSX.Element | undefined; if (this.props.AuxButtonComponent) { const AuxButtonComponent = this.props.AuxButtonComponent; addRoomButton = ; @@ -747,7 +743,7 @@ export default class RoomSublist extends React.Component { mx_RoomSublist_hidden: hidden, }); - let content = null; + let content: JSX.Element | undefined; if (this.state.roomsLoading) { content =
; } else if (visibleTiles.length > 0 && this.props.forceExpanded) { @@ -773,7 +769,7 @@ export default class RoomSublist extends React.Component { // If we're hiding rooms, show a 'show more' button to the user. This button // floats above the resize handle, if we have one present. If the user has all // tiles visible, it becomes 'show less'. - let showNButton = null; + let showNButton: JSX.Element | undefined; const hasMoreSlidingSync = this.slidingSyncMode && RoomListStore.instance.getCount(this.props.tagId) > this.state.rooms.length; @@ -786,7 +782,7 @@ export default class RoomSublist extends React.Component { numMissing = RoomListStore.instance.getCount(this.props.tagId) - amountFullyShown; } const label = _t("Show %(count)s more", { count: numMissing }); - let showMoreText = {label}; + let showMoreText: ReactNode = {label}; if (this.props.isMinimized) showMoreText = null; showNButton = ( { } else if (this.numTiles > this.layout.defaultVisibleTiles) { // we have all tiles visible - add a button to show less const label = _t("Show less"); - let showLessText = {label}; + let showLessText: ReactNode = {label}; if (this.props.isMinimized) showLessText = null; showNButton = ( { }; private searchIfQuery(): void { - if (this.searchTerm.current.value) { + if (this.searchTerm.current?.value) { this.onSearch(); } } private onSearch = (): void => { - if (!this.searchTerm.current.value.trim()) return; + if (!this.searchTerm.current?.value.trim()) return; this.props.onSearch(this.searchTerm.current.value, this.state.scope); }; diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index ed60113c3f..a99d2c1c68 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -368,7 +368,12 @@ export class SendMessageComposer extends React.Component this.props.mxClient.sendMessage(actualRoomId, threadId, content), + (actualRoomId: string) => this.props.mxClient.sendMessage(actualRoomId, threadId ?? null, content!), this.props.mxClient, ); if (replyToEvent) { @@ -439,7 +444,7 @@ export class SendMessageComposer extends React.Component { - if (containsEmoji(content, effect.emojis)) { + if (containsEmoji(content!, effect.emojis)) { // For initial threads launch, chat effects are disabled // see #19731 const isNotThread = this.props.relation?.rel_type !== THREAD_RELATION_TYPE.name; diff --git a/src/components/views/rooms/Stickerpicker.tsx b/src/components/views/rooms/Stickerpicker.tsx index 3854527288..f1808aab1f 100644 --- a/src/components/views/rooms/Stickerpicker.tsx +++ b/src/components/views/rooms/Stickerpicker.tsx @@ -52,9 +52,9 @@ interface IProps { } interface IState { - imError: string; - stickerpickerWidget: IWidgetEvent; - widgetId: string; + imError: string | null; + stickerpickerWidget: IWidgetEvent | null; + widgetId: string | null; } export default class Stickerpicker extends React.PureComponent { @@ -71,7 +71,7 @@ export default class Stickerpicker extends React.PureComponent { private popoverWidth = 300; private popoverHeight = 300; // This is loaded by _acquireScalarClient on an as-needed basis. - private scalarClient: ScalarAuthClient = null; + private scalarClient: ScalarAuthClient | null = null; public constructor(props: IProps) { super(props); @@ -82,13 +82,13 @@ export default class Stickerpicker extends React.PureComponent { }; } - private acquireScalarClient(): Promise { + private async acquireScalarClient(): Promise { if (this.scalarClient) return Promise.resolve(this.scalarClient); // TODO: Pick the right manager for the widget if (IntegrationManagers.sharedInstance().hasManager()) { - this.scalarClient = IntegrationManagers.sharedInstance().getPrimaryManager().getScalarClient(); + this.scalarClient = IntegrationManagers.sharedInstance().getPrimaryManager()?.getScalarClient() ?? null; return this.scalarClient - .connect() + ?.connect() .then(() => { this.forceUpdate(); return this.scalarClient; @@ -170,21 +170,14 @@ export default class Stickerpicker extends React.PureComponent { private updateWidget = (): void => { const stickerpickerWidget = WidgetUtils.getStickerpickerWidgets()[0]; if (!stickerpickerWidget) { - Stickerpicker.currentWidget = null; + Stickerpicker.currentWidget = undefined; this.setState({ stickerpickerWidget: null, widgetId: null }); return; } const currentWidget = Stickerpicker.currentWidget; - let currentUrl = null; - if (currentWidget && currentWidget.content && currentWidget.content.url) { - currentUrl = currentWidget.content.url; - } - - let newUrl = null; - if (stickerpickerWidget && stickerpickerWidget.content && stickerpickerWidget.content.url) { - newUrl = stickerpickerWidget.content.url; - } + const currentUrl = currentWidget?.content?.url ?? null; + const newUrl = stickerpickerWidget?.content?.url ?? null; if (newUrl !== currentUrl) { // Destroy the existing frame so a new one can be created @@ -238,7 +231,7 @@ export default class Stickerpicker extends React.PureComponent { private sendVisibilityToWidget(visible: boolean): void { if (!this.state.stickerpickerWidget) return; const messaging = WidgetMessagingStore.instance.getMessagingForUid( - WidgetUtils.calcWidgetUid(this.state.stickerpickerWidget.id, null), + WidgetUtils.calcWidgetUid(this.state.stickerpickerWidget.id), ); if (messaging && visible !== this.prevSentVisibility) { messaging.updateVisibility(visible).catch((err) => { @@ -300,8 +293,8 @@ export default class Stickerpicker extends React.PureComponent { room={this.props.room} threadId={this.props.threadId} fullWidth={true} - userId={MatrixClientPeg.get().credentials.userId} - creatorUserId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId} + userId={MatrixClientPeg.get().credentials.userId!} + creatorUserId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId!} waitForIframeLoad={true} showMenubar={true} onEditClick={this.launchManageIntegrations} @@ -347,8 +340,8 @@ export default class Stickerpicker extends React.PureComponent { private launchManageIntegrations = (): void => { // noinspection JSIgnoredPromiseFromCall IntegrationManagers.sharedInstance() - .getPrimaryManager() - .open(this.props.room, `type_${WidgetType.STICKERPICKER.preferred}`, this.state.widgetId); + ?.getPrimaryManager() + ?.open(this.props.room, `type_${WidgetType.STICKERPICKER.preferred}`, this.state.widgetId ?? undefined); }; public render(): React.ReactNode { diff --git a/src/components/views/rooms/ThirdPartyMemberInfo.tsx b/src/components/views/rooms/ThirdPartyMemberInfo.tsx index 04e429e618..917e8d6ce3 100644 --- a/src/components/views/rooms/ThirdPartyMemberInfo.tsx +++ b/src/components/views/rooms/ThirdPartyMemberInfo.tsx @@ -45,19 +45,19 @@ interface IState { } export default class ThirdPartyMemberInfo extends React.Component { - private room: Room; + private readonly room: Room | null; public constructor(props: IProps) { super(props); this.room = MatrixClientPeg.get().getRoom(this.props.event.getRoomId()); - const me = this.room.getMember(MatrixClientPeg.get().getUserId()); - const powerLevels = this.room.currentState.getStateEvents("m.room.power_levels", ""); + const me = this.room?.getMember(MatrixClientPeg.get().getUserId()!); + const powerLevels = this.room?.currentState.getStateEvents("m.room.power_levels", ""); let kickLevel = powerLevels ? powerLevels.getContent().kick : 50; if (typeof kickLevel !== "number") kickLevel = 50; - const sender = this.room.getMember(this.props.event.getSender()); + const sender = this.room?.getMember(this.props.event.getSender()); this.state = { stateKey: this.props.event.getStateKey(), @@ -121,7 +121,7 @@ export default class ThirdPartyMemberInfo extends React.Component @@ -135,8 +135,8 @@ export default class ThirdPartyMemberInfo extends React.Component diff --git a/src/components/views/rooms/ThreadSummary.tsx b/src/components/views/rooms/ThreadSummary.tsx index 7a55050898..717767d9a1 100644 --- a/src/components/views/rooms/ThreadSummary.tsx +++ b/src/components/views/rooms/ThreadSummary.tsx @@ -77,18 +77,18 @@ interface IPreviewProps { export const ThreadMessagePreview: React.FC = ({ thread, showDisplayname = false }) => { const cli = useContext(MatrixClientContext); - const lastReply = useTypedEventEmitterState(thread, ThreadEvent.Update, () => thread.replyToEvent); + const lastReply = useTypedEventEmitterState(thread, ThreadEvent.Update, () => thread.replyToEvent) ?? undefined; // track the content as a means to regenerate the thread message preview upon edits & decryption - const [content, setContent] = useState(lastReply?.getContent()); + const [content, setContent] = useState(lastReply?.getContent()); useTypedEventEmitter(lastReply, MatrixEventEvent.Replaced, () => { - setContent(lastReply.getContent()); + setContent(lastReply!.getContent()); }); const awaitDecryption = lastReply?.shouldAttemptDecryption() || lastReply?.isBeingDecrypted(); - useTypedEventEmitter(awaitDecryption ? lastReply : null, MatrixEventEvent.Decrypted, () => { - setContent(lastReply.getContent()); + useTypedEventEmitter(awaitDecryption ? lastReply : undefined, MatrixEventEvent.Decrypted, () => { + setContent(lastReply!.getContent()); }); - const preview = useAsyncMemo(async (): Promise => { + const preview = useAsyncMemo(async (): Promise => { if (!lastReply) return; await cli.decryptEventIfNeeded(lastReply); return MessagePreviewStore.instance.generatePreviewForEvent(lastReply); diff --git a/src/components/views/rooms/TopUnreadMessagesBar.tsx b/src/components/views/rooms/TopUnreadMessagesBar.tsx index 3e4eb87538..51e67e611b 100644 --- a/src/components/views/rooms/TopUnreadMessagesBar.tsx +++ b/src/components/views/rooms/TopUnreadMessagesBar.tsx @@ -20,8 +20,8 @@ import { _t } from "../../../languageHandler"; import AccessibleButton from "../elements/AccessibleButton"; interface IProps { - onScrollUpClick?: (e: React.MouseEvent) => void; - onCloseClick?: (e: React.MouseEvent) => void; + onScrollUpClick: (e: React.MouseEvent) => void; + onCloseClick: (e: React.MouseEvent) => void; } export default class TopUnreadMessagesBar extends React.PureComponent { diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 938ef71d73..2f7be4c9b2 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -48,7 +48,7 @@ import { createVoiceMessageContent } from "../../../utils/createVoiceMessageCont interface IProps { room: Room; - permalinkCreator?: RoomPermalinkCreator; + permalinkCreator: RoomPermalinkCreator; relation?: IEventRelation; replyToEvent?: MatrixEvent; } @@ -70,9 +70,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent => { @@ -220,7 +218,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent { return WhoIsTypingTile.isVisible(this.state); }; - private onRoomTimeline = (event: MatrixEvent, room: Room | null): void => { + private onRoomTimeline = (event: MatrixEvent, room?: Room): void => { if (room?.roomId === this.props.room.roomId) { const userId = event.getSender(); // remove user from usersTyping diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/utils.ts b/src/components/views/rooms/wysiwyg_composer/hooks/utils.ts index 648f63eaef..83a18fb55b 100644 --- a/src/components/views/rooms/wysiwyg_composer/hooks/utils.ts +++ b/src/components/views/rooms/wysiwyg_composer/hooks/utils.ts @@ -45,7 +45,7 @@ export function setCursorPositionAtTheEnd(element: HTMLElement): void { const range = document.createRange(); range.selectNodeContents(element); range.collapse(false); - const selection = document.getSelection(); + const selection = document.getSelection()!; selection.removeAllRanges(); selection.addRange(range); diff --git a/src/components/views/settings/BridgeTile.tsx b/src/components/views/settings/BridgeTile.tsx index 34c36cda3f..1ec7a07e5a 100644 --- a/src/components/views/settings/BridgeTile.tsx +++ b/src/components/views/settings/BridgeTile.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactNode } from "react"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { Room } from "matrix-js-sdk/src/models/room"; import { logger } from "matrix-js-sdk/src/logger"; @@ -79,13 +79,13 @@ export default class BridgeTile extends React.PureComponent { `Bridge info event ${this.props.ev.getId()} does not provide a 'bridgebot' key which` + "is deprecated behaviour. Using sender for now.", ); - content.bridgebot = this.props.ev.getSender(); + content.bridgebot = this.props.ev.getSender()!; } const { channel, network, protocol } = content; const protocolName = protocol.displayname || protocol.id; const channelName = channel.displayname || channel.id; - let creator = null; + let creator: JSX.Element | undefined; if (content.creator) { creator = (
  • @@ -129,7 +129,7 @@ export default class BridgeTile extends React.PureComponent { let networkIcon; if (protocol.avatar_url) { - const avatarUrl = mediaFromMxc(protocol.avatar_url).getSquareThumbnailHttp(64); + const avatarUrl = mediaFromMxc(protocol.avatar_url).getSquareThumbnailHttp(64) ?? undefined; networkIcon = ( { } else { networkIcon =
    ; } - let networkItem = null; + let networkItem: ReactNode | undefined; if (network) { const networkName = network.displayname || network.id; let networkLink = {networkName}; diff --git a/src/components/views/settings/ChangeDisplayName.tsx b/src/components/views/settings/ChangeDisplayName.tsx index 0c68c54940..696384ef67 100644 --- a/src/components/views/settings/ChangeDisplayName.tsx +++ b/src/components/views/settings/ChangeDisplayName.tsx @@ -24,8 +24,8 @@ export default class ChangeDisplayName extends React.Component { private getDisplayName = async (): Promise => { const cli = MatrixClientPeg.get(); try { - const res = await cli.getProfileInfo(cli.getUserId()); - return res.displayname; + const res = await cli.getProfileInfo(cli.getUserId()!); + return res.displayname ?? ""; } catch (e) { throw new Error("Failed to fetch display name"); } diff --git a/src/components/views/settings/ChangePassword.tsx b/src/components/views/settings/ChangePassword.tsx index 333d73e4ea..4c84a960f9 100644 --- a/src/components/views/settings/ChangePassword.tsx +++ b/src/components/views/settings/ChangePassword.tsx @@ -42,12 +42,12 @@ enum Phase { } interface IProps { - onFinished?: (outcome: { + onFinished: (outcome: { didSetEmail?: boolean; /** Was one or more other devices logged out whilst changing the password */ didLogoutOutOtherDevices: boolean; }) => void; - onError?: (error: { error: string }) => void; + onError: (error: { error: string }) => void; rowClassName?: string; buttonClassName?: string; buttonKind?: string; @@ -68,9 +68,9 @@ interface IState { } export default class ChangePassword extends React.Component { - private [FIELD_OLD_PASSWORD]: Field; - private [FIELD_NEW_PASSWORD]: Field; - private [FIELD_NEW_PASSWORD_CONFIRM]: Field; + private [FIELD_OLD_PASSWORD]: Field | null; + private [FIELD_NEW_PASSWORD]: Field | null; + private [FIELD_NEW_PASSWORD_CONFIRM]: Field | null; public static defaultProps: Partial = { onFinished() {}, @@ -154,7 +154,7 @@ export default class ChangePassword extends React.Component { }, // TODO: Remove `user` once servers support proper UIA // See https://github.com/matrix-org/synapse/issues/5665 - user: cli.credentials.userId, + user: cli.credentials.userId ?? undefined, password: oldPassword, }; @@ -195,7 +195,7 @@ export default class ChangePassword extends React.Component { }); } - private checkPassword(oldPass: string, newPass: string, confirmPass: string): { error: string } { + private checkPassword(oldPass: string, newPass: string, confirmPass: string): { error: string } | undefined { if (newPass !== confirmPass) { return { error: _t("New passwords don't match"), @@ -226,7 +226,7 @@ export default class ChangePassword extends React.Component { ); }; - private markFieldValid(fieldID: FieldType, valid: boolean): void { + private markFieldValid(fieldID: FieldType, valid?: boolean): void { const { fieldValid } = this.state; fieldValid[fieldID] = valid; this.setState({ @@ -367,7 +367,7 @@ export default class ChangePassword extends React.Component { return Object.values(this.state.fieldValid).every(Boolean); } - private findFirstInvalidField(fieldIDs: FieldType[]): Field { + private findFirstInvalidField(fieldIDs: FieldType[]): Field | null { for (const fieldID of fieldIDs) { if (!this.state.fieldValid[fieldID] && this[fieldID]) { return this[fieldID]; diff --git a/src/components/views/settings/CrossSigningPanel.tsx b/src/components/views/settings/CrossSigningPanel.tsx index be563cfc46..0ba8f580f5 100644 --- a/src/components/views/settings/CrossSigningPanel.tsx +++ b/src/components/views/settings/CrossSigningPanel.tsx @@ -75,7 +75,7 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> { private onBootstrapClick = (): void => { if (this.state.crossSigningPrivateKeysInStorage) { - Modal.createDialog(SetupEncryptionDialog, {}, null, /* priority = */ false, /* static = */ true); + Modal.createDialog(SetupEncryptionDialog, {}, undefined, /* priority = */ false, /* static = */ true); } else { // Trigger the flow to set up secure backup, which is what this will do when in // the appropriate state. @@ -90,8 +90,8 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> { private async getUpdatedStatus(): Promise { const cli = MatrixClientPeg.get(); const pkCache = cli.getCrossSigningCacheCallbacks(); - const crossSigning = cli.crypto.crossSigningInfo; - const secretStorage = cli.crypto.secretStorage; + const crossSigning = cli.crypto!.crossSigningInfo; + const secretStorage = cli.crypto!.secretStorage; const crossSigningPublicKeysOnDevice = Boolean(crossSigning.getId()); const crossSigningPrivateKeysInStorage = Boolean(await crossSigning.isStoredInSecretStorage(secretStorage)); const masterPrivateKeyCached = !!(pkCache && (await pkCache.getCrossSigningKeyCache("master"))); @@ -209,7 +209,7 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> { selfSigningPrivateKeyCached && userSigningPrivateKeyCached; - const actions = []; + const actions: JSX.Element[] = []; // TODO: determine how better to expose this to users in addition to prompts at login/toast if (!keysExistEverywhere && homeserverSupportsCrossSigning) { diff --git a/src/components/views/settings/DevicesPanel.tsx b/src/components/views/settings/DevicesPanel.tsx index c5eeca9856..cf7506caa5 100644 --- a/src/components/views/settings/DevicesPanel.tsx +++ b/src/components/views/settings/DevicesPanel.tsx @@ -64,7 +64,7 @@ export default class DevicesPanel extends React.Component { } private onDevicesUpdated = (users: string[]): void => { - if (!users.includes(this.context.getUserId())) return; + if (!users.includes(this.context.getUserId()!)) return; this.loadDevices(); }; @@ -252,9 +252,9 @@ export default class DevicesPanel extends React.Component { const otherDevices = devices.filter((device) => device.device_id !== myDeviceId); otherDevices.sort(this.deviceCompare); - const verifiedDevices = []; - const unverifiedDevices = []; - const nonCryptoDevices = []; + const verifiedDevices: IMyDevice[] = []; + const unverifiedDevices: IMyDevice[] = []; + const nonCryptoDevices: IMyDevice[] = []; for (const device of otherDevices) { const verified = this.isDeviceVerified(device); if (verified === true) { @@ -271,7 +271,7 @@ export default class DevicesPanel extends React.Component { return ; } - let selectButton: JSX.Element; + let selectButton: JSX.Element | undefined; if (deviceList.length > 1) { const anySelected = deviceList.some((device) => this.state.selectedDevices.includes(device.device_id)); const buttonAction = anySelected diff --git a/src/components/views/settings/DevicesPanelEntry.tsx b/src/components/views/settings/DevicesPanelEntry.tsx index a0d23e44bf..57f0bc24b2 100644 --- a/src/components/views/settings/DevicesPanelEntry.tsx +++ b/src/components/views/settings/DevicesPanelEntry.tsx @@ -103,7 +103,7 @@ export default class DevicesPanelEntry extends React.Component { }); } else { const cli = MatrixClientPeg.get(); - const userId = cli.getUserId(); + const userId = cli.getUserId()!; const verificationRequestPromise = cli.requestVerification(userId, [this.props.device.device_id]); Modal.createDialog(VerificationRequestDialog, { verificationRequestPromise, @@ -119,7 +119,7 @@ export default class DevicesPanelEntry extends React.Component { public render(): React.ReactNode { let iconClass = ""; - let verifyButton: JSX.Element; + let verifyButton: JSX.Element | undefined; if (this.props.verified !== null) { iconClass = this.props.verified ? "mx_E2EIcon_verified" : "mx_E2EIcon_warning"; if (!this.props.verified && this.props.canBeVerified) { @@ -131,7 +131,7 @@ export default class DevicesPanelEntry extends React.Component { } } - let signOutButton: JSX.Element; + let signOutButton: JSX.Element | undefined; if (this.props.isOwnDevice) { signOutButton = ( diff --git a/src/components/views/settings/EventIndexPanel.tsx b/src/components/views/settings/EventIndexPanel.tsx index 6178e8bfd7..c5e617355d 100644 --- a/src/components/views/settings/EventIndexPanel.tsx +++ b/src/components/views/settings/EventIndexPanel.tsx @@ -26,6 +26,7 @@ import EventIndexPeg from "../../../indexing/EventIndexPeg"; import { SettingLevel } from "../../../settings/SettingLevel"; import SeshatResetDialog from "../dialogs/SeshatResetDialog"; import InlineSpinner from "../elements/InlineSpinner"; +import { IIndexStats } from "../../../indexing/BaseEventIndexManager"; interface IState { enabling: boolean; @@ -48,10 +49,10 @@ export default class EventIndexPanel extends React.Component<{}, IState> { public updateCurrentRoom = async (): Promise => { const eventIndex = EventIndexPeg.get(); - let stats; + let stats: IIndexStats | undefined; try { - stats = await eventIndex.getStats(); + stats = await eventIndex?.getStats(); } catch { // This call may fail if sporadically, not a huge issue as we will // try later again and probably succeed. @@ -126,8 +127,8 @@ export default class EventIndexPanel extends React.Component<{}, IState> { }); await EventIndexPeg.initEventIndex(); - await EventIndexPeg.get().addInitialCheckpoints(); - EventIndexPeg.get().startCrawler(); + await EventIndexPeg.get()?.addInitialCheckpoints(); + EventIndexPeg.get()?.startCrawler(); await SettingsStore.setValue("enableEventIndexing", null, SettingLevel.DEVICE, true); await this.updateState(); }; @@ -146,7 +147,7 @@ export default class EventIndexPanel extends React.Component<{}, IState> { }; public render(): React.ReactNode { - let eventIndexingSettings = null; + let eventIndexingSettings: JSX.Element | undefined; const brand = SdkConfig.get().brand; if (EventIndexPeg.get() !== null) { diff --git a/src/components/views/settings/FontScalingPanel.tsx b/src/components/views/settings/FontScalingPanel.tsx index 2da567719e..f8aa9186bb 100644 --- a/src/components/views/settings/FontScalingPanel.tsx +++ b/src/components/views/settings/FontScalingPanel.tsx @@ -39,8 +39,8 @@ interface IState { layout: Layout; // User profile data for the message preview userId?: string; - displayName: string; - avatarUrl: string; + displayName?: string; + avatarUrl?: string; } export default class FontScalingPanel extends React.Component { @@ -55,16 +55,13 @@ export default class FontScalingPanel extends React.Component { fontSize: (SettingsStore.getValue("baseFontSize", null) + FontWatcher.SIZE_DIFF).toString(), useCustomFontSize: SettingsStore.getValue("useCustomFontSize"), layout: SettingsStore.getValue("layout"), - userId: null, - displayName: null, - avatarUrl: null, }; } public async componentDidMount(): Promise { // Fetch the current user profile for the message preview const client = MatrixClientPeg.get(); - const userId = client.getUserId(); + const userId = client.getUserId()!; const profileInfo = await client.getProfileInfo(userId); if (this.unmounted) return; diff --git a/src/components/views/settings/JoinRuleSettings.tsx b/src/components/views/settings/JoinRuleSettings.tsx index 61cf38f8dc..f4f82bac9f 100644 --- a/src/components/views/settings/JoinRuleSettings.tsx +++ b/src/components/views/settings/JoinRuleSettings.tsx @@ -74,14 +74,14 @@ const JoinRuleSettings: React.FC = ({ ? content.allow?.filter((o) => o.type === RestrictedAllowType.RoomMembership).map((o) => o.room_id) : undefined; - const editRestrictedRoomIds = async (): Promise => { + const editRestrictedRoomIds = async (): Promise => { let selected = restrictedAllowRoomIds; if (!selected?.length && SpaceStore.instance.activeSpaceRoom) { selected = [SpaceStore.instance.activeSpaceRoom.roomId]; } const matrixClient = MatrixClientPeg.get(); - const { finished } = Modal.createDialog( + const { finished } = Modal.createDialog<[string[]]>( ManageRestrictedJoinRuleDialog, { matrixClient, @@ -127,7 +127,7 @@ const JoinRuleSettings: React.FC = ({ const shownSpaces = restrictedAllowRoomIds .map((roomId) => cli.getRoom(roomId)) .filter((room) => room?.isSpaceRoom()) - .slice(0, 4); + .slice(0, 4) as Room[]; let moreText; if (shownSpaces.length < restrictedAllowRoomIds.length) { @@ -234,7 +234,7 @@ const JoinRuleSettings: React.FC = ({ const onChange = async (joinRule: JoinRule): Promise => { const beforeJoinRule = content.join_rule; - let restrictedAllowRoomIds: string[]; + let restrictedAllowRoomIds: string[] | undefined; if (joinRule === JoinRule.Restricted) { if (beforeJoinRule === JoinRule.Restricted || roomSupportsRestricted) { // Have the user pick which spaces to allow joins from @@ -244,8 +244,8 @@ const JoinRuleSettings: React.FC = ({ // Block this action on a room upgrade otherwise it'd make their room unjoinable const targetVersion = preferredRestrictionVersion; - let warning: JSX.Element; - const userId = cli.getUserId(); + let warning: JSX.Element | undefined; + const userId = cli.getUserId()!; const unableToUpdateSomeParents = Array.from(SpaceStore.instance.getKnownParents(room.roomId)).some( (roomId) => !cli.getRoom(roomId)?.currentState.maySendStateEvent(EventType.SpaceChild, userId), ); @@ -332,7 +332,7 @@ const JoinRuleSettings: React.FC = ({ } // when setting to 0 allowed rooms/spaces set to invite only instead as per the note - if (!restrictedAllowRoomIds.length) { + if (!restrictedAllowRoomIds?.length) { joinRule = JoinRule.Invite; } } @@ -346,7 +346,7 @@ const JoinRuleSettings: React.FC = ({ // pre-set the accepted spaces with the currently viewed one as per the microcopy if (joinRule === JoinRule.Restricted) { - newContent.allow = restrictedAllowRoomIds.map((roomId) => ({ + newContent.allow = restrictedAllowRoomIds?.map((roomId) => ({ type: RestrictedAllowType.RoomMembership, room_id: roomId, })); diff --git a/src/components/views/settings/Notifications.tsx b/src/components/views/settings/Notifications.tsx index b4ed0c680f..3fb902ba5e 100644 --- a/src/components/views/settings/Notifications.tsx +++ b/src/components/views/settings/Notifications.tsx @@ -328,7 +328,7 @@ export default class Notifications extends React.PureComponent { this.setState({ phase: Phase.Persisting }); try { - const masterRule = this.state.masterPushRule; + const masterRule = this.state.masterPushRule!; await MatrixClientPeg.get().setPushRuleEnabled("global", masterRule.kind, masterRule.rule_id, !checked); await this.refreshFromServer(); } catch (e) { @@ -396,8 +396,8 @@ export default class Notifications extends React.PureComponent { if (rule.ruleId === KEYWORD_RULE_ID) { // Update all the keywords for (const rule of this.state.vectorKeywordRuleInfo.rules) { - let enabled: boolean; - let actions: PushRuleAction[]; + let enabled: boolean | undefined; + let actions: PushRuleAction[] | undefined; if (checkedState === VectorState.On) { if (rule.actions.length !== 1) { // XXX: Magic number diff --git a/src/components/views/settings/SecureBackupPanel.tsx b/src/components/views/settings/SecureBackupPanel.tsx index 5919225984..cc5dde1cb0 100644 --- a/src/components/views/settings/SecureBackupPanel.tsx +++ b/src/components/views/settings/SecureBackupPanel.tsx @@ -33,14 +33,14 @@ import { accessSecretStorage } from "../../../SecurityManager"; interface IState { loading: boolean; - error: null; - backupKeyStored: boolean; - backupKeyCached: boolean; - backupKeyWellFormed: boolean; - secretStorageKeyInAccount: boolean; - secretStorageReady: boolean; - backupInfo: IKeyBackupInfo; - backupSigStatus: TrustInfo; + error: Error | null; + backupKeyStored: boolean | null; + backupKeyCached: boolean | null; + backupKeyWellFormed: boolean | null; + secretStorageKeyInAccount: boolean | null; + secretStorageReady: boolean | null; + backupInfo: IKeyBackupInfo | null; + backupSigStatus: TrustInfo | null; sessionsRemaining: number; } @@ -144,10 +144,10 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> { private async getUpdatedDiagnostics(): Promise { const cli = MatrixClientPeg.get(); - const secretStorage = cli.crypto.secretStorage; + const secretStorage = cli.crypto!.secretStorage; const backupKeyStored = !!(await cli.isKeyBackupKeyStored()); - const backupKeyFromCache = await cli.crypto.getSessionBackupPrivateKey(); + const backupKeyFromCache = await cli.crypto!.getSessionBackupPrivateKey(); const backupKeyCached = !!backupKeyFromCache; const backupKeyWellFormed = backupKeyFromCache instanceof Uint8Array; const secretStorageKeyInAccount = await secretStorage.hasKey(); @@ -173,7 +173,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> { this.loadBackupStatus(); }, }, - null, + undefined, /* priority = */ false, /* static = */ true, ); @@ -200,7 +200,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> { }; private restoreBackup = async (): Promise => { - Modal.createDialog(RestoreKeyBackupDialog, null, null, /* priority = */ false, /* static = */ true); + Modal.createDialog(RestoreKeyBackupDialog, undefined, undefined, /* priority = */ false, /* static = */ true); }; private resetSecretStorage = async (): Promise => { @@ -233,7 +233,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> { let statusDescription; let extraDetailsTableRows; let extraDetails; - const actions = []; + const actions: JSX.Element[] = []; if (error) { statusDescription =
    {_t("Unable to load key backup status")}
    ; } else if (loading) { diff --git a/src/components/views/settings/SetIdServer.tsx b/src/components/views/settings/SetIdServer.tsx index 9b57f182d7..43f59c8ee1 100644 --- a/src/components/views/settings/SetIdServer.tsx +++ b/src/components/views/settings/SetIdServer.tsx @@ -97,7 +97,6 @@ export default class SetIdServer extends React.Component { defaultIdServer, currentClientIdServer: MatrixClientPeg.get().getIdentityServerUrl(), idServer: "", - error: null, busy: false, disconnectBusy: false, checking: false, diff --git a/src/components/views/settings/account/EmailAddresses.tsx b/src/components/views/settings/account/EmailAddresses.tsx index 7de9117162..83e137a21a 100644 --- a/src/components/views/settings/account/EmailAddresses.tsx +++ b/src/components/views/settings/account/EmailAddresses.tsx @@ -133,7 +133,7 @@ interface IProps { interface IState { verifying: boolean; - addTask: AddThreepid; + addTask: AddThreepid | null; continueDisabled: boolean; newEmailAddress: string; } @@ -201,7 +201,7 @@ export default class EmailAddresses extends React.Component { this.setState({ continueDisabled: true }); this.state.addTask - .checkEmailLinkClicked() + ?.checkEmailLinkClicked() .then(([finished]) => { let newEmailAddress = this.state.newEmailAddress; if (finished) { diff --git a/src/components/views/settings/devices/CurrentDeviceSection.tsx b/src/components/views/settings/devices/CurrentDeviceSection.tsx index db56a07e0e..ef9b28aa86 100644 --- a/src/components/views/settings/devices/CurrentDeviceSection.tsx +++ b/src/components/views/settings/devices/CurrentDeviceSection.tsx @@ -33,11 +33,11 @@ interface Props { device?: ExtendedDevice; isLoading: boolean; isSigningOut: boolean; - localNotificationSettings?: LocalNotificationSettings | undefined; + localNotificationSettings?: LocalNotificationSettings; // number of other sessions the user has // excludes current session otherSessionsCount: number; - setPushNotifications?: (deviceId: string, enabled: boolean) => Promise | undefined; + setPushNotifications: (deviceId: string, enabled: boolean) => Promise; onVerifyCurrentDevice: () => void; onSignOutCurrentDevice: () => void; signOutAllOtherSessions?: () => void; diff --git a/src/components/views/settings/devices/deleteDevices.tsx b/src/components/views/settings/devices/deleteDevices.tsx index 3f4b34d04d..3bbfee0032 100644 --- a/src/components/views/settings/devices/deleteDevices.tsx +++ b/src/components/views/settings/devices/deleteDevices.tsx @@ -32,7 +32,7 @@ const makeDeleteRequest = export const deleteDevicesWithInteractiveAuth = async ( matrixClient: MatrixClient, deviceIds: string[], - onFinished?: InteractiveAuthCallback, + onFinished: InteractiveAuthCallback, ): Promise => { if (!deviceIds.length) { return; diff --git a/src/components/views/settings/discovery/EmailAddresses.tsx b/src/components/views/settings/discovery/EmailAddresses.tsx index 9eaf69b098..9f2e123ac3 100644 --- a/src/components/views/settings/discovery/EmailAddresses.tsx +++ b/src/components/views/settings/discovery/EmailAddresses.tsx @@ -48,9 +48,9 @@ interface IEmailAddressProps { interface IEmailAddressState { verifying: boolean; - addTask: any; // FIXME: When AddThreepid is TSfied + addTask: AddThreepid | null; continueDisabled: boolean; - bound: boolean; + bound?: boolean; } export class EmailAddress extends React.Component { @@ -172,7 +172,7 @@ export class EmailAddress extends React.Component { @@ -89,6 +89,7 @@ export class PhoneNumber extends React.Component void; // Promise resolve function for startTermsFlow callback + policiesAndServices: ServicePolicyPair[] | null; // From the startTermsFlow callback + agreedUrls: string[] | null; // From the startTermsFlow callback + resolve: ((values: string[]) => void) | null; // Promise resolve function for startTermsFlow callback }; emails: IThreepid[]; msisdns: IThreepid[]; loading3pids: boolean; // whether or not the emails and msisdns have been loaded canChangePassword: boolean; - idServerName: string; + idServerName?: string; externalAccountManagementUrl?: string; } @@ -92,7 +92,6 @@ export default class GeneralUserSettingsTab extends React.Component { const plat = PlatformPeg.get(); const [spellCheckEnabled, spellCheckLanguages] = await Promise.all([ - plat.getSpellCheckEnabled(), - plat.getSpellCheckLanguages(), + plat?.getSpellCheckEnabled(), + plat?.getSpellCheckLanguages(), ]); if (spellCheckLanguages) { @@ -301,7 +298,7 @@ export default class GeneralUserSettingsTab extends React.Component ); - let threepidSection = null; + let threepidSection: ReactNode = null; // For older homeservers without separate 3PID add and bind methods (MSC2290), // we use a combo add with bind option API which requires an identity server to @@ -345,7 +342,7 @@ export default class GeneralUserSettingsTab extends React.Component; } - let passwordChangeText = _t("Set a new account password…"); + let passwordChangeText: ReactNode = _t("Set a new account password…"); if (!this.state.canChangePassword) { // Just don't show anything if you can't do anything. passwordChangeText = null; @@ -483,7 +480,7 @@ export default class GeneralUserSettingsTab extends React.Component ) : null; - let accountManagementSection; + let accountManagementSection: JSX.Element | undefined; if (SettingsStore.getValue(UIFeature.Deactivate)) { accountManagementSection = ( <> diff --git a/src/components/views/spaces/SpaceChildrenPicker.tsx b/src/components/views/spaces/SpaceChildrenPicker.tsx index 3cf63fd0ad..4201b7db1a 100644 --- a/src/components/views/spaces/SpaceChildrenPicker.tsx +++ b/src/components/views/spaces/SpaceChildrenPicker.tsx @@ -23,6 +23,7 @@ import QueryMatcher from "../../../autocomplete/QueryMatcher"; import SearchBox from "../../structures/SearchBox"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import { Entry } from "../dialogs/AddExistingToSpaceDialog"; +import { filterBoolean } from "../../../utils/arrays"; enum Target { All = "All", @@ -53,7 +54,7 @@ const SpecificChildrenPicker: React.FC = ({ const matcher = new QueryMatcher(rooms, { keys: ["name"], - funcs: [(r) => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean) as string[]], + funcs: [(r) => filterBoolean([r.getCanonicalAlias(), ...r.getAltAliases()])], shouldMatchWordsOnly: false, }); diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx index 3a23b4074b..855526a2fe 100644 --- a/src/components/views/spaces/SpaceCreateMenu.tsx +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -56,7 +56,7 @@ export const createSpace = async ( avatar?: string | File, createOpts: Partial = {}, otherOpts: Partial> = {}, -): Promise => { +): Promise => { return createRoom({ createOpts: { name, @@ -179,7 +179,7 @@ export const SpaceCreateForm: React.FC = ({ children, }) => { const cli = useContext(MatrixClientContext); - const domain = cli.getDomain(); + const domain = cli.getDomain() ?? undefined; const onKeyDown = (ev: KeyboardEvent): void => { const action = getKeyBindingsManager().getAccessibilityAction(ev); @@ -231,7 +231,7 @@ export const SpaceCreateForm: React.FC = ({ name="spaceTopic" element="textarea" label={_t("Description")} - value={topic} + value={topic ?? ""} onChange={(ev) => setTopic(ev.target.value)} rows={3} disabled={busy} @@ -261,14 +261,18 @@ const SpaceCreateMenu: React.FC<{ setBusy(true); // require & validate the space name field - if (!(await spaceNameField.current.validate({ allowEmpty: false }))) { + if (spaceNameField.current && !(await spaceNameField.current.validate({ allowEmpty: false }))) { spaceNameField.current.focus(); spaceNameField.current.validate({ allowEmpty: false, focused: true }); setBusy(false); return; } - if (visibility === Visibility.Public && !(await spaceAliasField.current.validate({ allowEmpty: false }))) { + if ( + spaceAliasField.current && + visibility === Visibility.Public && + !(await spaceAliasField.current.validate({ allowEmpty: false })) + ) { spaceAliasField.current.focus(); spaceAliasField.current.validate({ allowEmpty: false, focused: true }); setBusy(false); diff --git a/src/dispatcher/payloads/JoinRoomErrorPayload.ts b/src/dispatcher/payloads/JoinRoomErrorPayload.ts index cbd5332dbc..b393c14b32 100644 --- a/src/dispatcher/payloads/JoinRoomErrorPayload.ts +++ b/src/dispatcher/payloads/JoinRoomErrorPayload.ts @@ -23,5 +23,5 @@ export interface JoinRoomErrorPayload extends Pick { action: Action.JoinRoomError; roomId: string; - err?: MatrixError; + err: MatrixError; } diff --git a/src/editor/commands.tsx b/src/editor/commands.tsx index f65507054b..71f9b678b3 100644 --- a/src/editor/commands.tsx +++ b/src/editor/commands.tsx @@ -59,7 +59,7 @@ export function getSlashCommand(model: EditorModel): [Command | undefined, strin export async function runSlashCommand( cmd: Command, - args: string, + args: string | undefined, roomId: string, threadId: string | null, ): Promise<[content: IContent | null, success: boolean]> { diff --git a/src/editor/model.ts b/src/editor/model.ts index 83f0a232f6..695e544c22 100644 --- a/src/editor/model.ts +++ b/src/editor/model.ts @@ -45,7 +45,7 @@ import { Caret } from "./caret"; */ type TransformCallback = (caretPosition: DocumentPosition, inputType: string, diff: IDiff) => number | void; -type UpdateCallback = (caret: Caret, inputType?: string, diff?: IDiff) => void; +type UpdateCallback = (caret?: Caret, inputType?: string, diff?: IDiff) => void; type ManualTransformCallback = () => Caret; export default class EditorModel { @@ -252,7 +252,7 @@ export default class EditorModel { } private onAutoComplete = ({ replaceParts, close }: ICallback): void => { - let pos; + let pos: DocumentPosition | undefined; if (replaceParts) { this._parts.splice(this.autoCompletePartIdx, this.autoCompletePartCount, ...replaceParts); this.autoCompletePartCount = replaceParts.length; @@ -380,13 +380,15 @@ export default class EditorModel { // reset it to insert as first part index = 0; } - while (str) { - const newPart = this._partCreator.createPartForInput(str, index, inputType); - const oldStr = str; - str = newPart.appendUntilRejected(str, inputType); - if (str === oldStr) { + + let it: string | undefined = str; + while (it) { + const newPart = this._partCreator.createPartForInput(it, index, inputType); + const oldStr = it; + it = newPart.appendUntilRejected(it, inputType); + if (it === oldStr) { // nothing changed, break out of this infinite loop and log an error - console.error(`Failed to update model for input (str ${str}) (type ${inputType})`); + console.error(`Failed to update model for input (str ${it}) (type ${inputType})`); break; } this.insertPart(index, newPart); diff --git a/src/hooks/spotlight/useRecentSearches.ts b/src/hooks/spotlight/useRecentSearches.ts index 0c7c2cf53a..ec416ee4ce 100644 --- a/src/hooks/spotlight/useRecentSearches.ts +++ b/src/hooks/spotlight/useRecentSearches.ts @@ -20,12 +20,13 @@ import { Room } from "matrix-js-sdk/src/matrix"; import { MatrixClientPeg } from "../../MatrixClientPeg"; import { SettingLevel } from "../../settings/SettingLevel"; import SettingsStore from "../../settings/SettingsStore"; +import { filterBoolean } from "../../utils/arrays"; export const useRecentSearches = (): [Room[], () => void] => { const [rooms, setRooms] = useState(() => { const cli = MatrixClientPeg.get(); const recents = SettingsStore.getValue("SpotlightSearch.recentSearches", null); - return recents.map((r) => cli.getRoom(r)).filter(Boolean) as Room[]; + return filterBoolean(recents.map((r) => cli.getRoom(r))); }); return [ diff --git a/src/hooks/useEventEmitter.ts b/src/hooks/useEventEmitter.ts index 40176d2c26..54a5a7a017 100644 --- a/src/hooks/useEventEmitter.ts +++ b/src/hooks/useEventEmitter.ts @@ -67,7 +67,7 @@ type Mapper = (...args: any[]) => T; * {@link useEventEmitterState} */ export function useTypedEventEmitterState>( - emitter: TypedEventEmitter, + emitter: TypedEventEmitter | undefined, eventName: Events, fn: Mapper, ): T { diff --git a/src/hooks/usePublicRoomDirectory.ts b/src/hooks/usePublicRoomDirectory.ts index d4862b6e26..a14142ebcf 100644 --- a/src/hooks/usePublicRoomDirectory.ts +++ b/src/hooks/usePublicRoomDirectory.ts @@ -107,7 +107,7 @@ export const usePublicRoomDirectory = (): { "org.matrix.msc3827.stable", )) ? Array.from(roomTypes) - : null, + : undefined, }; } diff --git a/src/hooks/useUserOnboardingContext.ts b/src/hooks/useUserOnboardingContext.ts index 234fc997a9..a3ea8ed319 100644 --- a/src/hooks/useUserOnboardingContext.ts +++ b/src/hooks/useUserOnboardingContext.ts @@ -84,7 +84,7 @@ function useUserOnboardingContextValue(defaultValue: T, callback: (cli: Matri export function useUserOnboardingContext(): UserOnboardingContext { const hasAvatar = useUserOnboardingContextValue(false, async (cli) => { - const profile = await cli.getProfileInfo(cli.getUserId()); + const profile = await cli.getProfileInfo(cli.getUserId()!); return Boolean(profile?.avatar_url); }); const hasDevices = useUserOnboardingContextValue(false, async (cli) => { diff --git a/src/indexing/BaseEventIndexManager.ts b/src/indexing/BaseEventIndexManager.ts index 11b1b195da..1234496b44 100644 --- a/src/indexing/BaseEventIndexManager.ts +++ b/src/indexing/BaseEventIndexManager.ts @@ -21,7 +21,7 @@ import { Direction } from "matrix-js-sdk/src/matrix"; /* eslint-disable camelcase */ export interface ICrawlerCheckpoint { roomId: string; - token: string; + token: string | null; fullCrawl?: boolean; direction: Direction; } diff --git a/src/indexing/EventIndex.ts b/src/indexing/EventIndex.ts index fbc528dec4..bca7547b6c 100644 --- a/src/indexing/EventIndex.ts +++ b/src/indexing/EventIndex.ts @@ -56,6 +56,7 @@ export default class EventIndex extends EventEmitter { public async init(): Promise { const indexManager = PlatformPeg.get()?.getEventIndexingManager(); + if (!indexManager) return; this.crawlerCheckpoints = await indexManager.loadCheckpoints(); logger.log("EventIndex: Loaded checkpoints", this.crawlerCheckpoints); @@ -93,6 +94,7 @@ export default class EventIndex extends EventEmitter { */ public async addInitialCheckpoints(): Promise { const indexManager = PlatformPeg.get()?.getEventIndexingManager(); + if (!indexManager) return; const client = MatrixClientPeg.get(); const rooms = client.getRooms(); @@ -160,6 +162,7 @@ export default class EventIndex extends EventEmitter { */ private onSync = async (state: SyncState, prevState: SyncState | null, data?: ISyncStateData): Promise => { const indexManager = PlatformPeg.get()?.getEventIndexingManager(); + if (!indexManager) return; if (prevState === "PREPARED" && state === "SYNCING") { // If our indexer is empty we're most likely running Element the @@ -230,6 +233,7 @@ export default class EventIndex extends EventEmitter { */ private redactEvent = async (ev: MatrixEvent): Promise => { const indexManager = PlatformPeg.get()?.getEventIndexingManager(); + if (!indexManager) return; try { await indexManager.deleteEvent(ev.getAssociatedId()); @@ -355,6 +359,7 @@ export default class EventIndex extends EventEmitter { private async addRoomCheckpoint(roomId: string, fullCrawl = false): Promise { const indexManager = PlatformPeg.get()?.getEventIndexingManager(); + if (!indexManager) return; const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); @@ -403,6 +408,7 @@ export default class EventIndex extends EventEmitter { const client = MatrixClientPeg.get(); const indexManager = PlatformPeg.get()?.getEventIndexingManager(); + if (!indexManager) return; this.crawler = { cancel: () => { @@ -653,7 +659,7 @@ export default class EventIndex extends EventEmitter { const indexManager = PlatformPeg.get()?.getEventIndexingManager(); this.removeListeners(); this.stopCrawler(); - await indexManager.closeEventIndex(); + await indexManager?.closeEventIndex(); } /** @@ -665,9 +671,9 @@ export default class EventIndex extends EventEmitter { * @return {Promise} A promise that will resolve to an array * of search results once the search is done. */ - public async search(searchArgs: ISearchArgs): Promise { + public async search(searchArgs: ISearchArgs): Promise { const indexManager = PlatformPeg.get()?.getEventIndexingManager(); - return indexManager.searchEventIndex(searchArgs); + return indexManager?.searchEventIndex(searchArgs); } /** @@ -699,6 +705,7 @@ export default class EventIndex extends EventEmitter { ): Promise { const client = MatrixClientPeg.get(); const indexManager = PlatformPeg.get()?.getEventIndexingManager(); + if (!indexManager) return []; const loadArgs: ILoadArgs = { roomId: room.roomId, diff --git a/src/integrations/IntegrationManagers.ts b/src/integrations/IntegrationManagers.ts index c1f8eb5a3c..9adcb56162 100644 --- a/src/integrations/IntegrationManagers.ts +++ b/src/integrations/IntegrationManagers.ts @@ -74,8 +74,8 @@ export class IntegrationManagers { } private setupConfiguredManager(): void { - const apiUrl: string = SdkConfig.get("integrations_rest_url"); - const uiUrl: string = SdkConfig.get("integrations_ui_url"); + const apiUrl = SdkConfig.get("integrations_rest_url"); + const uiUrl = SdkConfig.get("integrations_ui_url"); if (apiUrl && uiUrl) { this.managers.push(new IntegrationManagerInstance(Kind.Config, apiUrl, uiUrl)); diff --git a/src/linkify-matrix.ts b/src/linkify-matrix.ts index 29258558ce..3369a18157 100644 --- a/src/linkify-matrix.ts +++ b/src/linkify-matrix.ts @@ -172,16 +172,16 @@ export const options: Opts = { return { // @ts-ignore see https://linkify.js.org/docs/options.html click: function (e: MouseEvent) { - const userId = parsePermalink(href).userId; - onUserClick(e, userId); + const userId = parsePermalink(href)?.userId; + if (userId) onUserClick(e, userId); }, }; case Type.RoomAlias: return { // @ts-ignore see https://linkify.js.org/docs/options.html click: function (e: MouseEvent) { - const alias = parsePermalink(href).roomIdOrAlias; - onAliasClick(e, alias); + const alias = parsePermalink(href)?.roomIdOrAlias; + if (alias) onAliasClick(e, alias); }, }; } diff --git a/src/mjolnir/BanList.ts b/src/mjolnir/BanList.ts index d3c4fbc143..7eec5d3b26 100644 --- a/src/mjolnir/BanList.ts +++ b/src/mjolnir/BanList.ts @@ -29,7 +29,7 @@ export const ROOM_RULE_TYPES = [RULE_ROOM, "m.room.rule.room", "org.matrix.mjoln export const SERVER_RULE_TYPES = [RULE_SERVER, "m.room.rule.server", "org.matrix.mjolnir.rule.server"]; export const ALL_RULE_TYPES = [...USER_RULE_TYPES, ...ROOM_RULE_TYPES, ...SERVER_RULE_TYPES]; -export function ruleTypeToStable(rule: string): string { +export function ruleTypeToStable(rule: string): string | null { if (USER_RULE_TYPES.includes(rule)) { return RULE_USER; } @@ -68,9 +68,11 @@ export class BanList { } public async banEntity(kind: string, entity: string, reason: string): Promise { + const type = ruleTypeToStable(kind); + if (!type) return; // unknown rule type await MatrixClientPeg.get().sendStateEvent( this._roomId, - ruleTypeToStable(kind), + type, { entity: entity, reason: reason, @@ -78,12 +80,14 @@ export class BanList { }, "rule:" + entity, ); - this._rules.push(new ListRule(entity, RECOMMENDATION_BAN, reason, ruleTypeToStable(kind))); + this._rules.push(new ListRule(entity, RECOMMENDATION_BAN, reason, type)); } public async unbanEntity(kind: string, entity: string): Promise { + const type = ruleTypeToStable(kind); + if (!type) return; // unknown rule type // Empty state event is effectively deleting it. - await MatrixClientPeg.get().sendStateEvent(this._roomId, ruleTypeToStable(kind), {}, "rule:" + entity); + await MatrixClientPeg.get().sendStateEvent(this._roomId, type, {}, "rule:" + entity); this._rules = this._rules.filter((r) => { if (r.kind !== ruleTypeToStable(kind)) return true; if (r.entity !== entity) return true; @@ -103,6 +107,7 @@ export class BanList { if (!ev.getStateKey()) continue; const kind = ruleTypeToStable(eventType); + if (!kind) continue; // unknown type const entity = ev.getContent()["entity"]; const recommendation = ev.getContent()["recommendation"]; diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts index ec0d6b4594..dce8799829 100644 --- a/src/rageshake/submit-rageshake.ts +++ b/src/rageshake/submit-rageshake.ts @@ -40,15 +40,12 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true): Promise
    {}); progressCallback(_t("Collecting app version information")); - let version = "UNKNOWN"; + let version: string | undefined; try { - version = await PlatformPeg.get().getAppVersion(); + version = await PlatformPeg.get()?.getAppVersion(); } catch (err) {} // PlatformPeg already logs this. - let userAgent = "UNKNOWN"; - if (window.navigator && window.navigator.userAgent) { - userAgent = window.navigator.userAgent; - } + const userAgent = window.navigator?.userAgent ?? "UNKNOWN"; let installedPWA = "UNKNOWN"; try { @@ -69,7 +66,7 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true): Promise { public readonly domNode: HTMLElement; - protected readonly id: string; + protected readonly id: string | null; protected reverse: boolean; public constructor( diff --git a/src/resizer/resizer.ts b/src/resizer/resizer.ts index a69b2c446e..4f5c97f72c 100644 --- a/src/resizer/resizer.ts +++ b/src/resizer/resizer.ts @@ -34,7 +34,7 @@ interface IClassNames { export interface IConfig { onResizeStart?(): void; onResizeStop?(): void; - onResized?(size: number, id: string, element: HTMLElement): void; + onResized?(size: number | null, id: string | null, element: HTMLElement): void; handler?: HTMLDivElement; } @@ -51,7 +51,7 @@ export default class Resizer { resizeHandle: HTMLDivElement, resizer: Resizer, sizer: Sizer, - container: HTMLElement, + container?: HTMLElement, ): ResizeItem; createSizer(containerElement: HTMLElement, vertical: boolean, reverse: boolean): Sizer; }, diff --git a/src/sentry.ts b/src/sentry.ts index a9cd1f18fc..fbb52dbb14 100644 --- a/src/sentry.ts +++ b/src/sentry.ts @@ -53,8 +53,8 @@ type CryptoContext = { }; type DeviceContext = { - device_id: string; - mx_local_settings: string; + device_id?: string; + mx_local_settings: string | null; modernizr_missing_features?: string; }; diff --git a/src/stores/ThreepidInviteStore.ts b/src/stores/ThreepidInviteStore.ts index 742856ff19..6dfb7d0561 100644 --- a/src/stores/ThreepidInviteStore.ts +++ b/src/stores/ThreepidInviteStore.ts @@ -82,7 +82,7 @@ export default class ThreepidInviteStore extends EventEmitter { const results: IPersistedThreepidInvite[] = []; for (let i = 0; i < localStorage.length; i++) { const keyName = localStorage.key(i); - if (!keyName.startsWith(STORAGE_PREFIX)) continue; + if (!keyName?.startsWith(STORAGE_PREFIX)) continue; results.push(JSON.parse(localStorage.getItem(keyName)) as IPersistedThreepidInvite); } return results; diff --git a/src/stores/right-panel/RightPanelStore.ts b/src/stores/right-panel/RightPanelStore.ts index 96b1432ca7..8e8155c4bc 100644 --- a/src/stores/right-panel/RightPanelStore.ts +++ b/src/stores/right-panel/RightPanelStore.ts @@ -160,7 +160,7 @@ export default class RightPanelStore extends ReadyWatchingStore { } } - public setCards(cards: IRightPanelCard[], allowClose = true, roomId: string = null): void { + public setCards(cards: IRightPanelCard[], allowClose = true, roomId: string | null = null): void { // This function sets the history of the right panel and shows the right panel if not already visible. const rId = roomId ?? this.viewedRoomId; const history = cards.map((c) => ({ phase: c.phase, state: c.state ?? {} })); @@ -170,7 +170,7 @@ export default class RightPanelStore extends ReadyWatchingStore { } // Appends a card to the history and shows the right panel if not already visible - public pushCard(card: IRightPanelCard, allowClose = true, roomId: string = null): void { + public pushCard(card: IRightPanelCard, allowClose = true, roomId: string | null = null): void { const rId = roomId ?? this.viewedRoomId; const redirect = this.getVerificationRedirect(card); const targetPhase = redirect?.phase ?? card.phase; @@ -196,7 +196,7 @@ export default class RightPanelStore extends ReadyWatchingStore { this.emitAndUpdateSettings(); } - public popCard(roomId: string = null): IRightPanelCard { + public popCard(roomId: string | null = null): IRightPanelCard | undefined { const rId = roomId ?? this.viewedRoomId; if (!this.byRoom[rId]) return; @@ -304,7 +304,7 @@ export default class RightPanelStore extends ReadyWatchingStore { return true; } - private getVerificationRedirect(card: IRightPanelCard): IRightPanelCard { + private getVerificationRedirect(card: IRightPanelCard): IRightPanelCard | null { if (card.phase === RightPanelPhases.RoomMemberInfo && card.state) { // RightPanelPhases.RoomMemberInfo -> needs to be changed to RightPanelPhases.EncryptionPanel if there is a pending verification request const { member } = card.state; @@ -322,8 +322,8 @@ export default class RightPanelStore extends ReadyWatchingStore { return null; } - private isPhaseValid(targetPhase: RightPanelPhases, isViewingRoom: boolean): boolean { - if (!RightPanelPhases[targetPhase]) { + private isPhaseValid(targetPhase: RightPanelPhases | null, isViewingRoom: boolean): boolean { + if (!targetPhase || !RightPanelPhases[targetPhase]) { logger.warn(`Tried to switch right panel to unknown phase: ${targetPhase}`); return false; } diff --git a/src/stores/right-panel/RightPanelStoreIPanelState.ts b/src/stores/right-panel/RightPanelStoreIPanelState.ts index 34b6d03ed4..8064f24c43 100644 --- a/src/stores/right-panel/RightPanelStoreIPanelState.ts +++ b/src/stores/right-panel/RightPanelStoreIPanelState.ts @@ -52,7 +52,7 @@ export interface IRightPanelCardStateStored { } export interface IRightPanelCard { - phase: RightPanelPhases; + phase: RightPanelPhases | null; state?: IRightPanelCardState; } @@ -90,10 +90,10 @@ export function convertCardToStore(panelState: IRightPanelCard): IRightPanelCard spaceId: state.spaceId, isInitialEventHighlighted: state.isInitialEventHighlighted, initialEventScrollIntoView: state.initialEventScrollIntoView, - threadHeadEventId: !!state?.threadHeadEvent?.getId() ? panelState.state.threadHeadEvent.getId() : undefined, - memberInfoEventId: !!state?.memberInfoEvent?.getId() ? panelState.state.memberInfoEvent.getId() : undefined, - initialEventId: !!state?.initialEvent?.getId() ? panelState.state.initialEvent.getId() : undefined, - memberId: !!state?.member?.userId ? panelState.state.member.userId : undefined, + threadHeadEventId: !!state?.threadHeadEvent?.getId() ? state.threadHeadEvent.getId() : undefined, + memberInfoEventId: !!state?.memberInfoEvent?.getId() ? state.memberInfoEvent.getId() : undefined, + initialEventId: !!state?.initialEvent?.getId() ? state.initialEvent.getId() : undefined, + memberId: !!state?.member?.userId ? state.member.userId : undefined, }; return { state: stateStored, phase: panelState.phase }; @@ -113,7 +113,7 @@ function convertStoreToCard(panelStateStore: IRightPanelCardStored, room: Room): ? room.findEventById(stateStored.memberInfoEventId) : undefined, initialEvent: !!stateStored?.initialEventId ? room.findEventById(stateStored.initialEventId) : undefined, - member: !!stateStored?.memberId ? room.getMember(stateStored.memberId) : undefined, + member: (!!stateStored?.memberId && room.getMember(stateStored.memberId)) || undefined, }; return { state: state, phase: panelStateStore.phase }; diff --git a/src/stores/room-list/Interface.ts b/src/stores/room-list/Interface.ts index 55de9dd3ad..4e30182e94 100644 --- a/src/stores/room-list/Interface.ts +++ b/src/stores/room-list/Interface.ts @@ -57,7 +57,7 @@ export interface RoomListStore extends EventEmitter { * @param tagId tag to get the sort algorithm for * @returns the sort algorithm */ - getTagSorting(tagId: TagID): SortAlgorithm; + getTagSorting(tagId: TagID): SortAlgorithm | null; /** * Set the list algorithm for the specified tag. @@ -71,7 +71,7 @@ export interface RoomListStore extends EventEmitter { * @param tagId tag to get the list algorithm for * @returns the list algorithm */ - getListOrder(tagId: TagID): ListAlgorithm; + getListOrder(tagId: TagID): ListAlgorithm | null; /** * Regenerates the room whole room list, discarding any previous results. diff --git a/src/stores/room-list/RoomListLayoutStore.ts b/src/stores/room-list/RoomListLayoutStore.ts index 5b7ec0692a..ce8bcf836d 100644 --- a/src/stores/room-list/RoomListLayoutStore.ts +++ b/src/stores/room-list/RoomListLayoutStore.ts @@ -51,7 +51,7 @@ export default class RoomListLayoutStore extends AsyncStoreWithClient { if (!this.layoutMap.has(tagId)) { this.layoutMap.set(tagId, new ListLayout(tagId)); } - return this.layoutMap.get(tagId); + return this.layoutMap.get(tagId)!; } // Note: this primarily exists for debugging, and isn't really intended to be used by anything. diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts index 0fd511e8a8..e7032f1073 100644 --- a/src/stores/room-list/RoomListStore.ts +++ b/src/stores/room-list/RoomListStore.ts @@ -404,7 +404,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient implements localStorage.setItem(`mx_tagSort_${tagId}`, sort); } - public getTagSorting(tagId: TagID): SortAlgorithm { + public getTagSorting(tagId: TagID): SortAlgorithm | null { return this.algorithm.getTagSorting(tagId); } @@ -443,7 +443,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient implements localStorage.setItem(`mx_listOrder_${tagId}`, order); } - public getListOrder(tagId: TagID): ListAlgorithm { + public getListOrder(tagId: TagID): ListAlgorithm | null { return this.algorithm.getListOrdering(tagId); } diff --git a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts index e9af79fa1a..b6ee070c03 100644 --- a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts +++ b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts @@ -56,7 +56,7 @@ export const sortRooms = (rooms: Room[]): Room[] => { // See https://github.com/vector-im/element-web/issues/14458 let myUserId = ""; if (MatrixClientPeg.get()) { - myUserId = MatrixClientPeg.get().getUserId(); + myUserId = MatrixClientPeg.get().getUserId()!; } const tsCache: { [roomId: string]: number } = {}; diff --git a/src/stores/room-list/previews/LegacyCallAnswerEventPreview.ts b/src/stores/room-list/previews/LegacyCallAnswerEventPreview.ts index 68826b2611..e8a604e31e 100644 --- a/src/stores/room-list/previews/LegacyCallAnswerEventPreview.ts +++ b/src/stores/room-list/previews/LegacyCallAnswerEventPreview.ts @@ -23,7 +23,7 @@ import { _t } from "../../../languageHandler"; export class LegacyCallAnswerEventPreview implements IPreview { public getTextFor(event: MatrixEvent, tagId?: TagID): string { - if (shouldPrefixMessagesIn(event.getRoomId(), tagId)) { + if (shouldPrefixMessagesIn(event.getRoomId()!, tagId)) { if (isSelf(event)) { return _t("You joined the call"); } else { diff --git a/src/stores/room-list/previews/LegacyCallHangupEvent.ts b/src/stores/room-list/previews/LegacyCallHangupEvent.ts index 0896e43bf0..397a491bcd 100644 --- a/src/stores/room-list/previews/LegacyCallHangupEvent.ts +++ b/src/stores/room-list/previews/LegacyCallHangupEvent.ts @@ -23,7 +23,7 @@ import { _t } from "../../../languageHandler"; export class LegacyCallHangupEvent implements IPreview { public getTextFor(event: MatrixEvent, tagId?: TagID): string { - if (shouldPrefixMessagesIn(event.getRoomId(), tagId)) { + if (shouldPrefixMessagesIn(event.getRoomId()!, tagId)) { if (isSelf(event)) { return _t("You ended the call"); } else { diff --git a/src/stores/room-list/previews/LegacyCallInviteEventPreview.ts b/src/stores/room-list/previews/LegacyCallInviteEventPreview.ts index 1aff65decc..0afeaa56da 100644 --- a/src/stores/room-list/previews/LegacyCallInviteEventPreview.ts +++ b/src/stores/room-list/previews/LegacyCallInviteEventPreview.ts @@ -23,7 +23,7 @@ import { _t } from "../../../languageHandler"; export class LegacyCallInviteEventPreview implements IPreview { public getTextFor(event: MatrixEvent, tagId?: TagID): string { - if (shouldPrefixMessagesIn(event.getRoomId(), tagId)) { + if (shouldPrefixMessagesIn(event.getRoomId()!, tagId)) { if (isSelf(event)) { return _t("You started a call"); } else { diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 3405155382..6faac2a485 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -36,7 +36,7 @@ import { DefaultTagID } from "../room-list/models"; import { EnhancedMap, mapDiff } from "../../utils/maps"; import { setDiff, setHasDiff } from "../../utils/sets"; import { Action } from "../../dispatcher/actions"; -import { arrayHasDiff, arrayHasOrderChange } from "../../utils/arrays"; +import { arrayHasDiff, arrayHasOrderChange, filterBoolean } from "../../utils/arrays"; import { reorderLexicographically } from "../../utils/stringOrderField"; import { TAG_ORDER } from "../../components/views/rooms/RoomList"; import { SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload"; @@ -373,31 +373,29 @@ export class SpaceStoreClass extends AsyncStoreWithClient { public getParents(roomId: string, canonicalOnly = false): Room[] { const userId = this.matrixClient?.getUserId(); const room = this.matrixClient?.getRoom(roomId); - return ( - (room?.currentState - .getStateEvents(EventType.SpaceParent) - .map((ev) => { - const content = ev.getContent(); - if (!Array.isArray(content.via) || (canonicalOnly && !content.canonical)) { - return; // skip - } + const events = room?.currentState.getStateEvents(EventType.SpaceParent) ?? []; + return filterBoolean( + events.map((ev) => { + const content = ev.getContent(); + if (!Array.isArray(content.via) || (canonicalOnly && !content.canonical)) { + return; // skip + } - // only respect the relationship if the sender has sufficient permissions in the parent to set - // child relations, as per MSC1772. - // https://github.com/matrix-org/matrix-doc/blob/main/proposals/1772-groups-as-rooms.md#relationship-between-rooms-and-spaces - const parent = this.matrixClient.getRoom(ev.getStateKey()); - const relation = parent?.currentState.getStateEvents(EventType.SpaceChild, roomId); - if ( - !parent?.currentState.maySendStateEvent(EventType.SpaceChild, userId) || - // also skip this relation if the parent had this child added but then since removed it - (relation && !Array.isArray(relation.getContent().via)) - ) { - return; // skip - } + // only respect the relationship if the sender has sufficient permissions in the parent to set + // child relations, as per MSC1772. + // https://github.com/matrix-org/matrix-doc/blob/main/proposals/1772-groups-as-rooms.md#relationship-between-rooms-and-spaces + const parent = this.matrixClient.getRoom(ev.getStateKey()); + const relation = parent?.currentState.getStateEvents(EventType.SpaceChild, roomId); + if ( + !parent?.currentState.maySendStateEvent(EventType.SpaceChild, userId) || + // also skip this relation if the parent had this child added but then since removed it + (relation && !Array.isArray(relation.getContent().via)) + ) { + return; // skip + } - return parent; - }) - .filter(Boolean) as Room[]) || [] + return parent; + }), ); } diff --git a/src/stores/widgets/WidgetLayoutStore.ts b/src/stores/widgets/WidgetLayoutStore.ts index a204b7d6ed..74a6d6c3ee 100644 --- a/src/stores/widgets/WidgetLayoutStore.ts +++ b/src/stores/widgets/WidgetLayoutStore.ts @@ -99,7 +99,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { [roomId: string]: Partial<{ [container in Container]: { ordered: IApp[]; - height?: number; + height?: number | null; distributions?: number[]; }; }>; @@ -402,11 +402,11 @@ export class WidgetLayoutStore extends ReadyWatchingStore { this.updateUserLayout(room, localLayout); } - public getContainerHeight(room: Room, container: Container): number { - return this.byRoom[room.roomId]?.[container]?.height; // let the default get returned if needed + public getContainerHeight(room: Room, container: Container): number | null { + return this.byRoom[room.roomId]?.[container]?.height ?? null; // let the default get returned if needed } - public setContainerHeight(room: Room, container: Container, height: number): void { + public setContainerHeight(room: Room, container: Container, height?: number): void { const widgets = this.getContainerWidgets(room, container); const widths = this.byRoom[room.roomId]?.[container]?.distributions; const localLayout: Record = {}; @@ -502,8 +502,8 @@ export class WidgetLayoutStore extends ReadyWatchingStore { const height = this.byRoom[room.roomId]?.[container]?.height; evContent.widgets[widget.id] = { ...evContent.widgets[widget.id], - height: height ? Math.round(height) : null, - width: widths[idx] ? Math.round(widths[idx]) : null, + height: height ? Math.round(height) : undefined, + width: widths?.[idx] ? Math.round(widths[idx]) : undefined, index: idx, }; } diff --git a/src/toasts/DesktopNotificationsToast.ts b/src/toasts/DesktopNotificationsToast.ts index bb64272f97..856c6d9dd6 100644 --- a/src/toasts/DesktopNotificationsToast.ts +++ b/src/toasts/DesktopNotificationsToast.ts @@ -24,7 +24,7 @@ import { getLocalNotificationAccountDataEventType } from "../utils/notifications const onAccept = (): void => { Notifier.setEnabled(true); const cli = MatrixClientPeg.get(); - const eventType = getLocalNotificationAccountDataEventType(cli.deviceId); + const eventType = getLocalNotificationAccountDataEventType(cli.deviceId!); cli.setAccountData(eventType, { is_silenced: false, }); diff --git a/src/utils/Reply.ts b/src/utils/Reply.ts index be59f72ec9..ffa29b6ffd 100644 --- a/src/utils/Reply.ts +++ b/src/utils/Reply.ts @@ -218,17 +218,20 @@ export function shouldDisplayReply(event: MatrixEvent): boolean { return !!inReplyTo.event_id; } -interface IAddReplyOpts { +interface AddReplyOpts { permalinkCreator?: RoomPermalinkCreator; - includeLegacyFallback?: boolean; + includeLegacyFallback: false; +} + +interface IncludeLegacyFeedbackOpts { + permalinkCreator: RoomPermalinkCreator; + includeLegacyFallback: true; } export function addReplyToMessageContent( content: IContent, replyToEvent: MatrixEvent, - opts: IAddReplyOpts = { - includeLegacyFallback: true, - }, + opts: AddReplyOpts | IncludeLegacyFeedbackOpts, ): void { content["m.relates_to"] = { ...(content["m.relates_to"] || {}), diff --git a/src/utils/SortMembers.ts b/src/utils/SortMembers.ts index 4bc17be2c1..2f63dacd7b 100644 --- a/src/utils/SortMembers.ts +++ b/src/utils/SortMembers.ts @@ -64,7 +64,7 @@ interface IActivityScore { // We do this by checking every room to see who has sent a message in the last few hours, and giving them // a score which correlates to the freshness of their message. In theory, this results in suggestions // which are closer to "continue this conversation" rather than "this person exists". -export function buildActivityScores(cli: MatrixClient): { [key: string]: IActivityScore } { +export function buildActivityScores(cli: MatrixClient): { [key: string]: IActivityScore | undefined } { const now = new Date().getTime(); const earliestAgeConsidered = now - 60 * 60 * 1000; // 1 hour ago const maxMessagesConsidered = 50; // so we don't iterate over a huge amount of traffic @@ -73,7 +73,8 @@ export function buildActivityScores(cli: MatrixClient): { [key: string]: IActivi .filter((ev) => ev.getTs() > earliestAgeConsidered); const senderEvents = groupBy(events, (ev) => ev.getSender()); return mapValues(senderEvents, (events) => { - const lastEvent = maxBy(events, (ev) => ev.getTs()); + if (!events.length) return; + const lastEvent = maxBy(events, (ev) => ev.getTs())!; const distanceFromNow = Math.abs(now - lastEvent.getTs()); // abs to account for slight future messages const inverseTime = now - earliestAgeConsidered - distanceFromNow; return { @@ -92,7 +93,7 @@ interface IMemberScore { numRooms: number; } -export function buildMemberScores(cli: MatrixClient): { [key: string]: IMemberScore } { +export function buildMemberScores(cli: MatrixClient): { [key: string]: IMemberScore | undefined } { const maxConsideredMembers = 200; const consideredRooms = joinedRooms(cli).filter((room) => room.getJoinedMemberCount() < maxConsideredMembers); const memberPeerEntries = consideredRooms.flatMap((room) => @@ -100,10 +101,11 @@ export function buildMemberScores(cli: MatrixClient): { [key: string]: IMemberSc ); const userMeta = groupBy(memberPeerEntries, ({ member }) => member.userId); return mapValues(userMeta, (roomMemberships) => { + if (!roomMemberships.length) return; const maximumPeers = maxConsideredMembers * roomMemberships.length; const totalPeers = sumBy(roomMemberships, (entry) => entry.roomSize); return { - member: minBy(roomMemberships, (entry) => entry.roomSize).member, + member: minBy(roomMemberships, (entry) => entry.roomSize)!.member, numRooms: roomMemberships.length, score: Math.max(0, Math.pow(1 - totalPeers / maximumPeers, 5)), }; diff --git a/src/utils/arrays.ts b/src/utils/arrays.ts index d5436c601c..bbc75fa4f7 100644 --- a/src/utils/arrays.ts +++ b/src/utils/arrays.ts @@ -323,3 +323,7 @@ export async function asyncEvery(values: T[], predicate: (value: T) => Promis } return true; } + +export function filterBoolean(values: Array): T[] { + return values.filter(Boolean) as T[]; +} diff --git a/src/utils/beacon/getShareableLocation.ts b/src/utils/beacon/getShareableLocation.ts index 1b353f2b04..e6652187a9 100644 --- a/src/utils/beacon/getShareableLocation.ts +++ b/src/utils/beacon/getShareableLocation.ts @@ -23,7 +23,7 @@ import { MatrixClient, MatrixEvent, getBeaconInfoIdentifier } from "matrix-js-sd */ export const getShareableLocationEventForBeacon = (event: MatrixEvent, cli: MatrixClient): MatrixEvent | null => { const room = cli.getRoom(event.getRoomId()); - const beacon = room.currentState.beacons?.get(getBeaconInfoIdentifier(event)); + const beacon = room?.currentState.beacons?.get(getBeaconInfoIdentifier(event)); const latestLocationEvent = beacon?.latestLocationEvent; if (beacon?.isLive && latestLocationEvent) { diff --git a/src/utils/dm/startDm.ts b/src/utils/dm/startDm.ts index bea04c1ed6..8d54292c71 100644 --- a/src/utils/dm/startDm.ts +++ b/src/utils/dm/startDm.ts @@ -39,7 +39,7 @@ export async function startDm(client: MatrixClient, targets: Member[], showSpinn if (targetIds.length === 1) { existingRoom = findDMForUser(client, targetIds[0]); } else { - existingRoom = DMRoomMap.shared().getDMRoomForIdentifiers(targetIds); + existingRoom = DMRoomMap.shared().getDMRoomForIdentifiers(targetIds) ?? undefined; } if (existingRoom && !isLocalRoom(existingRoom)) { dis.dispatch({ diff --git a/src/utils/location/parseGeoUri.ts b/src/utils/location/parseGeoUri.ts index 0ad4adbad9..c0de9cf416 100644 --- a/src/utils/location/parseGeoUri.ts +++ b/src/utils/location/parseGeoUri.ts @@ -15,10 +15,10 @@ limitations under the License. */ export const parseGeoUri = (uri: string): GeolocationCoordinates | undefined => { - function parse(s: string): number | undefined { + function parse(s: string): number | null { const ret = parseFloat(s); if (Number.isNaN(ret)) { - return undefined; + return null; } else { return ret; } @@ -28,7 +28,7 @@ export const parseGeoUri = (uri: string): GeolocationCoordinates | undefined => if (!m) return; const parts = m[1].split(";"); const coords = parts[0].split(","); - let uncertainty: number | undefined; + let uncertainty: number | null; for (const param of parts.slice(1)) { const m = param.match(/u=(.*)/); if (m) uncertainty = parse(m[1]); @@ -38,8 +38,8 @@ export const parseGeoUri = (uri: string): GeolocationCoordinates | undefined => longitude: parse(coords[1]), altitude: parse(coords[2]), accuracy: uncertainty, - altitudeAccuracy: undefined, - heading: undefined, - speed: undefined, + altitudeAccuracy: null, + heading: null, + speed: null, }; }; diff --git a/src/utils/permalinks/Permalinks.ts b/src/utils/permalinks/Permalinks.ts index a209e9dfc4..1350868094 100644 --- a/src/utils/permalinks/Permalinks.ts +++ b/src/utils/permalinks/Permalinks.ts @@ -84,7 +84,7 @@ export class RoomPermalinkCreator { private populationMap: { [serverName: string]: number } | null = null; private bannedHostsRegexps: RegExp[] | null = null; private allowedHostsRegexps: RegExp[] | null = null; - private _serverCandidates: string[] | null = null; + private _serverCandidates?: string[]; private started = false; // We support being given a roomId as a fallback in the event the `room` object @@ -124,7 +124,7 @@ export class RoomPermalinkCreator { this.started = false; } - public get serverCandidates(): string[] { + public get serverCandidates(): string[] | undefined { return this._serverCandidates; } diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts index 7c950e75fe..22f5022f21 100644 --- a/src/utils/stringOrderField.ts +++ b/src/utils/stringOrderField.ts @@ -54,7 +54,7 @@ export function midPointsBetweenStrings( interface IEntry { index: number; - order: string; + order?: string; } export const reorderLexicographically = ( @@ -82,12 +82,12 @@ export const reorderLexicographically = ( let canMoveLeft = true; const nextBase = newOrder[toIndex + 1]?.order !== undefined - ? stringToBase(newOrder[toIndex + 1].order) + ? stringToBase(newOrder[toIndex + 1].order!) : BigInt(Number.MAX_VALUE); // check how far left we would have to mutate to fit in that direction for (let i = toIndex - 1, j = 1; i >= 0; i--, j++) { - if (newOrder[i]?.order !== undefined && nextBase - stringToBase(newOrder[i].order) > j) break; + if (newOrder[i]?.order !== undefined && nextBase - stringToBase(newOrder[i].order!) > j) break; leftBoundIdx = i; } @@ -108,12 +108,12 @@ export const reorderLexicographically = ( if (canDisplaceRight) { const prevBase = newOrder[toIndex - 1]?.order !== undefined - ? stringToBase(newOrder[toIndex - 1]?.order) + ? stringToBase(newOrder[toIndex - 1].order!) : BigInt(Number.MIN_VALUE); // check how far right we would have to mutate to fit in that direction for (let i = toIndex + 1, j = 1; i < newOrder.length; i++, j++) { - if (newOrder[i]?.order === undefined || stringToBase(newOrder[i].order) - prevBase > j) break; + if (newOrder[i]?.order === undefined || stringToBase(newOrder[i].order!) - prevBase > j) break; rightBoundIdx = i; } diff --git a/src/utils/strings.ts b/src/utils/strings.ts index aef2ea850c..23ea37ed28 100644 --- a/src/utils/strings.ts +++ b/src/utils/strings.ts @@ -37,7 +37,7 @@ export async function copyPlaintext(text: string): Promise { textArea.style.position = "fixed"; document.body.appendChild(textArea); - const selection = document.getSelection(); + const selection = document.getSelection()!; const range = document.createRange(); // range.selectNodeContents(textArea); range.selectNode(textArea); @@ -59,7 +59,7 @@ export function selectText(target: Element): void { const range = document.createRange(); range.selectNodeContents(target); - const selection = window.getSelection(); + const selection = window.getSelection()!; selection.removeAllRanges(); selection.addRange(range); } @@ -80,5 +80,5 @@ export function copyNode(ref: Element): boolean { * @returns the selected text */ export function getSelectedText(): string { - return window.getSelection().toString(); + return window.getSelection()!.toString(); } diff --git a/src/voice-broadcast/hooks/useCurrentVoiceBroadcastRecording.ts b/src/voice-broadcast/hooks/useCurrentVoiceBroadcastRecording.ts index a9b9635ba7..18283bcf04 100644 --- a/src/voice-broadcast/hooks/useCurrentVoiceBroadcastRecording.ts +++ b/src/voice-broadcast/hooks/useCurrentVoiceBroadcastRecording.ts @@ -20,7 +20,7 @@ import { useTypedEventEmitterState } from "../../hooks/useEventEmitter"; export const useCurrentVoiceBroadcastRecording = ( voiceBroadcastRecordingsStore: VoiceBroadcastRecordingsStore, ): { - currentVoiceBroadcastRecording: VoiceBroadcastRecording; + currentVoiceBroadcastRecording: VoiceBroadcastRecording | null; } => { const currentVoiceBroadcastRecording = useTypedEventEmitterState( voiceBroadcastRecordingsStore, diff --git a/test/accessibility/RovingTabIndex-test.tsx b/test/accessibility/RovingTabIndex-test.tsx index 54625b7ef5..4a2e67fece 100644 --- a/test/accessibility/RovingTabIndex-test.tsx +++ b/test/accessibility/RovingTabIndex-test.tsx @@ -185,7 +185,6 @@ describe("RovingTabIndex", () => { const ref4 = React.createRef(); let state: IState = { - activeRef: null, refs: [ref1, ref2, ref3, ref4], }; @@ -196,7 +195,6 @@ describe("RovingTabIndex", () => { }, }); expect(state).toStrictEqual({ - activeRef: null, refs: [ref1, ref3, ref4], }); @@ -207,7 +205,6 @@ describe("RovingTabIndex", () => { }, }); expect(state).toStrictEqual({ - activeRef: null, refs: [ref1, ref4], }); @@ -218,7 +215,6 @@ describe("RovingTabIndex", () => { }, }); expect(state).toStrictEqual({ - activeRef: null, refs: [ref1], }); @@ -229,7 +225,6 @@ describe("RovingTabIndex", () => { }, }); expect(state).toStrictEqual({ - activeRef: null, refs: [], }); }); @@ -250,7 +245,6 @@ describe("RovingTabIndex", () => { ); let state: IState = { - activeRef: null, refs: [], }; diff --git a/test/components/views/context_menus/__snapshots__/EmbeddedPage-test.tsx.snap b/test/components/views/context_menus/__snapshots__/EmbeddedPage-test.tsx.snap index f5f874e275..fd88987845 100644 --- a/test/components/views/context_menus/__snapshots__/EmbeddedPage-test.tsx.snap +++ b/test/components/views/context_menus/__snapshots__/EmbeddedPage-test.tsx.snap @@ -3,7 +3,7 @@ exports[` should render nothing if no url given 1`] = `
    should render nothing if no url given 1`] = ` exports[` should show error if unable to load 1`] = `
    should show error if unable to load 1`] = ` exports[` should translate _t strings 1`] = `
    { diff --git a/test/components/views/dialogs/SpotlightDialog-test.tsx b/test/components/views/dialogs/SpotlightDialog-test.tsx index 08e746bb1d..276c42d5d5 100644 --- a/test/components/views/dialogs/SpotlightDialog-test.tsx +++ b/test/components/views/dialogs/SpotlightDialog-test.tsx @@ -97,7 +97,7 @@ function mockClient({ ); return Promise.resolve({ results: results.slice(0, limit ?? +Infinity), - limited: limit && limit < results.length, + limited: !!limit && limit < results.length, }); }); cli.getProfileInfo = jest.fn(async (userId) => { diff --git a/test/components/views/dialogs/UserSettingsDialog-test.tsx b/test/components/views/dialogs/UserSettingsDialog-test.tsx index c0cf9fccec..190d05a0b6 100644 --- a/test/components/views/dialogs/UserSettingsDialog-test.tsx +++ b/test/components/views/dialogs/UserSettingsDialog-test.tsx @@ -74,8 +74,8 @@ describe("", () => { }); const getActiveTabLabel = (container: Element) => - container.querySelector(".mx_TabbedView_tabLabel_active").textContent; - const getActiveTabHeading = (container: Element) => container.querySelector(".mx_SettingsTab_heading").textContent; + container.querySelector(".mx_TabbedView_tabLabel_active")?.textContent; + const getActiveTabHeading = (container: Element) => container.querySelector(".mx_SettingsTab_heading")?.textContent; it("should render general settings tab when no initialTabId", () => { const { container } = render(getComponent()); diff --git a/test/components/views/location/LocationShareMenu-test.tsx b/test/components/views/location/LocationShareMenu-test.tsx index 5aeb79b922..42331429d3 100644 --- a/test/components/views/location/LocationShareMenu-test.tsx +++ b/test/components/views/location/LocationShareMenu-test.tsx @@ -146,7 +146,7 @@ describe("", () => { const setLocationGeolocate = () => { // get the callback LocationShareMenu registered for geolocate expect(mocked(mockGeolocate.on)).toHaveBeenCalledWith("geolocate", expect.any(Function)); - const [, onGeolocateCallback] = mocked(mockGeolocate.on).mock.calls.find(([event]) => event === "geolocate"); + const [, onGeolocateCallback] = mocked(mockGeolocate.on).mock.calls.find(([event]) => event === "geolocate")!; // set the location onGeolocateCallback(position); @@ -155,7 +155,7 @@ describe("", () => { const setLocationClick = () => { // get the callback LocationShareMenu registered for geolocate expect(mocked(mockMap.on)).toHaveBeenCalledWith("click", expect.any(Function)); - const [, onMapClickCallback] = mocked(mockMap.on).mock.calls.find(([event]) => event === "click"); + const [, onMapClickCallback] = mocked(mockMap.on).mock.calls.find(([event]) => event === "click")!; const event = { lngLat: { lng: position.coords.longitude, lat: position.coords.latitude }, diff --git a/test/components/views/location/LocationViewDialog-test.tsx b/test/components/views/location/LocationViewDialog-test.tsx index bcf8f8ec8d..f4108cd625 100644 --- a/test/components/views/location/LocationViewDialog-test.tsx +++ b/test/components/views/location/LocationViewDialog-test.tsx @@ -51,6 +51,6 @@ describe("", () => { // @ts-ignore cheat assignment to property selfShareEvent.sender = member; const { container } = getComponent({ mxEvent: selfShareEvent }); - expect(container.querySelector(".mx_BaseAvatar_image").getAttribute("title")).toEqual(userId); + expect(container.querySelector(".mx_BaseAvatar_image")?.getAttribute("title")).toEqual(userId); }); }); diff --git a/test/components/views/rooms/ReadReceiptGroup-test.tsx b/test/components/views/rooms/ReadReceiptGroup-test.tsx index b0110a2cc2..12ed076962 100644 --- a/test/components/views/rooms/ReadReceiptGroup-test.tsx +++ b/test/components/views/rooms/ReadReceiptGroup-test.tsx @@ -28,7 +28,7 @@ describe("ReadReceiptGroup", () => { expect(readReceiptTooltip(["Alice", "Bob", "Charlie"], true)).toEqual("Alice, Bob, Charlie and more"); expect(readReceiptTooltip(["Alice", "Bob"], true)).toEqual("Alice, Bob and more"); expect(readReceiptTooltip(["Alice"], true)).toEqual("Alice and more"); - expect(readReceiptTooltip([], false)).toEqual(null); + expect(readReceiptTooltip([], false)).toBeUndefined(); }); it("returns a pretty list without hasMore", () => { expect(readReceiptTooltip(["Alice", "Bob", "Charlie", "Dan", "Eve"], false)).toEqual( @@ -40,7 +40,7 @@ describe("ReadReceiptGroup", () => { expect(readReceiptTooltip(["Alice", "Bob", "Charlie"], false)).toEqual("Alice, Bob and Charlie"); expect(readReceiptTooltip(["Alice", "Bob"], false)).toEqual("Alice and Bob"); expect(readReceiptTooltip(["Alice"], false)).toEqual("Alice"); - expect(readReceiptTooltip([], false)).toEqual(null); + expect(readReceiptTooltip([], false)).toBeUndefined(); }); }); describe("AvatarPosition", () => { diff --git a/test/components/views/rooms/SendMessageComposer-test.tsx b/test/components/views/rooms/SendMessageComposer-test.tsx index 17b977077d..db05bb77e0 100644 --- a/test/components/views/rooms/SendMessageComposer-test.tsx +++ b/test/components/views/rooms/SendMessageComposer-test.tsx @@ -281,7 +281,7 @@ describe("", () => { it("correctly sends a message", () => { mocked(doMaybeLocalRoomAction).mockImplementation( - (roomId: string, fn: (actualRoomId: string) => Promise, _client?: MatrixClient) => { + (roomId: string, fn: (actualRoomId: string) => Promise, _client?: MatrixClient) => { return fn(roomId); }, ); @@ -300,7 +300,7 @@ describe("", () => { it("shows chat effects on message sending", () => { mocked(doMaybeLocalRoomAction).mockImplementation( - (roomId: string, fn: (actualRoomId: string) => Promise, _client?: MatrixClient) => { + (roomId: string, fn: (actualRoomId: string) => Promise, _client?: MatrixClient) => { return fn(roomId); }, ); @@ -321,7 +321,7 @@ describe("", () => { it("not to send chat effects on message sending for threads", () => { mocked(doMaybeLocalRoomAction).mockImplementation( - (roomId: string, fn: (actualRoomId: string) => Promise, _client?: MatrixClient) => { + (roomId: string, fn: (actualRoomId: string) => Promise, _client?: MatrixClient) => { return fn(roomId); }, ); diff --git a/test/components/views/rooms/VoiceRecordComposerTile-test.tsx b/test/components/views/rooms/VoiceRecordComposerTile-test.tsx index e9f7615cca..fb47104f5c 100644 --- a/test/components/views/rooms/VoiceRecordComposerTile-test.tsx +++ b/test/components/views/rooms/VoiceRecordComposerTile-test.tsx @@ -25,6 +25,7 @@ import { VoiceRecording } from "../../../../src/audio/VoiceRecording"; import { doMaybeLocalRoomAction } from "../../../../src/utils/local-room"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { IUpload } from "../../../../src/audio/VoiceMessageRecording"; +import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks"; jest.mock("../../../../src/utils/local-room", () => ({ doMaybeLocalRoomAction: jest.fn(), @@ -43,10 +44,12 @@ describe("", () => { } as unknown as MatrixClient; MatrixClientPeg.get = () => mockClient; + const room = { + roomId, + } as unknown as Room; const props = { - room: { - roomId, - } as unknown as Room, + room, + permalinkCreator: new RoomPermalinkCreator(room), }; mockUpload = { mxc: "mxc://example.com/voice", @@ -66,7 +69,7 @@ describe("", () => { }); mocked(doMaybeLocalRoomAction).mockImplementation( - (roomId: string, fn: (actualRoomId: string) => Promise, _client?: MatrixClient) => { + (roomId: string, fn: (actualRoomId: string) => Promise, _client?: MatrixClient) => { return fn(roomId); }, ); diff --git a/test/components/views/settings/devices/CurrentDeviceSection-test.tsx b/test/components/views/settings/devices/CurrentDeviceSection-test.tsx index b102a996cb..1526682703 100644 --- a/test/components/views/settings/devices/CurrentDeviceSection-test.tsx +++ b/test/components/views/settings/devices/CurrentDeviceSection-test.tsx @@ -42,6 +42,7 @@ describe("", () => { isLoading: false, isSigningOut: false, otherSessionsCount: 1, + setPushNotifications: jest.fn(), }; const getComponent = (props = {}): React.ReactElement => ; diff --git a/test/components/views/settings/tabs/room/AdvancedRoomSettingsTab-test.tsx b/test/components/views/settings/tabs/room/AdvancedRoomSettingsTab-test.tsx index cb4b373e4f..da1f3460e0 100644 --- a/test/components/views/settings/tabs/room/AdvancedRoomSettingsTab-test.tsx +++ b/test/components/views/settings/tabs/room/AdvancedRoomSettingsTab-test.tsx @@ -47,7 +47,7 @@ describe("AdvancedRoomSettingsTab", () => { mocked(dis.dispatch).mockReset(); mocked(room.findPredecessor).mockImplementation((msc3946: boolean) => msc3946 - ? { roomId: "old_room_id_via_predecessor", eventId: null } + ? { roomId: "old_room_id_via_predecessor" } : { roomId: "old_room_id", eventId: "tombstone_event_id" }, ); }); @@ -136,7 +136,7 @@ describe("AdvancedRoomSettingsTab", () => { fireEvent.click(link); expect(dis.dispatch).toHaveBeenCalledWith({ action: Action.ViewRoom, - event_id: null, + event_id: undefined, room_id: "old_room_id_via_predecessor", metricsTrigger: "WebPredecessorSettings", metricsViaKeyboard: false, diff --git a/test/createRoom-test.ts b/test/createRoom-test.ts index 6ff690b41e..9bb4f79875 100644 --- a/test/createRoom-test.ts +++ b/test/createRoom-test.ts @@ -78,22 +78,16 @@ describe("createRoom", () => { const createCallSpy = jest.spyOn(ElementCall, "create"); const roomId = await createRoom({ roomType: RoomType.UnstableCall }); - const [ - [ - { - power_level_content_override: { - users: { [userId]: userPower }, - events: { - [ElementCall.CALL_EVENT_TYPE.name]: callPower, - [ElementCall.MEMBER_EVENT_TYPE.name]: callMemberPower, - }, - }, - }, - ], - ] = client.createRoom.mock.calls; + const userPower = client.createRoom.mock.calls[0][0].power_level_content_override?.users?.[userId]; + const callPower = + client.createRoom.mock.calls[0][0].power_level_content_override?.events?.[ElementCall.CALL_EVENT_TYPE.name]; + const callMemberPower = + client.createRoom.mock.calls[0][0].power_level_content_override?.events?.[ + ElementCall.MEMBER_EVENT_TYPE.name + ]; // We should have had enough power to be able to set up the call - expect(userPower).toBeGreaterThanOrEqual(callPower); + expect(userPower).toBeGreaterThanOrEqual(callPower!); // and should have actually set it up expect(createCallSpy).toHaveBeenCalled(); @@ -122,18 +116,12 @@ describe("createRoom", () => { await createRoom({}); - const [ - [ - { - power_level_content_override: { - events: { - [ElementCall.CALL_EVENT_TYPE.name]: callPower, - [ElementCall.MEMBER_EVENT_TYPE.name]: callMemberPower, - }, - }, - }, - ], - ] = client.createRoom.mock.calls; + const callPower = + client.createRoom.mock.calls[0][0].power_level_content_override?.events?.[ElementCall.CALL_EVENT_TYPE.name]; + const callMemberPower = + client.createRoom.mock.calls[0][0].power_level_content_override?.events?.[ + ElementCall.MEMBER_EVENT_TYPE.name + ]; expect(callPower).toBe(100); expect(callMemberPower).toBe(100); diff --git a/test/utils/location/parseGeoUri-test.ts b/test/utils/location/parseGeoUri-test.ts index 7e7a9020a8..027d0b3e85 100644 --- a/test/utils/location/parseGeoUri-test.ts +++ b/test/utils/location/parseGeoUri-test.ts @@ -35,9 +35,9 @@ describe("parseGeoUri", () => { longitude: 16.3695, altitude: 183, accuracy: undefined, - altitudeAccuracy: undefined, - heading: undefined, - speed: undefined, + altitudeAccuracy: null, + heading: null, + speed: null, }); }); @@ -45,11 +45,11 @@ describe("parseGeoUri", () => { expect(parseGeoUri("geo:48.198634,16.371648;crs=wgs84;u=40")).toEqual({ latitude: 48.198634, longitude: 16.371648, - altitude: undefined, + altitude: null, accuracy: 40, - altitudeAccuracy: undefined, - heading: undefined, - speed: undefined, + altitudeAccuracy: null, + heading: null, + speed: null, }); }); @@ -57,11 +57,11 @@ describe("parseGeoUri", () => { expect(parseGeoUri("geo:90,-22.43;crs=WGS84")).toEqual({ latitude: 90, longitude: -22.43, - altitude: undefined, + altitude: null, accuracy: undefined, - altitudeAccuracy: undefined, - heading: undefined, - speed: undefined, + altitudeAccuracy: null, + heading: null, + speed: null, }); }); @@ -69,11 +69,11 @@ describe("parseGeoUri", () => { expect(parseGeoUri("geo:90,46")).toEqual({ latitude: 90, longitude: 46, - altitude: undefined, + altitude: null, accuracy: undefined, - altitudeAccuracy: undefined, - heading: undefined, - speed: undefined, + altitudeAccuracy: null, + heading: null, + speed: null, }); }); @@ -81,11 +81,11 @@ describe("parseGeoUri", () => { expect(parseGeoUri("geo:66,30;u=6.500;FOo=this%2dthat")).toEqual({ latitude: 66, longitude: 30, - altitude: undefined, + altitude: null, accuracy: 6.5, - altitudeAccuracy: undefined, - heading: undefined, - speed: undefined, + altitudeAccuracy: null, + heading: null, + speed: null, }); }); @@ -93,11 +93,11 @@ describe("parseGeoUri", () => { expect(parseGeoUri("geo:66.0,30;u=6.5;foo=this-that>")).toEqual({ latitude: 66.0, longitude: 30, - altitude: undefined, + altitude: null, accuracy: 6.5, - altitudeAccuracy: undefined, - heading: undefined, - speed: undefined, + altitudeAccuracy: null, + heading: null, + speed: null, }); }); @@ -105,11 +105,11 @@ describe("parseGeoUri", () => { expect(parseGeoUri("geo:70,20;foo=1.00;bar=white")).toEqual({ latitude: 70, longitude: 20, - altitude: undefined, + altitude: null, accuracy: undefined, - altitudeAccuracy: undefined, - heading: undefined, - speed: undefined, + altitudeAccuracy: null, + heading: null, + speed: null, }); }); @@ -117,11 +117,11 @@ describe("parseGeoUri", () => { expect(parseGeoUri("geo:-7.5,20")).toEqual({ latitude: -7.5, longitude: 20, - altitude: undefined, + altitude: null, accuracy: undefined, - altitudeAccuracy: undefined, - heading: undefined, - speed: undefined, + altitudeAccuracy: null, + heading: null, + speed: null, }); }); @@ -131,9 +131,9 @@ describe("parseGeoUri", () => { longitude: -20, altitude: 0, accuracy: undefined, - altitudeAccuracy: undefined, - heading: undefined, - speed: undefined, + altitudeAccuracy: null, + heading: null, + speed: null, }); }); }); diff --git a/test/utils/permalinks/Permalinks-test.ts b/test/utils/permalinks/Permalinks-test.ts index 7d27d04673..0a50327c2c 100644 --- a/test/utils/permalinks/Permalinks-test.ts +++ b/test/utils/permalinks/Permalinks-test.ts @@ -91,7 +91,7 @@ describe("Permalinks", function () { const creator = new RoomPermalinkCreator(room); creator.load(); expect(creator.serverCandidates).toBeTruthy(); - expect(creator.serverCandidates.length).toBe(0); + expect(creator.serverCandidates!.length).toBe(0); }); it("should gracefully handle invalid MXIDs", () => { @@ -112,8 +112,8 @@ describe("Permalinks", function () { const creator = new RoomPermalinkCreator(room); creator.load(); expect(creator.serverCandidates).toBeTruthy(); - expect(creator.serverCandidates.length).toBe(3); - expect(creator.serverCandidates[0]).toBe("pl_95"); + expect(creator.serverCandidates!.length).toBe(3); + expect(creator.serverCandidates![0]).toBe("pl_95"); // we don't check the 2nd and 3rd servers because that is done by the next test }); @@ -128,15 +128,15 @@ describe("Permalinks", function () { ]); const creator = new RoomPermalinkCreator(room, null); creator.load(); - expect(creator.serverCandidates[0]).toBe("pl_95"); + expect(creator.serverCandidates![0]).toBe("pl_95"); member95.membership = "left"; // @ts-ignore illegal private property creator.onRoomStateUpdate(); - expect(creator.serverCandidates[0]).toBe("pl_75"); + expect(creator.serverCandidates![0]).toBe("pl_75"); member95.membership = "join"; // @ts-ignore illegal private property creator.onRoomStateUpdate(); - expect(creator.serverCandidates[0]).toBe("pl_95"); + expect(creator.serverCandidates![0]).toBe("pl_95"); }); it("should pick candidate servers based on user population", function () { @@ -152,10 +152,10 @@ describe("Permalinks", function () { const creator = new RoomPermalinkCreator(room); creator.load(); expect(creator.serverCandidates).toBeTruthy(); - expect(creator.serverCandidates.length).toBe(3); - expect(creator.serverCandidates[0]).toBe("first"); - expect(creator.serverCandidates[1]).toBe("second"); - expect(creator.serverCandidates[2]).toBe("third"); + expect(creator.serverCandidates!.length).toBe(3); + expect(creator.serverCandidates![0]).toBe("first"); + expect(creator.serverCandidates![1]).toBe("second"); + expect(creator.serverCandidates![2]).toBe("third"); }); it("should pick prefer candidate servers with higher power levels", function () { @@ -168,10 +168,10 @@ describe("Permalinks", function () { ]); const creator = new RoomPermalinkCreator(room); creator.load(); - expect(creator.serverCandidates.length).toBe(3); - expect(creator.serverCandidates[0]).toBe("first"); - expect(creator.serverCandidates[1]).toBe("second"); - expect(creator.serverCandidates[2]).toBe("third"); + expect(creator.serverCandidates!.length).toBe(3); + expect(creator.serverCandidates![0]).toBe("first"); + expect(creator.serverCandidates![1]).toBe("second"); + expect(creator.serverCandidates![2]).toBe("third"); }); it("should pick a maximum of 3 candidate servers", function () { @@ -186,7 +186,7 @@ describe("Permalinks", function () { const creator = new RoomPermalinkCreator(room); creator.load(); expect(creator.serverCandidates).toBeTruthy(); - expect(creator.serverCandidates.length).toBe(3); + expect(creator.serverCandidates!.length).toBe(3); }); it("should not consider IPv4 hosts", function () { @@ -195,7 +195,7 @@ describe("Permalinks", function () { const creator = new RoomPermalinkCreator(room); creator.load(); expect(creator.serverCandidates).toBeTruthy(); - expect(creator.serverCandidates.length).toBe(0); + expect(creator.serverCandidates!.length).toBe(0); }); it("should not consider IPv6 hosts", function () { @@ -204,7 +204,7 @@ describe("Permalinks", function () { const creator = new RoomPermalinkCreator(room); creator.load(); expect(creator.serverCandidates).toBeTruthy(); - expect(creator.serverCandidates.length).toBe(0); + expect(creator.serverCandidates!.length).toBe(0); }); it("should not consider IPv4 hostnames with ports", function () { @@ -213,7 +213,7 @@ describe("Permalinks", function () { const creator = new RoomPermalinkCreator(room); creator.load(); expect(creator.serverCandidates).toBeTruthy(); - expect(creator.serverCandidates.length).toBe(0); + expect(creator.serverCandidates!.length).toBe(0); }); it("should not consider IPv6 hostnames with ports", function () { @@ -222,7 +222,7 @@ describe("Permalinks", function () { const creator = new RoomPermalinkCreator(room); creator.load(); expect(creator.serverCandidates).toBeTruthy(); - expect(creator.serverCandidates.length).toBe(0); + expect(creator.serverCandidates!.length).toBe(0); }); it("should work with hostnames with ports", function () { @@ -232,8 +232,8 @@ describe("Permalinks", function () { const creator = new RoomPermalinkCreator(room); creator.load(); expect(creator.serverCandidates).toBeTruthy(); - expect(creator.serverCandidates.length).toBe(1); - expect(creator.serverCandidates[0]).toBe("example.org:8448"); + expect(creator.serverCandidates!.length).toBe(1); + expect(creator.serverCandidates![0]).toBe("example.org:8448"); }); it("should not consider servers explicitly denied by ACLs", function () { @@ -252,7 +252,7 @@ describe("Permalinks", function () { const creator = new RoomPermalinkCreator(room); creator.load(); expect(creator.serverCandidates).toBeTruthy(); - expect(creator.serverCandidates.length).toBe(0); + expect(creator.serverCandidates!.length).toBe(0); }); it("should not consider servers not allowed by ACLs", function () { @@ -271,7 +271,7 @@ describe("Permalinks", function () { const creator = new RoomPermalinkCreator(room); creator.load(); expect(creator.serverCandidates).toBeTruthy(); - expect(creator.serverCandidates.length).toBe(0); + expect(creator.serverCandidates!.length).toBe(0); }); it("should consider servers not explicitly banned by ACLs", function () { @@ -290,8 +290,8 @@ describe("Permalinks", function () { const creator = new RoomPermalinkCreator(room); creator.load(); expect(creator.serverCandidates).toBeTruthy(); - expect(creator.serverCandidates.length).toBe(1); - expect(creator.serverCandidates[0]).toEqual("evilcorp.com"); + expect(creator.serverCandidates!.length).toBe(1); + expect(creator.serverCandidates![0]).toEqual("evilcorp.com"); }); it("should consider servers not disallowed by ACLs", function () { @@ -310,8 +310,8 @@ describe("Permalinks", function () { const creator = new RoomPermalinkCreator(room); creator.load(); expect(creator.serverCandidates).toBeTruthy(); - expect(creator.serverCandidates.length).toBe(1); - expect(creator.serverCandidates[0]).toEqual("evilcorp.com"); + expect(creator.serverCandidates!.length).toBe(1); + expect(creator.serverCandidates![0]).toEqual("evilcorp.com"); }); it("should generate an event permalink for room IDs with no candidate servers", function () {