Conform more of the codebase to strictNullChecks
(#10358
* Conform more of the codebase to `strictNullChecks` * Fix types * Iterate * Iterate
This commit is contained in:
parent
41d88ad6ae
commit
503df62191
76 changed files with 323 additions and 327 deletions
|
@ -80,7 +80,7 @@ export default class DownloadActionButton extends React.PureComponent<IProps, IS
|
|||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
let spinner: JSX.Element;
|
||||
let spinner: JSX.Element | undefined;
|
||||
if (this.state.loading) {
|
||||
spinner = <Spinner w={18} h={18} />;
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ interface IProps {
|
|||
|
||||
interface IState {
|
||||
canRedact: boolean;
|
||||
sendStatus: EventStatus;
|
||||
sendStatus: EventStatus | null;
|
||||
}
|
||||
|
||||
export default class EditHistoryMessage extends React.PureComponent<IProps, IState> {
|
||||
|
@ -59,12 +59,10 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
|
|||
super(props);
|
||||
|
||||
const cli = MatrixClientPeg.get();
|
||||
const { userId } = cli.credentials;
|
||||
const userId = cli.getSafeUserId();
|
||||
const event = this.props.mxEvent;
|
||||
const room = cli.getRoom(event.getRoomId());
|
||||
if (event.localRedactionEvent()) {
|
||||
event.localRedactionEvent().on(MatrixEventEvent.Status, this.onAssociatedStatusChanged);
|
||||
}
|
||||
event.localRedactionEvent()?.on(MatrixEventEvent.Status, this.onAssociatedStatusChanged);
|
||||
const canRedact = room.currentState.maySendRedactionForEvent(event, userId);
|
||||
this.state = { canRedact, sendStatus: event.getAssociatedStatus() };
|
||||
}
|
||||
|
@ -121,9 +119,7 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
|
|||
unmountPills(this.pills);
|
||||
unmountTooltips(this.tooltips);
|
||||
const event = this.props.mxEvent;
|
||||
if (event.localRedactionEvent()) {
|
||||
event.localRedactionEvent().off(MatrixEventEvent.Status, this.onAssociatedStatusChanged);
|
||||
}
|
||||
event.localRedactionEvent()?.off(MatrixEventEvent.Status, this.onAssociatedStatusChanged);
|
||||
}
|
||||
|
||||
public componentDidUpdate(): void {
|
||||
|
@ -133,12 +129,12 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
|
|||
|
||||
private renderActionBar(): JSX.Element {
|
||||
// hide the button when already redacted
|
||||
let redactButton: JSX.Element;
|
||||
let redactButton: JSX.Element | undefined;
|
||||
if (!this.props.mxEvent.isRedacted() && !this.props.isBaseEvent && this.state.canRedact) {
|
||||
redactButton = <AccessibleButton onClick={this.onRedactClick}>{_t("Remove")}</AccessibleButton>;
|
||||
}
|
||||
|
||||
let viewSourceButton: JSX.Element;
|
||||
let viewSourceButton: JSX.Element | undefined;
|
||||
if (SettingsStore.getValue("developerMode")) {
|
||||
viewSourceButton = (
|
||||
<AccessibleButton onClick={this.onViewSourceClick}>{_t("View Source")}</AccessibleButton>
|
||||
|
@ -189,9 +185,8 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
|
|||
}
|
||||
|
||||
const timestamp = formatTime(new Date(mxEvent.getTs()), this.props.isTwelveHour);
|
||||
const isSending = ["sending", "queued", "encrypting"].indexOf(this.state.sendStatus) !== -1;
|
||||
const classes = classNames({
|
||||
mx_EventTile: true,
|
||||
const isSending = ["sending", "queued", "encrypting"].includes(this.state.sendStatus!);
|
||||
const classes = classNames("mx_EventTile", {
|
||||
// Note: we keep the `sending` state class for tests, not for our styles
|
||||
mx_EventTile_sending: isSending,
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@ import { RovingAccessibleButton, useRovingTabIndex } from "../../../accessibilit
|
|||
|
||||
interface IProps {
|
||||
ts: number;
|
||||
onDatePicked?: (dateString: string) => void;
|
||||
onDatePicked: (dateString: string) => void;
|
||||
}
|
||||
|
||||
const JumpToDatePicker: React.FC<IProps> = ({ ts, onDatePicked }: IProps) => {
|
||||
|
|
|
@ -165,7 +165,7 @@ export default class LegacyCallEvent extends React.PureComponent<IProps, IState>
|
|||
{this.props.timestamp}
|
||||
</div>
|
||||
);
|
||||
} else if ([CallErrorCode.UserHangup, "user hangup"].includes(hangupReason) || !hangupReason) {
|
||||
} else if (!hangupReason || [CallErrorCode.UserHangup, "user hangup"].includes(hangupReason)) {
|
||||
// workaround for https://github.com/vector-im/element-web/issues/5178
|
||||
// it seems Android randomly sets a reason of "user hangup" which is
|
||||
// interpreted as an error code :(
|
||||
|
|
|
@ -140,7 +140,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
|
|||
error?.message === LocationShareError.MapStyleUrlNotConfigured ||
|
||||
error?.message === LocationShareError.MapStyleUrlNotReachable;
|
||||
const displayStatus = getBeaconDisplayStatus(
|
||||
isLive,
|
||||
!!isLive,
|
||||
latestLocationState,
|
||||
// if we are unable to display maps because it is not configured for the server
|
||||
// don't display an error
|
||||
|
@ -174,7 +174,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
|
|||
map = (
|
||||
<Map
|
||||
id={mapId}
|
||||
centerGeoUri={latestLocationState.uri}
|
||||
centerGeoUri={latestLocationState?.uri}
|
||||
onError={setError}
|
||||
onClick={onClick}
|
||||
className="mx_MBeaconBody_map"
|
||||
|
@ -184,7 +184,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
|
|||
map={map}
|
||||
id={`${mapId}-marker`}
|
||||
geoUri={latestLocationState.uri}
|
||||
roomMember={markerRoomMember}
|
||||
roomMember={markerRoomMember ?? undefined}
|
||||
useMemberColor
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ComponentProps, createRef } from "react";
|
||||
import React, { ComponentProps, createRef, ReactNode } from "react";
|
||||
import { Blurhash } from "react-blurhash";
|
||||
import classNames from "classnames";
|
||||
import { CSSTransition, SwitchTransition } from "react-transition-group";
|
||||
|
@ -47,8 +47,8 @@ enum Placeholder {
|
|||
}
|
||||
|
||||
interface IState {
|
||||
contentUrl?: string;
|
||||
thumbUrl?: string;
|
||||
contentUrl: string | null;
|
||||
thumbUrl: string | null;
|
||||
isAnimated?: boolean;
|
||||
error?: Error;
|
||||
imgError: boolean;
|
||||
|
@ -78,6 +78,8 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
this.reconnectedListener = createReconnectedListener(this.clearError);
|
||||
|
||||
this.state = {
|
||||
contentUrl: null,
|
||||
thumbUrl: null,
|
||||
imgError: false,
|
||||
imgLoaded: false,
|
||||
hover: false,
|
||||
|
@ -126,7 +128,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
};
|
||||
}
|
||||
|
||||
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true);
|
||||
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", undefined, true);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -177,7 +179,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
this.setState({ imgLoaded: true, loadedImageDimensions });
|
||||
};
|
||||
|
||||
private getContentUrl(): string {
|
||||
private getContentUrl(): string | null {
|
||||
// During export, the content url will point to the MSC, which will later point to a local url
|
||||
if (this.props.forExport) return this.media.srcMxc;
|
||||
return this.media.srcHttp;
|
||||
|
@ -187,7 +189,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
return mediaFromContent(this.props.mxEvent.getContent());
|
||||
}
|
||||
|
||||
private getThumbUrl(): string {
|
||||
private getThumbUrl(): string | null {
|
||||
// FIXME: we let images grow as wide as you like, rather than capped to 800x600.
|
||||
// So either we need to support custom timeline widths here, or reimpose the cap, otherwise the
|
||||
// thumbnail resolution will be unnecessarily reduced.
|
||||
|
@ -242,8 +244,8 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
private async downloadImage(): Promise<void> {
|
||||
if (this.state.contentUrl) return; // already downloaded
|
||||
|
||||
let thumbUrl: string;
|
||||
let contentUrl: string;
|
||||
let thumbUrl: string | null;
|
||||
let contentUrl: string | null;
|
||||
if (this.props.mediaEventHelper.media.isEncrypted) {
|
||||
try {
|
||||
[contentUrl, thumbUrl] = await Promise.all([
|
||||
|
@ -276,7 +278,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
// If there is no included non-animated thumbnail then we will generate our own, we can't depend on the server
|
||||
// because 1. encryption and 2. we can't ask the server specifically for a non-animated thumbnail.
|
||||
if (isAnimated && !SettingsStore.getValue("autoplayGifs")) {
|
||||
if (!thumbUrl || !content?.info.thumbnail_info || mayBeAnimated(content.info.thumbnail_info.mimetype)) {
|
||||
if (!thumbUrl || !content?.info?.thumbnail_info || mayBeAnimated(content.info.thumbnail_info.mimetype)) {
|
||||
const img = document.createElement("img");
|
||||
const loadPromise = new Promise((resolve, reject) => {
|
||||
img.onload = resolve;
|
||||
|
@ -364,7 +366,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
protected getBanner(content: IMediaEventContent): JSX.Element {
|
||||
protected getBanner(content: IMediaEventContent): ReactNode {
|
||||
// Hide it for the threads list & the file panel where we show it as text anyway.
|
||||
if (
|
||||
[TimelineRenderingType.ThreadsList, TimelineRenderingType.File].includes(this.context.timelineRenderingType)
|
||||
|
@ -429,9 +431,9 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
forcedHeight ?? this.props.maxImageHeight,
|
||||
);
|
||||
|
||||
let img: JSX.Element;
|
||||
let placeholder: JSX.Element;
|
||||
let gifLabel: JSX.Element;
|
||||
let img: JSX.Element | undefined;
|
||||
let placeholder: JSX.Element | undefined;
|
||||
let gifLabel: JSX.Element | undefined;
|
||||
|
||||
if (!this.props.forExport && !this.state.imgLoaded) {
|
||||
const classes = classNames("mx_MImageBody_placeholder", {
|
||||
|
@ -471,7 +473,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
gifLabel = <p className="mx_MImageBody_gifLabel">GIF</p>;
|
||||
}
|
||||
|
||||
let banner: JSX.Element;
|
||||
let banner: ReactNode | undefined;
|
||||
if (this.state.showImage && this.state.hover) {
|
||||
banner = this.getBanner(content);
|
||||
}
|
||||
|
@ -526,7 +528,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
}
|
||||
|
||||
// Overridden by MStickerBody
|
||||
protected getPlaceholder(width: number, height: number): JSX.Element {
|
||||
protected getPlaceholder(width: number, height: number): ReactNode {
|
||||
const blurhash = this.props.mxEvent.getContent().info?.[BLURHASH_FIELD];
|
||||
|
||||
if (blurhash) {
|
||||
|
@ -540,12 +542,12 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
}
|
||||
|
||||
// Overridden by MStickerBody
|
||||
protected getTooltip(): JSX.Element {
|
||||
protected getTooltip(): ReactNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Overridden by MStickerBody
|
||||
protected getFileBody(): string | JSX.Element {
|
||||
protected getFileBody(): ReactNode {
|
||||
if (this.props.forExport) return null;
|
||||
/*
|
||||
* In the room timeline or the thread context we don't need the download
|
||||
|
@ -577,7 +579,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
|||
}
|
||||
|
||||
let contentUrl = this.state.contentUrl;
|
||||
let thumbUrl: string;
|
||||
let thumbUrl: string | undefined;
|
||||
if (this.props.forExport) {
|
||||
contentUrl = this.props.mxEvent.getContent().url ?? this.props.mxEvent.getContent().file?.url;
|
||||
thumbUrl = contentUrl;
|
||||
|
|
|
@ -41,7 +41,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
|
|||
const widgetId = this.props.mxEvent.getStateKey();
|
||||
const widget = WidgetStore.instance.getRoom(room.roomId, true).widgets.find((w) => w.id === widgetId);
|
||||
|
||||
let joinCopy = _t("Join the conference at the top of this room");
|
||||
let joinCopy: string | null = _t("Join the conference at the top of this room");
|
||||
if (widget && WidgetLayoutStore.instance.isInContainer(room, widget, Container.Right)) {
|
||||
joinCopy = _t("Join the conference from the room information card on the right");
|
||||
} else if (!widget) {
|
||||
|
|
|
@ -124,7 +124,7 @@ export default class MKeyVerificationRequest extends React.Component<IProps> {
|
|||
|
||||
let title: string;
|
||||
let subtitle: string;
|
||||
let stateNode: JSX.Element;
|
||||
let stateNode: JSX.Element | undefined;
|
||||
|
||||
if (!request.canAccept) {
|
||||
let stateLabel;
|
||||
|
|
|
@ -37,7 +37,7 @@ import { IBodyProps } from "./IBodyProps";
|
|||
import { createReconnectedListener } from "../../../utils/connection";
|
||||
|
||||
interface IState {
|
||||
error: Error;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
export default class MLocationBody extends React.Component<IBodyProps, IState> {
|
||||
|
@ -58,9 +58,7 @@ export default class MLocationBody extends React.Component<IBodyProps, IState> {
|
|||
|
||||
this.reconnectedListener = createReconnectedListener(this.clearError);
|
||||
|
||||
this.state = {
|
||||
error: undefined,
|
||||
};
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
private onClick = (): void => {
|
||||
|
@ -149,7 +147,12 @@ export const LocationBodyContent: React.FC<LocationBodyContentProps> = ({
|
|||
const mapElement = (
|
||||
<Map id={mapId} centerGeoUri={geoUri} onClick={onClick} onError={onError} className="mx_MLocationBody_map">
|
||||
{({ map }) => (
|
||||
<SmartMarker map={map} id={`${mapId}-marker`} geoUri={geoUri} roomMember={markerRoomMember} />
|
||||
<SmartMarker
|
||||
map={map}
|
||||
id={`${mapId}-marker`}
|
||||
geoUri={geoUri}
|
||||
roomMember={markerRoomMember ?? undefined}
|
||||
/>
|
||||
)}
|
||||
</Map>
|
||||
);
|
||||
|
|
|
@ -128,7 +128,7 @@ export function launchPollEditor(mxEvent: MatrixEvent, getRelationsForEvent?: Ge
|
|||
PollCreateDialog,
|
||||
{
|
||||
room: MatrixClientPeg.get().getRoom(mxEvent.getRoomId()),
|
||||
threadId: mxEvent.getThread()?.id ?? null,
|
||||
threadId: mxEvent.getThread()?.id,
|
||||
editingMxEvent: mxEvent,
|
||||
},
|
||||
"mx_CompoundDialog",
|
||||
|
|
|
@ -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 MImageBody from "./MImageBody";
|
||||
import { BLURHASH_FIELD } from "../../../utils/image-media";
|
||||
|
@ -33,7 +33,7 @@ export default class MStickerBody extends MImageBody {
|
|||
// MStickerBody doesn't need a wrapping `<a href=...>`, but it does need extra padding
|
||||
// which is added by mx_MStickerBody_wrapper
|
||||
protected wrapImage(contentUrl: string, children: React.ReactNode): JSX.Element {
|
||||
let onClick = null;
|
||||
let onClick: React.MouseEventHandler | undefined;
|
||||
if (!this.state.showImage) {
|
||||
onClick = this.onClick;
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ export default class MStickerBody extends MImageBody {
|
|||
}
|
||||
|
||||
// Placeholder to show in place of the sticker image if img onLoad hasn't fired yet.
|
||||
protected getPlaceholder(width: number, height: number): JSX.Element {
|
||||
protected getPlaceholder(width: number, height: number): ReactNode {
|
||||
if (this.props.mxEvent.getContent().info?.[BLURHASH_FIELD]) return super.getPlaceholder(width, height);
|
||||
return (
|
||||
<img
|
||||
|
@ -61,7 +61,7 @@ export default class MStickerBody extends MImageBody {
|
|||
}
|
||||
|
||||
// Tooltip to show on mouse over
|
||||
protected getTooltip(): JSX.Element {
|
||||
protected getTooltip(): ReactNode {
|
||||
const content = this.props.mxEvent && this.props.mxEvent.getContent();
|
||||
|
||||
if (!content || !content.body || !content.info || !content.info.w) return null;
|
||||
|
@ -74,11 +74,11 @@ export default class MStickerBody extends MImageBody {
|
|||
}
|
||||
|
||||
// Don't show "Download this_file.png ..."
|
||||
protected getFileBody(): JSX.Element {
|
||||
protected getFileBody(): ReactNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected getBanner(content: IMediaEventContent): JSX.Element {
|
||||
protected getBanner(content: IMediaEventContent): ReactNode {
|
||||
return null; // we don't need a banner, we have a tooltip
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 { decode } from "blurhash";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
|
@ -31,13 +31,13 @@ import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContex
|
|||
import MediaProcessingError from "./shared/MediaProcessingError";
|
||||
|
||||
interface IState {
|
||||
decryptedUrl?: string;
|
||||
decryptedThumbnailUrl?: string;
|
||||
decryptedBlob?: Blob;
|
||||
decryptedUrl: string | null;
|
||||
decryptedThumbnailUrl: string | null;
|
||||
decryptedBlob: Blob | null;
|
||||
error?: any;
|
||||
fetchingData: boolean;
|
||||
posterLoading: boolean;
|
||||
blurhashUrl: string;
|
||||
blurhashUrl: string | null;
|
||||
}
|
||||
|
||||
export default class MVideoBody extends React.PureComponent<IBodyProps, IState> {
|
||||
|
@ -61,21 +61,21 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
|
|||
};
|
||||
}
|
||||
|
||||
private getContentUrl(): string | null {
|
||||
private getContentUrl(): string | undefined {
|
||||
const content = this.props.mxEvent.getContent<IMediaEventContent>();
|
||||
// During export, the content url will point to the MSC, which will later point to a local url
|
||||
if (this.props.forExport) return content.file?.url || content.url;
|
||||
if (this.props.forExport) return content.file?.url ?? content.url;
|
||||
const media = mediaFromContent(content);
|
||||
if (media.isEncrypted) {
|
||||
return this.state.decryptedUrl;
|
||||
return this.state.decryptedUrl ?? undefined;
|
||||
} else {
|
||||
return media.srcHttp;
|
||||
return media.srcHttp ?? undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private hasContentUrl(): boolean {
|
||||
const url = this.getContentUrl();
|
||||
return url && !url.startsWith("data:");
|
||||
return !!url && !url.startsWith("data:");
|
||||
}
|
||||
|
||||
private getThumbUrl(): string | null {
|
||||
|
@ -227,7 +227,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
|
|||
);
|
||||
}
|
||||
|
||||
private getFileBody = (): JSX.Element => {
|
||||
private getFileBody = (): ReactNode => {
|
||||
if (this.props.forExport) return null;
|
||||
return this.showFileBody && <MFileBody {...this.props} showGenericPlaceholder={false} />;
|
||||
};
|
||||
|
@ -271,7 +271,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
|
|||
|
||||
const contentUrl = this.getContentUrl();
|
||||
const thumbUrl = this.getThumbUrl();
|
||||
let poster = null;
|
||||
let poster: string | undefined;
|
||||
let preload = "metadata";
|
||||
if (content.info && thumbUrl) {
|
||||
poster = thumbUrl;
|
||||
|
|
|
@ -63,7 +63,7 @@ interface IOptionsButtonProps {
|
|||
mxEvent: MatrixEvent;
|
||||
// TODO: Types
|
||||
getTile: () => any | null;
|
||||
getReplyChain: () => ReplyChain;
|
||||
getReplyChain: () => ReplyChain | null;
|
||||
permalinkCreator: RoomPermalinkCreator;
|
||||
onFocusChange: (menuDisplayed: boolean) => void;
|
||||
getRelationsForEvent?: GetRelationsForEvent;
|
||||
|
@ -97,10 +97,10 @@ const OptionsButton: React.FC<IOptionsButtonProps> = ({
|
|||
[openMenu, onFocus],
|
||||
);
|
||||
|
||||
let contextMenu: ReactElement | null;
|
||||
if (menuDisplayed) {
|
||||
let contextMenu: ReactElement | undefined;
|
||||
if (menuDisplayed && button.current) {
|
||||
const tile = getTile && getTile();
|
||||
const replyChain = getReplyChain && getReplyChain();
|
||||
const replyChain = getReplyChain();
|
||||
|
||||
const buttonRect = button.current.getBoundingClientRect();
|
||||
contextMenu = (
|
||||
|
@ -109,7 +109,7 @@ const OptionsButton: React.FC<IOptionsButtonProps> = ({
|
|||
mxEvent={mxEvent}
|
||||
permalinkCreator={permalinkCreator}
|
||||
eventTileOps={tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined}
|
||||
collapseReplyChain={replyChain && replyChain.canCollapse() ? replyChain.collapse : undefined}
|
||||
collapseReplyChain={replyChain?.canCollapse() ? replyChain.collapse : undefined}
|
||||
onFinished={closeMenu}
|
||||
getRelationsForEvent={getRelationsForEvent}
|
||||
/>
|
||||
|
@ -148,8 +148,8 @@ const ReactButton: React.FC<IReactButtonProps> = ({ mxEvent, reactions, onFocusC
|
|||
onFocusChange(menuDisplayed);
|
||||
}, [onFocusChange, menuDisplayed]);
|
||||
|
||||
let contextMenu;
|
||||
if (menuDisplayed) {
|
||||
let contextMenu: JSX.Element | undefined;
|
||||
if (menuDisplayed && button.current) {
|
||||
const buttonRect = button.current.getBoundingClientRect();
|
||||
contextMenu = (
|
||||
<ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false}>
|
||||
|
@ -211,7 +211,7 @@ const ReplyInThreadButton: React.FC<IReplyInThreadButton> = ({ mxEvent }) => {
|
|||
if (mxEvent.getThread() && !mxEvent.isThreadRoot) {
|
||||
defaultDispatcher.dispatch<ShowThreadPayload>({
|
||||
action: Action.ShowThread,
|
||||
rootEvent: mxEvent.getThread().rootEvent,
|
||||
rootEvent: mxEvent.getThread()!.rootEvent,
|
||||
initialEvent: mxEvent,
|
||||
scroll_into_view: true,
|
||||
highlighted: true,
|
||||
|
@ -293,7 +293,7 @@ interface IMessageActionBarProps {
|
|||
reactions?: Relations | null | undefined;
|
||||
// TODO: Types
|
||||
getTile: () => any | null;
|
||||
getReplyChain: () => ReplyChain | undefined;
|
||||
getReplyChain: () => ReplyChain | null;
|
||||
permalinkCreator?: RoomPermalinkCreator;
|
||||
onFocusChange?: (menuDisplayed: boolean) => void;
|
||||
toggleThreadExpanded: () => void;
|
||||
|
@ -421,7 +421,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
|||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const toolbarOpts = [];
|
||||
const toolbarOpts: JSX.Element[] = [];
|
||||
if (canEditContent(this.props.mxEvent)) {
|
||||
toolbarOpts.push(
|
||||
<RovingAccessibleTooltipButton
|
||||
|
@ -452,8 +452,8 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
|||
|
||||
// We show a different toolbar for failed events, so detect that first.
|
||||
const mxEvent = this.props.mxEvent;
|
||||
const editStatus = mxEvent.replacingEvent() && mxEvent.replacingEvent().status;
|
||||
const redactStatus = mxEvent.localRedactionEvent() && mxEvent.localRedactionEvent().status;
|
||||
const editStatus = mxEvent.replacingEvent()?.status;
|
||||
const redactStatus = mxEvent.localRedactionEvent()?.status;
|
||||
const allowCancel = canCancel(mxEvent.status) || canCancel(editStatus) || canCancel(redactStatus);
|
||||
const isFailed = [mxEvent.status, editStatus, redactStatus].includes(EventStatus.NOT_SENT);
|
||||
if (allowCancel && isFailed) {
|
||||
|
|
|
@ -58,7 +58,7 @@ interface IProps extends Omit<IBodyProps, "onMessageAllowed" | "mediaEventHelper
|
|||
}
|
||||
|
||||
export interface IOperableEventTile {
|
||||
getEventTileOps(): IEventTileOps;
|
||||
getEventTileOps(): IEventTileOps | null;
|
||||
}
|
||||
|
||||
const baseBodyTypes = new Map<string, typeof React.Component>([
|
||||
|
@ -159,12 +159,12 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
|
|||
if (this.props.mxEvent.isDecryptionFailure()) {
|
||||
BodyType = DecryptionFailureBody;
|
||||
} else if (type && this.evTypes.has(type)) {
|
||||
BodyType = this.evTypes.get(type);
|
||||
BodyType = this.evTypes.get(type)!;
|
||||
} else if (msgtype && this.bodyTypes.has(msgtype)) {
|
||||
BodyType = this.bodyTypes.get(msgtype);
|
||||
BodyType = this.bodyTypes.get(msgtype)!;
|
||||
} else if (content.url) {
|
||||
// Fallback to MFileBody if there's a content URL
|
||||
BodyType = this.bodyTypes.get(MsgType.File);
|
||||
BodyType = this.bodyTypes.get(MsgType.File)!;
|
||||
} else {
|
||||
// Fallback to UnknownBody otherwise if not redacted
|
||||
BodyType = UnknownBody;
|
||||
|
|
|
@ -142,7 +142,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
|
|||
this.forceUpdate();
|
||||
};
|
||||
|
||||
private getMyReactions(): MatrixEvent[] {
|
||||
private getMyReactions(): MatrixEvent[] | null {
|
||||
const reactions = this.props.reactions;
|
||||
if (!reactions) {
|
||||
return null;
|
||||
|
@ -206,7 +206,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
|
|||
// Show the first MAX_ITEMS if there are MAX_ITEMS + 1 or more items.
|
||||
// The "+ 1" ensure that the "show all" reveals something that takes up
|
||||
// more space than the button itself.
|
||||
let showAllButton: JSX.Element;
|
||||
let showAllButton: JSX.Element | undefined;
|
||||
if (items.length > MAX_ITEMS_WHEN_LIMITED + 1 && !showAll) {
|
||||
items = items.slice(0, MAX_ITEMS_WHEN_LIMITED);
|
||||
showAllButton = (
|
||||
|
|
|
@ -106,9 +106,9 @@ export default class ReactionsRowButton extends React.PureComponent<IProps, ISta
|
|||
}
|
||||
|
||||
const room = this.context.getRoom(mxEvent.getRoomId());
|
||||
let label: string;
|
||||
let label: string | undefined;
|
||||
if (room) {
|
||||
const senders = [];
|
||||
const senders: string[] = [];
|
||||
for (const reactionEvent of reactionEvents) {
|
||||
const member = room.getMember(reactionEvent.getSender());
|
||||
senders.push(member?.name || reactionEvent.getSender());
|
||||
|
|
|
@ -43,5 +43,7 @@ export default function SenderProfile({ mxEvent, onClick, withTooltip }: IProps)
|
|||
emphasizeDisplayName={true}
|
||||
withTooltip={withTooltip}
|
||||
/>
|
||||
) : null;
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -342,7 +342,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
|||
if (node.tagName === "SPAN" && typeof node.getAttribute("data-mx-spoiler") === "string") {
|
||||
const spoilerContainer = document.createElement("span");
|
||||
|
||||
const reason = node.getAttribute("data-mx-spoiler");
|
||||
const reason = node.getAttribute("data-mx-spoiler") ?? undefined;
|
||||
node.removeAttribute("data-mx-spoiler"); // we don't want to recurse
|
||||
const spoiler = <Spoiler reason={reason} contentHtml={node.outerHTML} />;
|
||||
|
||||
|
@ -367,7 +367,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
|||
const node = nodes[i];
|
||||
if (node.tagName === "A" && node.getAttribute("href")) {
|
||||
if (this.isLinkPreviewable(node)) {
|
||||
links.push(node.getAttribute("href"));
|
||||
links.push(node.getAttribute("href")!);
|
||||
}
|
||||
} else if (node.tagName === "PRE" || node.tagName === "CODE" || node.tagName === "BLOCKQUOTE") {
|
||||
continue;
|
||||
|
@ -380,7 +380,8 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
|||
|
||||
private isLinkPreviewable(node: Element): boolean {
|
||||
// don't try to preview relative links
|
||||
if (!node.getAttribute("href").startsWith("http://") && !node.getAttribute("href").startsWith("https://")) {
|
||||
const href = node.getAttribute("href") ?? "";
|
||||
if (!href.startsWith("http://") && !href.startsWith("https://")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -389,24 +390,24 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
|||
// or from a full foo.bar/baz style schemeless URL) - or be a markdown-style
|
||||
// link, in which case we check the target text differs from the link value.
|
||||
// TODO: make this configurable?
|
||||
if (node.textContent.indexOf("/") > -1) {
|
||||
if (node.textContent?.includes("/")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const url = node.getAttribute("href");
|
||||
const host = url.match(/^https?:\/\/(.*?)(\/|$)/)[1];
|
||||
|
||||
// never preview permalinks (if anything we should give a smart
|
||||
// preview of the room/user they point to: nobody needs to be reminded
|
||||
// what the matrix.to site looks like).
|
||||
if (isPermalinkHost(host)) return false;
|
||||
|
||||
if (node.textContent?.toLowerCase().trim().startsWith(host.toLowerCase())) {
|
||||
// it's a "foo.pl" style link
|
||||
return false;
|
||||
} else {
|
||||
const url = node.getAttribute("href");
|
||||
const host = url.match(/^https?:\/\/(.*?)(\/|$)/)[1];
|
||||
|
||||
// never preview permalinks (if anything we should give a smart
|
||||
// preview of the room/user they point to: nobody needs to be reminded
|
||||
// what the matrix.to site looks like).
|
||||
if (isPermalinkHost(host)) return false;
|
||||
|
||||
if (node.textContent.toLowerCase().trim().startsWith(host.toLowerCase())) {
|
||||
// it's a "foo.pl" style link
|
||||
return false;
|
||||
} else {
|
||||
// it's a [foo bar](http://foo.com) style link
|
||||
return true;
|
||||
}
|
||||
// it's a [foo bar](http://foo.com) style link
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -434,7 +435,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
|||
* to start with (e.g. pills, links in the content).
|
||||
*/
|
||||
private onBodyLinkClick = (e: MouseEvent): void => {
|
||||
let target = e.target as HTMLLinkElement;
|
||||
let target: HTMLLinkElement | null = e.target as HTMLLinkElement;
|
||||
// links processed by linkifyjs have their own handler so don't handle those here
|
||||
if (target.classList.contains(linkifyOpts.className as string)) return;
|
||||
if (target.nodeName !== "A") {
|
||||
|
|
|
@ -34,16 +34,14 @@ interface IProps {
|
|||
}
|
||||
|
||||
interface IState {
|
||||
error: Error;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
export default class TileErrorBoundary extends React.Component<IProps, IState> {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
error: null,
|
||||
};
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
public static getDerivedStateFromError(error: Error): Partial<IState> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue