From 605ef084ec42fe38fd043b3ffd33ba1d4a9dfc1a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 13 Apr 2023 08:52:57 +0100 Subject: [PATCH] Conform more of the codebase to `strictNullChecks` (#10573) * Conform more of the codebase to `strictNullChecks` * Iterate --- src/ImageUtils.ts | 11 +++++++++-- .../structures/AutoHideScrollbar.tsx | 2 +- .../structures/IndicatorScrollbar.tsx | 3 ++- src/components/structures/LeftPanel.tsx | 10 ++++++---- .../structures/LegacyCallEventGrouper.ts | 4 ++-- src/components/structures/MatrixChat.tsx | 17 +++++++++-------- src/components/structures/MessagePanel.tsx | 10 +++------- src/components/structures/RightPanel.tsx | 2 +- src/components/structures/RoomView.tsx | 1 + src/components/structures/ScrollPanel.tsx | 4 ++-- .../structures/auth/Registration.tsx | 2 +- .../dialogs/ConfirmSpaceUserActionDialog.tsx | 2 +- .../views/dialogs/CreateSubspaceDialog.tsx | 6 +++--- .../dialogs/ManageRestrictedJoinRuleDialog.tsx | 13 +++++++------ .../dialogs/RegistrationEmailPromptDialog.tsx | 4 ++-- .../dialogs/spotlight/SpotlightDialog.tsx | 4 ++-- .../views/elements/GenericEventListSummary.tsx | 2 +- src/components/views/rooms/AppsDrawer.tsx | 18 ++++++++++-------- .../views/rooms/BasicMessageComposer.tsx | 18 ++++++++++++------ .../views/rooms/EditMessageComposer.tsx | 6 ++++-- .../views/rooms/LinkPreviewWidget.tsx | 12 +++--------- src/components/views/rooms/MessageComposer.tsx | 3 ++- .../views/rooms/MessageComposerButtons.tsx | 4 ++-- .../views/rooms/RecentlyViewedButton.tsx | 4 ++-- src/components/views/rooms/ReplyPreview.tsx | 2 +- src/components/views/settings/SetIdServer.tsx | 2 +- .../settings/tabs/room/VoipRoomSettingsTab.tsx | 9 +++++---- .../views/spaces/SpaceBasicSettings.tsx | 4 ++-- src/editor/autocomplete.ts | 16 ++++++++-------- src/editor/model.ts | 10 +++++----- src/editor/parts.ts | 4 ++-- src/hooks/useRoomState.ts | 8 ++++---- src/stores/RoomScrollStateStore.ts | 4 ++-- src/utils/UrlUtils.ts | 2 +- 34 files changed, 119 insertions(+), 104 deletions(-) diff --git a/src/ImageUtils.ts b/src/ImageUtils.ts index e8564fb0172..80fbb928b49 100644 --- a/src/ImageUtils.ts +++ b/src/ImageUtils.ts @@ -28,9 +28,16 @@ limitations under the License. * consume in the timeline, when performing scroll offset calculations * (e.g. scroll locking) */ +export function thumbHeight(fullWidth: number, fullHeight: number, thumbWidth: number, thumbHeight: number): number; export function thumbHeight( - fullWidth: number, - fullHeight: number, + fullWidth: number | undefined, + fullHeight: number | undefined, + thumbWidth: number, + thumbHeight: number, +): null; +export function thumbHeight( + fullWidth: number | undefined, + fullHeight: number | undefined, thumbWidth: number, thumbHeight: number, ): number | null { diff --git a/src/components/structures/AutoHideScrollbar.tsx b/src/components/structures/AutoHideScrollbar.tsx index d8ee5b71bb6..8b923b84afe 100644 --- a/src/components/structures/AutoHideScrollbar.tsx +++ b/src/components/structures/AutoHideScrollbar.tsx @@ -23,7 +23,7 @@ type DynamicHtmlElementProps = type DynamicElementProps = Partial>; export type IProps = Omit, "onScroll"> & { - element?: T; + element: T; className?: string; onScroll?: (event: Event) => void; onWheel?: (event: WheelEvent) => void; diff --git a/src/components/structures/IndicatorScrollbar.tsx b/src/components/structures/IndicatorScrollbar.tsx index e85b03132af..54d70add77a 100644 --- a/src/components/structures/IndicatorScrollbar.tsx +++ b/src/components/structures/IndicatorScrollbar.tsx @@ -19,7 +19,8 @@ import React, { createRef } from "react"; import AutoHideScrollbar, { IProps as AutoHideScrollbarProps } from "./AutoHideScrollbar"; import UIStore, { UI_EVENTS } from "../../stores/UIStore"; -export type IProps = Omit, "onWheel"> & { +export type IProps = Omit, "onWheel" | "element"> & { + element?: T; // If true, the scrollbar will append mx_IndicatorScrollbar_leftOverflowIndicator // and mx_IndicatorScrollbar_rightOverflowIndicator elements to the list for positioning // by the parent element. diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index eab1972798e..689ebc31c55 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -90,11 +90,13 @@ export default class LeftPanel extends React.Component { } public componentDidMount(): void { - UIStore.instance.trackElementDimensions("ListContainer", this.listContainerRef.current); + if (this.listContainerRef.current) { + UIStore.instance.trackElementDimensions("ListContainer", this.listContainerRef.current); + // Using the passive option to not block the main thread + // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners + this.listContainerRef.current.addEventListener("scroll", this.onScroll, { passive: true }); + } UIStore.instance.on("ListContainer", this.refreshStickyHeaders); - // Using the passive option to not block the main thread - // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners - this.listContainerRef.current?.addEventListener("scroll", this.onScroll, { passive: true }); } public componentWillUnmount(): void { diff --git a/src/components/structures/LegacyCallEventGrouper.ts b/src/components/structures/LegacyCallEventGrouper.ts index ba218e8a898..ab15ce5b642 100644 --- a/src/components/structures/LegacyCallEventGrouper.ts +++ b/src/components/structures/LegacyCallEventGrouper.ts @@ -177,9 +177,9 @@ export default class LegacyCallEventGrouper extends EventEmitter { } private setState = (): void => { - if (CONNECTING_STATES.includes(this.call?.state)) { + if (this.call && CONNECTING_STATES.includes(this.call.state)) { this.state = CallState.Connecting; - } else if (SUPPORTED_STATES.includes(this.call?.state)) { + } else if (this.call && SUPPORTED_STATES.includes(this.call.state)) { this.state = this.call.state; } else { if (this.callWasMissed) this.state = CustomCallState.Missed; diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index b17441e30c6..575ff07925b 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -471,11 +471,9 @@ export default class MatrixChat extends React.PureComponent { ); }, 1000); - private getFallbackHsUrl(): string | null { + private getFallbackHsUrl(): string | undefined { if (this.props.serverConfig?.isDefault) { - return this.props.config.fallback_hs_url ?? null; - } else { - return null; + return this.props.config.fallback_hs_url; } } @@ -577,7 +575,7 @@ export default class MatrixChat extends React.PureComponent { if (payload.event_type === "m.identity_server") { const fullUrl = payload.event_content ? payload.event_content["base_url"] : null; if (!fullUrl) { - MatrixClientPeg.get().setIdentityServerUrl(null); + MatrixClientPeg.get().setIdentityServerUrl(undefined); localStorage.removeItem("mx_is_access_token"); localStorage.removeItem("mx_is_url"); } else { @@ -1229,6 +1227,10 @@ export default class MatrixChat extends React.PureComponent { * @returns {string} The room ID of the new room, or null if no room was created */ private async startWelcomeUserChat(): Promise { + const snakedConfig = new SnakedObject(this.props.config); + const welcomeUserId = snakedConfig.get("welcome_user_id"); + if (!welcomeUserId) return null; + // We can end up with multiple tabs post-registration where the user // might then end up with a session and we don't want them all making // a chat with the welcome user: try to de-dupe. @@ -1242,8 +1244,7 @@ export default class MatrixChat extends React.PureComponent { } await waitFor; - const snakedConfig = new SnakedObject(this.props.config); - const welcomeUserRooms = DMRoomMap.shared().getDMRoomsForUserId(snakedConfig.get("welcome_user_id")); + const welcomeUserRooms = DMRoomMap.shared().getDMRoomsForUserId(welcomeUserId); if (welcomeUserRooms.length === 0) { const roomId = await createRoom({ dmUserId: snakedConfig.get("welcome_user_id"), @@ -1260,7 +1261,7 @@ export default class MatrixChat extends React.PureComponent { // user room (it doesn't wait for new data from the server, just // the saved sync to be loaded). const saveWelcomeUser = (ev: MatrixEvent): void => { - if (ev.getType() === EventType.Direct && ev.getContent()[snakedConfig.get("welcome_user_id")]) { + if (ev.getType() === EventType.Direct && ev.getContent()[welcomeUserId]) { MatrixClientPeg.get().store.save(true); MatrixClientPeg.get().removeListener(ClientEvent.AccountData, saveWelcomeUser); } diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 87934114633..dd2ce65efb5 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -869,7 +869,7 @@ export default class MessagePanel extends React.Component { const receiptsByEvent: Map = new Map(); const receiptsByUserId: Map = new Map(); - let lastShownEventId: string; + let lastShownEventId: string | undefined; for (const event of this.props.events) { if (this.shouldShowEvent(event)) { lastShownEventId = event.getId(); @@ -1018,11 +1018,7 @@ export default class MessagePanel extends React.Component { let ircResizer: JSX.Element | undefined; if (this.props.layout == Layout.IRC) { ircResizer = ( - + ); } @@ -1206,7 +1202,7 @@ class CreationGrouper extends BaseGrouper { let summaryText: string; const roomId = ev.getRoomId(); const creator = ev.sender?.name ?? ev.getSender(); - if (DMRoomMap.shared().getUserIdForRoomId(roomId)) { + if (roomId && DMRoomMap.shared().getUserIdForRoomId(roomId)) { summaryText = _t("%(creator)s created this DM.", { creator }); } else { summaryText = _t("%(creator)s created and configured the room.", { creator }); diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index f6ad289318e..2424c580da4 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -141,7 +141,7 @@ export default class RightPanel extends React.Component { // When the user clicks close on the encryption panel cancel the pending request first if any this.state.cardState.verificationRequest.cancel(); } else { - RightPanelStore.instance.togglePanel(this.props.room?.roomId); + RightPanelStore.instance.togglePanel(this.props.room?.roomId ?? null); } }; diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 9af69100ab8..1eaa2cf16e3 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1597,6 +1597,7 @@ export class RoomView extends React.Component { }; private injectSticker(url: string, info: object, text: string, threadId: string | null): void { + if (!this.context.client) return; if (this.context.client.isGuest()) { dis.dispatch({ action: "require_registration" }); return; diff --git a/src/components/structures/ScrollPanel.tsx b/src/components/structures/ScrollPanel.tsx index 05fd42879bf..c732f07d3ac 100644 --- a/src/components/structures/ScrollPanel.tsx +++ b/src/components/structures/ScrollPanel.tsx @@ -189,7 +189,7 @@ export default class ScrollPanel extends React.Component { // Is that next fill request scheduled because of a props update? private pendingFillDueToPropsUpdate: boolean; private scrollState: IScrollState; - private preventShrinkingState: IPreventShrinkingState; + private preventShrinkingState: IPreventShrinkingState | null; private unfillDebouncer: number | null; private bottomGrowth: number; private minListHeight: number; @@ -676,7 +676,7 @@ export default class ScrollPanel extends React.Component { debuglog("unable to save scroll state: found no children in the viewport"); return; } - const scrollToken = node!.dataset.scrollTokens.split(",")[0]; + const scrollToken = node!.dataset.scrollTokens?.split(",")[0]; debuglog("saving anchored scroll state to message", scrollToken); const bottomOffset = this.topFromBottom(node); this.scrollState = { diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index dd9be190b2a..15dcda4bc29 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -50,7 +50,7 @@ const debuglog = (...args: any[]): void => { interface IProps { serverConfig: ValidatedServerConfig; - defaultDeviceDisplayName: string; + defaultDeviceDisplayName?: string; email?: string; brand?: string; clientSecret?: string; diff --git a/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx b/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx index 43804ea1717..e4d17834298 100644 --- a/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx +++ b/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx @@ -53,7 +53,7 @@ const ConfirmSpaceUserActionDialog: React.FC = ({ const [roomsToLeave, setRoomsToLeave] = useState([]); const selectedRooms = useMemo(() => new Set(roomsToLeave), [roomsToLeave]); - let warning: JSX.Element; + let warning: JSX.Element | undefined; if (warningMessage) { warning =
{warningMessage}
; } diff --git a/src/components/views/dialogs/CreateSubspaceDialog.tsx b/src/components/views/dialogs/CreateSubspaceDialog.tsx index 4c12587adef..447702bdae0 100644 --- a/src/components/views/dialogs/CreateSubspaceDialog.tsx +++ b/src/components/views/dialogs/CreateSubspaceDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useRef, useState } from "react"; +import React, { RefObject, useRef, useState } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import { JoinRule } from "matrix-js-sdk/src/@types/partials"; import { logger } from "matrix-js-sdk/src/logger"; @@ -41,9 +41,9 @@ const CreateSubspaceDialog: React.FC = ({ space, onAddExistingSpaceClick const [busy, setBusy] = useState(false); const [name, setName] = useState(""); - const spaceNameField = useRef(); + const spaceNameField = useRef() as RefObject; const [alias, setAlias] = useState(""); - const spaceAliasField = useRef(); + const spaceAliasField = useRef() as RefObject; const [avatar, setAvatar] = useState(); const [topic, setTopic] = useState(""); diff --git a/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx b/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx index 97c43ce8913..a4cefd7fbab 100644 --- a/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx +++ b/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx @@ -26,6 +26,7 @@ import AccessibleButton from "../elements/AccessibleButton"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import StyledCheckbox from "../elements/StyledCheckbox"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import { filterBoolean } from "../../../utils/arrays"; interface IProps { room: Room; @@ -65,7 +66,7 @@ const Entry: React.FC<{ )} onChange(e.target.checked) : null} + onChange={onChange ? (e) => onChange(e.target.checked) : undefined} checked={checked} disabled={!onChange} /> @@ -80,7 +81,7 @@ const addAllParents = (set: Set, room: Room): void => { ); parents.forEach((parent) => { - if (set.has(parent)) return; + if (!parent || set.has(parent)) return; set.add(parent); addAllParents(set, parent); }); @@ -97,8 +98,8 @@ const ManageRestrictedJoinRuleDialog: React.FC = ({ room, selected = [], addAllParents(parents, room); return [ Array.from(parents), - selected - .map((roomId) => { + filterBoolean( + selected.map((roomId) => { const room = cli.getRoom(roomId); if (!room) { return { roomId, name: roomId } as Room; @@ -106,8 +107,8 @@ const ManageRestrictedJoinRuleDialog: React.FC = ({ room, selected = [], if (room.getMyMembership() !== "join" || !room.isSpaceRoom()) { return room; } - }) - .filter(Boolean), + }), + ), ]; }, [cli, selected, room]); diff --git a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx index 9d8adc9d991..426b1770381 100644 --- a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx +++ b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import * as React from "react"; -import { SyntheticEvent, useRef, useState } from "react"; +import { RefObject, SyntheticEvent, useRef, useState } from "react"; import { _t, _td } from "../../../languageHandler"; import Field from "../elements/Field"; @@ -30,7 +30,7 @@ interface IProps { const RegistrationEmailPromptDialog: React.FC = ({ onFinished }) => { const [email, setEmail] = useState(""); - const fieldRef = useRef(); + const fieldRef = useRef() as RefObject; const onSubmit = async (e: SyntheticEvent): Promise => { e.preventDefault(); diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx index 8a2f00496b3..b45b0582b86 100644 --- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx +++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx @@ -288,8 +288,8 @@ interface IDirectoryOpts { } const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = null, onFinished }) => { - const inputRef = useRef(); - const scrollContainerRef = useRef(); + const inputRef = useRef() as RefObject; + const scrollContainerRef = useRef() as RefObject; const cli = MatrixClientPeg.get(); const rovingContext = useContext(RovingTabIndexContext); const [query, _setQuery] = useState(initialText); diff --git a/src/components/views/elements/GenericEventListSummary.tsx b/src/components/views/elements/GenericEventListSummary.tsx index 19578942a55..13a9466d5d4 100644 --- a/src/components/views/elements/GenericEventListSummary.tsx +++ b/src/components/views/elements/GenericEventListSummary.tsx @@ -38,7 +38,7 @@ interface IProps { // The text to show as the summary of this event list "summaryText"?: ReactNode; // An array of EventTiles to render when expanded - "children": ReactNode[]; + "children": ReactNode[] | null; // Called when the event list expansion is toggled onToggle?(): void; // The layout currently used diff --git a/src/components/views/rooms/AppsDrawer.tsx b/src/components/views/rooms/AppsDrawer.tsx index 1c57cc70ea3..cd5e9bc889e 100644 --- a/src/components/views/rooms/AppsDrawer.tsx +++ b/src/components/views/rooms/AppsDrawer.tsx @@ -45,7 +45,11 @@ interface IProps { } interface IState { - apps: Partial<{ [id in Container]: IApp[] }>; + apps: { + [Container.Top]: IApp[]; + [Container.Center]: IApp[]; + [Container.Right]?: IApp[]; + }; resizingVertical: boolean; // true when changing the height of the apps drawer resizingHorizontal: boolean; // true when changing the distribution of the width between widgets resizing: boolean; @@ -203,12 +207,10 @@ export default class AppsDrawer extends React.Component { } }; - private getApps = (): Partial<{ [id in Container]: IApp[] }> => { - const appsDict: Partial<{ [id in Container]: IApp[] }> = {}; - appsDict[Container.Top] = WidgetLayoutStore.instance.getContainerWidgets(this.props.room, Container.Top); - appsDict[Container.Center] = WidgetLayoutStore.instance.getContainerWidgets(this.props.room, Container.Center); - return appsDict; - }; + private getApps = (): IState["apps"] => ({ + [Container.Top]: WidgetLayoutStore.instance.getContainerWidgets(this.props.room, Container.Top), + [Container.Center]: WidgetLayoutStore.instance.getContainerWidgets(this.props.room, Container.Center), + }); private topApps = (): IApp[] => this.state.apps[Container.Top]; private centerApps = (): IApp[] => this.state.apps[Container.Center]; @@ -348,7 +350,7 @@ const PersistentVResizer: React.FC = ({ resizeNotifier.notifyTimelineHeightChanged(); }} onResizeStop={(e, dir, ref, d) => { - let newHeight = defaultHeight + d.height; + let newHeight = defaultHeight! + d.height; newHeight = percentageOf(newHeight, minHeight, maxHeight) * 100; WidgetLayoutStore.instance.setContainerHeight(room, Container.Top, newHeight); diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index e7bb866ec0d..2eb19e9e3c6 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -231,6 +231,7 @@ export default class BasicMessageEditor extends React.Component } private updateEditorState = (selection: Caret, inputType?: string, diff?: IDiff): void => { + if (!this.editorRef.current) return; renderModel(this.editorRef.current, this.props.model); if (selection) { // set the caret/selection @@ -358,6 +359,7 @@ export default class BasicMessageEditor extends React.Component private onPaste = (event: ClipboardEvent): boolean | undefined => { event.preventDefault(); // we always handle the paste ourselves + if (!this.editorRef.current) return; if (this.props.onPaste?.(event, this.props.model)) { // to prevent double handling, allow props.onPaste to skip internal onPaste return true; @@ -377,7 +379,7 @@ export default class BasicMessageEditor extends React.Component } this.modifiedFlag = true; - const range = getRangeForSelection(this.editorRef.current, model, document.getSelection()); + const range = getRangeForSelection(this.editorRef.current, model, document.getSelection()!); // If the user is pasting a link, and has a range selected which is not a link, wrap the range with the link if (plainText && range.length > 0 && linkify.test(plainText) && !linkify.test(range.text)) { @@ -388,18 +390,20 @@ export default class BasicMessageEditor extends React.Component }; private onInput = (event: Partial): void => { + if (!this.editorRef.current) return; // ignore any input while doing IME compositions if (this.isIMEComposing) { return; } this.modifiedFlag = true; - const sel = document.getSelection(); + const sel = document.getSelection()!; const { caret, text } = getCaretOffsetAndText(this.editorRef.current, sel); this.props.model.update(text, event.inputType, caret); }; private insertText(textToInsert: string, inputType = "insertText"): void { - const sel = document.getSelection(); + if (!this.editorRef.current) return; + const sel = document.getSelection()!; const { caret, text } = getCaretOffsetAndText(this.editorRef.current, sel); const newText = text.slice(0, caret.offset) + textToInsert + text.slice(caret.offset); caret.offset += textToInsert.length; @@ -468,6 +472,7 @@ export default class BasicMessageEditor extends React.Component }; private onSelectionChange = (): void => { + if (!this.editorRef.current) return; const { isEmpty } = this.props.model; this.refreshLastCaretIfNeeded(); @@ -486,6 +491,7 @@ export default class BasicMessageEditor extends React.Component }; private onKeyDown = (event: React.KeyboardEvent): void => { + if (!this.editorRef.current) return; const model = this.props.model; let handled = false; @@ -497,7 +503,7 @@ export default class BasicMessageEditor extends React.Component const selectionRange = getRangeForSelection( this.editorRef.current, this.props.model, - document.getSelection(), + document.getSelection()!, ); // trim the range as we want it to exclude leading/trailing spaces selectionRange.trim(); @@ -745,11 +751,11 @@ export default class BasicMessageEditor extends React.Component } public onFormatAction = (action: Formatting): void => { - if (!this.state.useMarkdown) { + if (!this.state.useMarkdown || !this.editorRef.current) { return; } - const range: Range = getRangeForSelection(this.editorRef.current, this.props.model, document.getSelection()); + const range: Range = getRangeForSelection(this.editorRef.current, this.props.model, document.getSelection()!); this.historyManager.ensureLastChangesPushed(this.props.model); this.modifiedFlag = true; diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index 16c9c66e8df..bed42fd0a89 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -238,9 +238,11 @@ class EditMessageComposer extends React.Component { diff --git a/src/components/views/rooms/LinkPreviewWidget.tsx b/src/components/views/rooms/LinkPreviewWidget.tsx index 0172a615493..4f3d1624c42 100644 --- a/src/components/views/rooms/LinkPreviewWidget.tsx +++ b/src/components/views/rooms/LinkPreviewWidget.tsx @@ -89,15 +89,9 @@ export default class LinkPreviewWidget extends React.Component { image = mediaFromMxc(image).getThumbnailOfSourceHttp(imageMaxWidth, imageMaxHeight, "scale"); } - let thumbHeight = imageMaxHeight; - if (p["og:image:width"] && p["og:image:height"]) { - thumbHeight = ImageUtils.thumbHeight( - p["og:image:width"], - p["og:image:height"], - imageMaxWidth, - imageMaxHeight, - ); - } + const thumbHeight = + ImageUtils.thumbHeight(p["og:image:width"], p["og:image:height"], imageMaxWidth, imageMaxHeight) ?? + imageMaxHeight; let img: JSX.Element | undefined; if (image) { diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index a5569de36aa..bf5f6b42bb7 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -276,7 +276,8 @@ export class MessageComposer extends React.Component { if (createEvent?.getId()) createEventId = createEvent.getId(); } - const viaServers = [this.context.tombstone.getSender().split(":").slice(1).join(":")]; + const sender = this.context.tombstone?.getSender(); + const viaServers = sender ? [sender.split(":").slice(1).join(":")] : undefined; dis.dispatch({ action: Action.ViewRoom, diff --git a/src/components/views/rooms/MessageComposerButtons.tsx b/src/components/views/rooms/MessageComposerButtons.tsx index 1b8e15b4981..9572a118eb5 100644 --- a/src/components/views/rooms/MessageComposerButtons.tsx +++ b/src/components/views/rooms/MessageComposerButtons.tsx @@ -17,7 +17,7 @@ limitations under the License. import classNames from "classnames"; import { IEventRelation } from "matrix-js-sdk/src/models/event"; import { M_POLL_START } from "matrix-js-sdk/src/@types/polls"; -import React, { createContext, MouseEventHandler, ReactElement, ReactNode, useContext, useRef } from "react"; +import React, { createContext, MouseEventHandler, ReactElement, ReactNode, RefObject, useContext, useRef } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread"; @@ -180,7 +180,7 @@ interface IUploadButtonProps { const UploadButtonContextProvider: React.FC = ({ roomId, relation, children }) => { const cli = useContext(MatrixClientContext); const roomContext = useContext(RoomContext); - const uploadInput = useRef(); + const uploadInput = useRef() as RefObject; const onUploadClick = (): void => { if (cli?.isGuest()) { diff --git a/src/components/views/rooms/RecentlyViewedButton.tsx b/src/components/views/rooms/RecentlyViewedButton.tsx index d6b0be80a56..8c0eacea9d4 100644 --- a/src/components/views/rooms/RecentlyViewedButton.tsx +++ b/src/components/views/rooms/RecentlyViewedButton.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useRef } from "react"; +import React, { RefObject, useRef } from "react"; import { BreadcrumbsStore } from "../../../stores/BreadcrumbsStore"; import { UPDATE_EVENT } from "../../../stores/AsyncStore"; @@ -30,7 +30,7 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import RoomAvatar from "../avatars/RoomAvatar"; const RecentlyViewedButton: React.FC = () => { - const tooltipRef = useRef(); + const tooltipRef = useRef() as RefObject; const crumbs = useEventEmitterState(BreadcrumbsStore.instance, UPDATE_EVENT, () => BreadcrumbsStore.instance.rooms); const content = ( diff --git a/src/components/views/rooms/ReplyPreview.tsx b/src/components/views/rooms/ReplyPreview.tsx index a81189840fe..3bc540af11b 100644 --- a/src/components/views/rooms/ReplyPreview.tsx +++ b/src/components/views/rooms/ReplyPreview.tsx @@ -33,7 +33,7 @@ function cancelQuoting(context: TimelineRenderingType): void { } interface IProps { - permalinkCreator: RoomPermalinkCreator; + permalinkCreator?: RoomPermalinkCreator; replyToEvent?: MatrixEvent; } diff --git a/src/components/views/settings/SetIdServer.tsx b/src/components/views/settings/SetIdServer.tsx index f6f166d2d62..6e1e6b8e35f 100644 --- a/src/components/views/settings/SetIdServer.tsx +++ b/src/components/views/settings/SetIdServer.tsx @@ -73,7 +73,7 @@ interface IProps { interface IState { defaultIdServer?: string; currentClientIdServer?: string; - idServer?: string; + idServer: string; error?: string; busy: boolean; disconnectBusy: boolean; diff --git a/src/components/views/settings/tabs/room/VoipRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/VoipRoomSettingsTab.tsx index 93c67d96f82..e38eb374c80 100644 --- a/src/components/views/settings/tabs/room/VoipRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/VoipRoomSettingsTab.tsx @@ -17,6 +17,7 @@ limitations under the License. import React, { useCallback, useMemo, useState } from "react"; import { JoinRule } from "matrix-js-sdk/src/@types/partials"; import { EventType } from "matrix-js-sdk/src/@types/event"; +import { RoomState } from "matrix-js-sdk/src/models/room-state"; import { _t } from "../../../../../languageHandler"; import { MatrixClientPeg } from "../../../../../MatrixClientPeg"; @@ -33,15 +34,15 @@ interface ElementCallSwitchProps { const ElementCallSwitch: React.FC = ({ roomId }) => { const room = useMemo(() => MatrixClientPeg.get().getRoom(roomId), [roomId]); - const isPublic = useMemo(() => room.getJoinRule() === JoinRule.Public, [room]); + const isPublic = useMemo(() => room?.getJoinRule() === JoinRule.Public, [room]); const [content, events, maySend] = useRoomState( - room, - useCallback((state) => { + room ?? undefined, + useCallback((state: RoomState) => { const content = state?.getStateEvents(EventType.RoomPowerLevels, "")?.getContent(); return [ content ?? {}, content?.["events"] ?? {}, - state?.maySendStateEvent(EventType.RoomPowerLevels, MatrixClientPeg.get().getUserId()), + state?.maySendStateEvent(EventType.RoomPowerLevels, MatrixClientPeg.get().getSafeUserId()), ]; }, []), ); diff --git a/src/components/views/spaces/SpaceBasicSettings.tsx b/src/components/views/spaces/SpaceBasicSettings.tsx index 4eb8c8a1a59..5ce844cc863 100644 --- a/src/components/views/spaces/SpaceBasicSettings.tsx +++ b/src/components/views/spaces/SpaceBasicSettings.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ChangeEvent, useRef, useState } from "react"; +import React, { ChangeEvent, RefObject, useRef, useState } from "react"; import { _t } from "../../../languageHandler"; import AccessibleButton from "../elements/AccessibleButton"; @@ -38,7 +38,7 @@ export const SpaceAvatar: React.FC { - const avatarUploadRef = useRef(); + const avatarUploadRef = useRef() as RefObject; const [avatar, setAvatarDataUrl] = useState(avatarUrl); // avatar data url cache let avatarSection; diff --git a/src/editor/autocomplete.ts b/src/editor/autocomplete.ts index 199b43a26ff..8d95ae523ba 100644 --- a/src/editor/autocomplete.ts +++ b/src/editor/autocomplete.ts @@ -28,7 +28,7 @@ export interface ICallback { } export type UpdateCallback = (data: ICallback) => void; -export type GetAutocompleterComponent = () => Autocomplete; +export type GetAutocompleterComponent = () => Autocomplete | null; export type UpdateQuery = (test: string) => Promise; export default class AutocompleteWrapperModel { @@ -42,7 +42,7 @@ export default class AutocompleteWrapperModel { ) {} public onEscape(e: KeyboardEvent): void { - this.getAutocompleterComponent().onEscape(e); + this.getAutocompleterComponent()?.onEscape(e); } public close(): void { @@ -50,16 +50,16 @@ export default class AutocompleteWrapperModel { } public hasSelection(): boolean { - return this.getAutocompleterComponent().hasSelection(); + return !!this.getAutocompleterComponent()?.hasSelection(); } public hasCompletions(): boolean { const ac = this.getAutocompleterComponent(); - return ac && ac.countCompletions() > 0; + return !!ac && ac.countCompletions() > 0; } public confirmCompletion(): void { - this.getAutocompleterComponent().onConfirmCompletion(); + this.getAutocompleterComponent()?.onConfirmCompletion(); this.updateCallback({ close: true }); } @@ -68,18 +68,18 @@ export default class AutocompleteWrapperModel { */ public async startSelection(): Promise { const acComponent = this.getAutocompleterComponent(); - if (acComponent.countCompletions() === 0) { + if (acComponent && acComponent.countCompletions() === 0) { // Force completions to show for the text currently entered await acComponent.forceComplete(); } } public selectPreviousSelection(): void { - this.getAutocompleterComponent().moveSelection(-1); + this.getAutocompleterComponent()?.moveSelection(-1); } public selectNextSelection(): void { - this.getAutocompleterComponent().moveSelection(+1); + this.getAutocompleterComponent()?.moveSelection(+1); } public onPartUpdate(part: Part, pos: DocumentPosition): Promise { diff --git a/src/editor/model.ts b/src/editor/model.ts index 8463540ebb1..65058c68cb5 100644 --- a/src/editor/model.ts +++ b/src/editor/model.ts @@ -44,7 +44,7 @@ import { Caret } from "./caret"; * @return the caret position */ -type TransformCallback = (caretPosition: DocumentPosition, inputType: string, diff: IDiff) => number | void; +type TransformCallback = (caretPosition: DocumentPosition, inputType: string | undefined, diff: IDiff) => number | void; type UpdateCallback = (caret?: Caret, inputType?: string, diff?: IDiff) => void; type ManualTransformCallback = () => Caret; @@ -151,7 +151,7 @@ export default class EditorModel { return this._parts.map((p) => p.serialize()); } - private diff(newValue: string, inputType: string, caret: DocumentOffset): IDiff { + private diff(newValue: string, inputType: string | undefined, caret: DocumentOffset): IDiff { const previousValue = this.parts.reduce((text, p) => text + p.text, ""); // can't use caret position with drag and drop if (inputType === "deleteByDrag") { @@ -196,7 +196,7 @@ export default class EditorModel { return newTextLength; } - public update(newValue: string, inputType: string, caret: DocumentOffset): Promise { + public update(newValue: string, inputType: string | undefined, caret: DocumentOffset): Promise { const diff = this.diff(newValue, inputType, caret); const position = this.positionForOffset(diff.at || 0, caret.atNodeEnd); let removedOffsetDecrease = 0; @@ -220,7 +220,7 @@ export default class EditorModel { return acPromise; } - private getTransformAddedLen(newPosition: DocumentPosition, inputType: string, diff: IDiff): number { + private getTransformAddedLen(newPosition: DocumentPosition, inputType: string | undefined, diff: IDiff): number { const result = this.transformCallback?.(newPosition, inputType, diff); return Number.isFinite(result) ? (result as number) : 0; } @@ -360,7 +360,7 @@ export default class EditorModel { * @return {Number} how far from position (in characters) the insertion ended. * This can be more than the length of `str` when crossing non-editable parts, which are skipped. */ - private addText(pos: IPosition, str: string, inputType: string): number { + private addText(pos: IPosition, str: string, inputType: string | undefined): number { let { index } = pos; const { offset } = pos; let addLen = str.length; diff --git a/src/editor/parts.ts b/src/editor/parts.ts index 271feadd398..e25b582e207 100644 --- a/src/editor/parts.ts +++ b/src/editor/parts.ts @@ -65,8 +65,8 @@ interface IBasePart { serialize(): SerializedPart; remove(offset: number, len: number): string | undefined; split(offset: number): IBasePart; - validateAndInsert(offset: number, str: string, inputType: string): boolean; - appendUntilRejected(str: string, inputType: string): string | undefined; + validateAndInsert(offset: number, str: string, inputType: string | undefined): boolean; + appendUntilRejected(str: string, inputType: string | undefined): string | undefined; updateDOMNode(node: Node): void; canUpdateDOMNode(node: Node): boolean; toDOMNode(): Node; diff --git a/src/hooks/useRoomState.ts b/src/hooks/useRoomState.ts index e833f3f10bf..b0637c2e19c 100644 --- a/src/hooks/useRoomState.ts +++ b/src/hooks/useRoomState.ts @@ -28,8 +28,8 @@ const defaultMapper: Mapper = (roomState: RoomState) => roomState; export const useRoomState = ( room?: Room, mapper: Mapper = defaultMapper as Mapper, -): T | undefined => { - const [value, setValue] = useState(room ? mapper(room.currentState) : undefined); +): T => { + const [value, setValue] = useState(room ? mapper(room.currentState) : (undefined as T)); const update = useCallback(() => { if (!room) return; @@ -40,8 +40,8 @@ export const useRoomState = ( useEffect(() => { update(); return () => { - setValue(undefined); + setValue(room ? mapper(room.currentState) : (undefined as T)); }; - }, [update]); + }, [room, mapper, update]); return value; }; diff --git a/src/stores/RoomScrollStateStore.ts b/src/stores/RoomScrollStateStore.ts index 8a7281e0664..4354bfb9c78 100644 --- a/src/stores/RoomScrollStateStore.ts +++ b/src/stores/RoomScrollStateStore.ts @@ -15,8 +15,8 @@ limitations under the License. */ export interface ScrollState { - focussedEvent: string; - pixelOffset: number; + focussedEvent?: string; + pixelOffset?: number; } /** diff --git a/src/utils/UrlUtils.ts b/src/utils/UrlUtils.ts index d3e1a9929ca..4513cafba59 100644 --- a/src/utils/UrlUtils.ts +++ b/src/utils/UrlUtils.ts @@ -22,7 +22,7 @@ import url from "url"; * @param {string} u The url to be abbreviated * @returns {string} The abbreviated url */ -export function abbreviateUrl(u: string): string { +export function abbreviateUrl(u?: string): string { if (!u) return ""; const parsedUrl = url.parse(u);