diff --git a/src/autocomplete/Autocompleter.ts b/src/autocomplete/Autocompleter.ts index 73933a23a9..51c160320b 100644 --- a/src/autocomplete/Autocompleter.ts +++ b/src/autocomplete/Autocompleter.ts @@ -39,7 +39,7 @@ export interface ICompletion { type?: "at-room" | "command" | "community" | "room" | "user"; completion: string; completionId?: string; - component?: ReactElement; + component: ReactElement; range: ISelectionRange; command?: string; suffix?: string; diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index c14e081a80..9c72b269f1 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -231,7 +231,7 @@ class LoggedInView extends React.Component { }; private createResizer(): Resizer { - let panelSize: number; + let panelSize: number | null; let panelCollapsed: boolean; const collapseConfig: ICollapseConfig = { // TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx index 69ee06a2cc..ab434e1a69 100644 --- a/src/components/views/avatars/RoomAvatar.tsx +++ b/src/components/views/avatars/RoomAvatar.tsx @@ -110,6 +110,7 @@ export default class RoomAvatar extends React.Component { private onRoomAvatarClick = (): void => { const avatarUrl = Avatar.avatarUrlForRoom(this.props.room ?? null, undefined, undefined, undefined); + if (!avatarUrl) return; const params = { src: avatarUrl, name: this.props.room?.name, diff --git a/src/components/views/dialogs/CreateSubspaceDialog.tsx b/src/components/views/dialogs/CreateSubspaceDialog.tsx index 447702bdae..d994b9fc64 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, { RefObject, useRef, useState } from "react"; +import React, { 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() as RefObject; + const spaceNameField = useRef(null); const [alias, setAlias] = useState(""); - const spaceAliasField = useRef() as RefObject; + const spaceAliasField = useRef(null); const [avatar, setAvatar] = useState(); const [topic, setTopic] = useState(""); diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx index 7402c3413c..7e01def9e1 100644 --- a/src/components/views/dialogs/ExportDialog.tsx +++ b/src/components/views/dialogs/ExportDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useRef, useState, Dispatch, SetStateAction, RefObject } from "react"; +import React, { useRef, useState, Dispatch, SetStateAction } from "react"; import { Room } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; @@ -104,8 +104,8 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { } = useExportFormState(); const [isExporting, setExporting] = useState(false); - const sizeLimitRef = useRef() as RefObject; - const messageCountRef = useRef() as RefObject; + const sizeLimitRef = useRef(null); + const messageCountRef = useRef(null); const [exportProgressText, setExportProgressText] = useState(_t("Processing…")); const [displayCancel, setCancelWarning] = useState(false); const [exportCancelled, setExportCancelled] = useState(false); diff --git a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx index 426b177038..df204093e9 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 { RefObject, SyntheticEvent, useRef, useState } from "react"; +import { 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() as RefObject; + const fieldRef = useRef(null); const onSubmit = async (e: SyntheticEvent): Promise => { e.preventDefault(); diff --git a/src/components/views/dialogs/ReportEventDialog.tsx b/src/components/views/dialogs/ReportEventDialog.tsx index 02ec9d4a35..8eaa64bc34 100644 --- a/src/components/views/dialogs/ReportEventDialog.tsx +++ b/src/components/views/dialogs/ReportEventDialog.tsx @@ -255,7 +255,7 @@ export default class ReportEventDialog extends React.Component { }); } else { // Report to homeserver admin through the dedicated Matrix API. - await client.reportEvent(ev.getRoomId(), ev.getId(), -100, this.state.reason.trim()); + await client.reportEvent(ev.getRoomId()!, ev.getId()!, -100, this.state.reason.trim()); } // if the user should also be ignored, do that @@ -340,7 +340,7 @@ export default class ReportEventDialog extends React.Component { ); break; case NonStandardValue.Admin: - if (client.isRoomEncrypted(this.props.mxEvent.getRoomId())) { + if (client.isRoomEncrypted(this.props.mxEvent.getRoomId()!)) { subtitle = _t( "This room is dedicated to illegal or toxic content " + "or the moderators fail to moderate illegal or toxic content.\n" + diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx index b45b0582b8..05ab8c1749 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() as RefObject; - const scrollContainerRef = useRef() as RefObject; + const inputRef = useRef(null); + const scrollContainerRef = useRef(null); const cli = MatrixClientPeg.get(); const rovingContext = useContext(RovingTabIndexContext); const [query, _setQuery] = useState(initialText); diff --git a/src/components/views/elements/MiniAvatarUploader.tsx b/src/components/views/elements/MiniAvatarUploader.tsx index 464c89ba23..663c8fd3d0 100644 --- a/src/components/views/elements/MiniAvatarUploader.tsx +++ b/src/components/views/elements/MiniAvatarUploader.tsx @@ -16,7 +16,7 @@ limitations under the License. import classNames from "classnames"; import { EventType } from "matrix-js-sdk/src/@types/event"; -import React, { useContext, useRef, useState, MouseEvent, ReactNode, RefObject } from "react"; +import React, { useContext, useRef, useState, MouseEvent, ReactNode } from "react"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import RoomContext from "../../../contexts/RoomContext"; @@ -59,7 +59,7 @@ const MiniAvatarUploader: React.FC = ({ setShow(false); }, 13000); // hide after being shown for 10 seconds - const uploadRef = useRef() as RefObject; + const uploadRef = useRef(null); const label = hasAvatar || busy ? hasAvatarLabel : noAvatarLabel; diff --git a/src/components/views/elements/RoomTopic.tsx b/src/components/views/elements/RoomTopic.tsx index ad59012c78..eb9ae028a4 100644 --- a/src/components/views/elements/RoomTopic.tsx +++ b/src/components/views/elements/RoomTopic.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { RefObject, useCallback, useContext, useRef } from "react"; +import React, { useCallback, useContext, useRef } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import classNames from "classnames"; import { EventType } from "matrix-js-sdk/src/@types/event"; @@ -38,7 +38,7 @@ interface IProps extends React.HTMLProps { export default function RoomTopic({ room, ...props }: IProps): JSX.Element { const client = useContext(MatrixClientContext); - const ref = useRef() as RefObject; + const ref = useRef(null); const topic = useTopic(room); const body = topicToHtml(topic?.text, topic?.html, ref); diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index c0fb6dd443..270e837a70 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -1504,9 +1504,10 @@ export const UserInfoHeader: React.FC<{ const avatarUrl = (member as RoomMember).getMxcAvatarUrl ? (member as RoomMember).getMxcAvatarUrl() : (member as User).avatarUrl; - if (!avatarUrl) return; const httpUrl = mediaFromMxc(avatarUrl).srcHttp; + if (!httpUrl) return; + const params = { src: httpUrl, name: (member as RoomMember).name || (member as User).displayName, diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 66ce83d6e8..dfcc6f27a1 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -100,14 +100,14 @@ export default class AuxPanel extends React.Component { if (this.props.room && SettingsStore.getValue("feature_state_counters")) { const stateEvs = this.props.room.currentState.getStateEvents("re.jki.counter"); - stateEvs.sort((a, b) => lexicographicCompare(a.getStateKey(), b.getStateKey())); + stateEvs.sort((a, b) => lexicographicCompare(a.getStateKey()!, b.getStateKey()!)); for (const ev of stateEvs) { const title = ev.getContent().title; const value = ev.getContent().value; const link = ev.getContent().link; const severity = ev.getContent().severity || "normal"; - const stateKey = ev.getStateKey(); + const stateKey = ev.getStateKey()!; // We want a non-empty title but can accept falsy values (e.g. // zero) diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 2eb19e9e3c..d7d2535610 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -765,7 +765,7 @@ export default class BasicMessageEditor extends React.Component public render(): React.ReactNode { let autoComplete: JSX.Element | undefined; - if (this.state.autoComplete) { + if (this.state.autoComplete && this.state.query) { const query = this.state.query; const queryLen = query.length; autoComplete = ( @@ -800,8 +800,8 @@ export default class BasicMessageEditor extends React.Component const { completionIndex } = this.state; const hasAutocomplete = Boolean(this.state.autoComplete); let activeDescendant: string | undefined; - if (hasAutocomplete && completionIndex >= 0) { - activeDescendant = generateCompletionDomId(completionIndex); + if (hasAutocomplete && completionIndex! >= 0) { + activeDescendant = generateCompletionDomId(completionIndex!); } return ( diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index bed42fd0a8..96c66dddfb 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -229,11 +229,11 @@ class EditMessageComposer extends React.Component { const item = SendHistoryManager.createItem(this.model); this.clearPreviousEdit(); - localStorage.setItem(this.editorRoomKey, this.props.editState.getEvent().getId()); + localStorage.setItem(this.editorRoomKey, this.props.editState.getEvent().getId()!); localStorage.setItem(this.editorStateKey, JSON.stringify(item)); }; @@ -329,7 +329,7 @@ class EditMessageComposer extends React.Component { if (ev.button != 0 || ev.metaKey) return; ev.preventDefault(); - let src = p["og:image"]; + let src: string | null | undefined = p["og:image"]; if (src?.startsWith("mxc://")) { src = mediaFromMxc(src).srcHttp; } + if (!src) return; + const params: Omit, "onFinished"> = { src: src, width: p["og:image:width"], diff --git a/src/components/views/rooms/MessageComposerButtons.tsx b/src/components/views/rooms/MessageComposerButtons.tsx index 9572a118eb..17990d3aa6 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, RefObject, useContext, useRef } from "react"; +import React, { createContext, MouseEventHandler, ReactElement, ReactNode, 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() as RefObject; + const uploadInput = useRef(null); const onUploadClick = (): void => { if (cli?.isGuest()) { diff --git a/src/components/views/rooms/ReadReceiptGroup.tsx b/src/components/views/rooms/ReadReceiptGroup.tsx index 3472ee8db3..7eb625de47 100644 --- a/src/components/views/rooms/ReadReceiptGroup.tsx +++ b/src/components/views/rooms/ReadReceiptGroup.tsx @@ -293,7 +293,7 @@ interface ISectionHeaderProps { } function SectionHeader({ className, children }: PropsWithChildren): JSX.Element { - const ref = useRef(); + const ref = useRef(null); const [onFocus] = useRovingTabIndex(ref); return ( diff --git a/src/components/views/rooms/ReadReceiptMarker.tsx b/src/components/views/rooms/ReadReceiptMarker.tsx index e5e2fafdd7..6d453085d5 100644 --- a/src/components/views/rooms/ReadReceiptMarker.tsx +++ b/src/components/views/rooms/ReadReceiptMarker.tsx @@ -124,7 +124,7 @@ export default class ReadReceiptMarker extends React.PureComponent { - const tooltipRef = useRef() as RefObject; + const tooltipRef = useRef(null); const crumbs = useEventEmitterState(BreadcrumbsStore.instance, UPDATE_EVENT, () => BreadcrumbsStore.instance.rooms); const content = ( diff --git a/src/components/views/rooms/ReplyTile.tsx b/src/components/views/rooms/ReplyTile.tsx index b9a5766dd9..f3fbf73db8 100644 --- a/src/components/views/rooms/ReplyTile.tsx +++ b/src/components/views/rooms/ReplyTile.tsx @@ -134,7 +134,7 @@ export default class ReplyTile extends React.PureComponent { let permalink = "#"; if (this.props.permalinkCreator) { - permalink = this.props.permalinkCreator.forEvent(mxEvent.getId()); + permalink = this.props.permalinkCreator.forEvent(mxEvent.getId()!); } let sender; diff --git a/src/components/views/spaces/SpaceBasicSettings.tsx b/src/components/views/spaces/SpaceBasicSettings.tsx index 5ce844cc86..30c05c8ddd 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, RefObject, useRef, useState } from "react"; +import React, { ChangeEvent, 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() as RefObject; + const avatarUploadRef = useRef(null); const [avatar, setAvatarDataUrl] = useState(avatarUrl); // avatar data url cache let avatarSection; diff --git a/src/customisations/Media.ts b/src/customisations/Media.ts index 9c07288b18..e9cade175f 100644 --- a/src/customisations/Media.ts +++ b/src/customisations/Media.ts @@ -77,7 +77,7 @@ export class Media { */ public get srcHttp(): string | null { // eslint-disable-next-line no-restricted-properties - return this.client.mxcUrlToHttp(this.srcMxc); + return this.client.mxcUrlToHttp(this.srcMxc) || null; } /** diff --git a/src/resizer/resizer.ts b/src/resizer/resizer.ts index d45898b525..07e74337a7 100644 --- a/src/resizer/resizer.ts +++ b/src/resizer/resizer.ts @@ -44,7 +44,7 @@ export default class Resizer { // TODO move vertical/horizontal to config option/container class // as it doesn't make sense to mix them within one container/Resizer public constructor( - public container: HTMLElement, + public container: HTMLElement | null, private readonly distributorCtor: { new (item: ResizeItem): FixedDistributor; createItem( @@ -53,7 +53,7 @@ export default class Resizer { sizer: Sizer, container?: HTMLElement, ): ResizeItem; - createSizer(containerElement: HTMLElement, vertical: boolean, reverse: boolean): Sizer; + createSizer(containerElement: HTMLElement | null, vertical: boolean, reverse: boolean): Sizer; }, public readonly config?: C, ) { @@ -71,13 +71,13 @@ export default class Resizer { public attach(): void { const attachment = this?.config?.handler?.parentElement ?? this.container; - attachment.addEventListener("mousedown", this.onMouseDown, false); + attachment?.addEventListener("mousedown", this.onMouseDown, false); window.addEventListener("resize", this.onResize); } public detach(): void { const attachment = this?.config?.handler?.parentElement ?? this.container; - attachment.removeEventListener("mousedown", this.onMouseDown, false); + attachment?.removeEventListener("mousedown", this.onMouseDown, false); window.removeEventListener("resize", this.onResize); } @@ -194,7 +194,7 @@ export default class Resizer { const Distributor = this.distributorCtor; const useItemContainer = this.config?.handler ? this.container : undefined; const sizer = Distributor.createSizer(this.container, vertical, reverse); - const item = Distributor.createItem(resizeHandle, this, sizer, useItemContainer); + const item = Distributor.createItem(resizeHandle, this, sizer, useItemContainer ?? undefined); const distributor = new Distributor(item); return { sizer, distributor }; } diff --git a/test/components/structures/AutocompleteInput-test.tsx b/test/components/structures/AutocompleteInput-test.tsx index 9827ba2dda..76a007a25e 100644 --- a/test/components/structures/AutocompleteInput-test.tsx +++ b/test/components/structures/AutocompleteInput-test.tsx @@ -24,8 +24,20 @@ import { AutocompleteInput } from "../../../src/components/structures/Autocomple describe("AutocompleteInput", () => { const mockCompletion: ICompletion[] = [ - { type: "user", completion: "user_1", completionId: "@user_1:host.local", range: { start: 1, end: 1 } }, - { type: "user", completion: "user_2", completionId: "@user_2:host.local", range: { start: 1, end: 1 } }, + { + type: "user", + completion: "user_1", + completionId: "@user_1:host.local", + range: { start: 1, end: 1 }, + component:
, + }, + { + type: "user", + completion: "user_2", + completionId: "@user_2:host.local", + range: { start: 1, end: 1 }, + component:
, + }, ]; const constructMockProvider = (data: ICompletion[]) => diff --git a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts index 366375380c..0553f61f14 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts @@ -15,6 +15,7 @@ limitations under the License. */ import { mocked } from "jest-mock"; +import React from "react"; import { ICompletion } from "../../../../../../src/autocomplete/Autocompleter"; import { @@ -34,6 +35,7 @@ const createMockCompletion = (props: Partial): ICompletion => { return { completion: "mock", range: { beginning: true, start: 0, end: 0 }, + component: React.createElement("div"), ...props, }; }; diff --git a/test/components/views/settings/AddPrivilegedUsers-test.tsx b/test/components/views/settings/AddPrivilegedUsers-test.tsx index 68acb7395d..118529349c 100644 --- a/test/components/views/settings/AddPrivilegedUsers-test.tsx +++ b/test/components/views/settings/AddPrivilegedUsers-test.tsx @@ -32,9 +32,21 @@ import { ICompletion } from "../../../../src/autocomplete/Autocompleter"; jest.mock("../../../../src/autocomplete/UserProvider"); const completions: ICompletion[] = [ - { type: "user", completion: "user_1", completionId: "@user_1:host.local", range: { start: 1, end: 1 } }, - { type: "user", completion: "user_2", completionId: "@user_2:host.local", range: { start: 1, end: 1 } }, - { type: "user", completion: "user_without_completion_id", range: { start: 1, end: 1 } }, + { + component:
, + type: "user", + completion: "user_1", + completionId: "@user_1:host.local", + range: { start: 1, end: 1 }, + }, + { + component:
, + type: "user", + completion: "user_2", + completionId: "@user_2:host.local", + range: { start: 1, end: 1 }, + }, + { component:
, type: "user", completion: "user_without_completion_id", range: { start: 1, end: 1 } }, ]; describe("", () => {