Conform src/components/views/messages/* code to strictNullChecks ()

This commit is contained in:
Michael Telatynski 2023-03-29 08:22:35 +01:00 committed by GitHub
parent 7cb90d0f78
commit cefd94859c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 109 additions and 96 deletions

View file

@ -63,17 +63,17 @@ export default class DownloadActionButton extends React.PureComponent<IProps, IS
if (this.state.blob) { if (this.state.blob) {
// Cheat and trigger a download, again. // Cheat and trigger a download, again.
return this.doDownload(); return this.doDownload(this.state.blob);
} }
const blob = await this.props.mediaEventHelperGet().sourceBlob.value; const blob = await this.props.mediaEventHelperGet().sourceBlob.value;
this.setState({ blob }); this.setState({ blob });
await this.doDownload(); await this.doDownload(blob);
}; };
private async doDownload(): Promise<void> { private async doDownload(blob: Blob): Promise<void> {
await this.downloader.download({ await this.downloader.download({
blob: this.state.blob, blob,
name: this.props.mediaEventHelperGet().fileName, name: this.props.mediaEventHelperGet().fileName,
}); });
this.setState({ loading: false }); this.setState({ loading: false });

View file

@ -79,7 +79,7 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
ConfirmAndWaitRedactDialog, ConfirmAndWaitRedactDialog,
{ {
redact: async () => { redact: async () => {
await cli.redactEvent(event.getRoomId(), event.getId()); await cli.redactEvent(event.getRoomId()!, event.getId());
}, },
}, },
"mx_Dialog_confirmredact", "mx_Dialog_confirmredact",

View file

@ -76,7 +76,7 @@ export default class MAudioBody extends React.PureComponent<IBodyProps, IState>
this.setState({ playback }); this.setState({ playback });
if (isVoiceMessage(this.props.mxEvent)) { if (isVoiceMessage(this.props.mxEvent)) {
PlaybackQueue.forRoom(this.props.mxEvent.getRoomId()).unsortedEnqueue(this.props.mxEvent, playback); PlaybackQueue.forRoom(this.props.mxEvent.getRoomId()!).unsortedEnqueue(this.props.mxEvent, playback);
} }
// Note: the components later on will handle preparing the Playback class for us. // Note: the components later on will handle preparing the Playback class for us.

View file

@ -22,6 +22,7 @@ import {
MatrixEventEvent, MatrixEventEvent,
MatrixClient, MatrixClient,
RelationType, RelationType,
IRedactOpts,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { BeaconLocationState } from "matrix-js-sdk/src/content-helpers"; import { BeaconLocationState } from "matrix-js-sdk/src/content-helpers";
import { randomString } from "matrix-js-sdk/src/randomstring"; import { randomString } from "matrix-js-sdk/src/randomstring";
@ -107,15 +108,15 @@ const useHandleBeaconRedaction = (
const onBeforeBeaconInfoRedaction = useCallback( const onBeforeBeaconInfoRedaction = useCallback(
(_event: MatrixEvent, redactionEvent: MatrixEvent) => { (_event: MatrixEvent, redactionEvent: MatrixEvent) => {
const relations = getRelationsForEvent const relations = getRelationsForEvent
? getRelationsForEvent(event.getId(), RelationType.Reference, M_BEACON.name) ? getRelationsForEvent(event.getId()!, RelationType.Reference, M_BEACON.name)
: undefined; : undefined;
relations?.getRelations()?.forEach((locationEvent) => { relations?.getRelations()?.forEach((locationEvent) => {
matrixClient.redactEvent( matrixClient.redactEvent(
locationEvent.getRoomId(), locationEvent.getRoomId()!,
locationEvent.getId(), locationEvent.getId()!,
undefined, undefined,
redactionEvent.getContent(), redactionEvent.getContent<IRedactOpts>(),
); );
}); });
}, },
@ -132,7 +133,7 @@ const useHandleBeaconRedaction = (
const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelationsForEvent }, ref) => { const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelationsForEvent }, ref) => {
const { beacon, isLive, latestLocationState, waitingToStart } = useBeaconState(mxEvent); const { beacon, isLive, latestLocationState, waitingToStart } = useBeaconState(mxEvent);
const mapId = useUniqueId(mxEvent.getId()); const mapId = useUniqueId(mxEvent.getId()!);
const matrixClient = useContext(MatrixClientContext); const matrixClient = useContext(MatrixClientContext);
const [error, setError] = useState<Error>(); const [error, setError] = useState<Error>();
@ -159,7 +160,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
Modal.createDialog( Modal.createDialog(
BeaconViewDialog, BeaconViewDialog,
{ {
roomId: mxEvent.getRoomId(), roomId: mxEvent.getRoomId()!,
matrixClient, matrixClient,
initialFocusedBeacon: beacon, initialFocusedBeacon: beacon,
}, },
@ -170,11 +171,11 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
}; };
let map: JSX.Element; let map: JSX.Element;
if (displayStatus === BeaconDisplayStatus.Active && !isMapDisplayError) { if (displayStatus === BeaconDisplayStatus.Active && !isMapDisplayError && latestLocationState?.uri) {
map = ( map = (
<Map <Map
id={mapId} id={mapId}
centerGeoUri={latestLocationState?.uri} centerGeoUri={latestLocationState.uri}
onError={setError} onError={setError}
onClick={onClick} onClick={onClick}
className="mx_MBeaconBody_map" className="mx_MBeaconBody_map"
@ -183,7 +184,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
<SmartMarker <SmartMarker
map={map} map={map}
id={`${mapId}-marker`} id={`${mapId}-marker`}
geoUri={latestLocationState?.uri} geoUri={latestLocationState.uri!}
roomMember={markerRoomMember ?? undefined} roomMember={markerRoomMember ?? undefined}
useMemberColor useMemberColor
/> />

View file

@ -78,7 +78,7 @@ cacheDownloadIcon();
* @param {HTMLElement} element The element to get the current style of. * @param {HTMLElement} element The element to get the current style of.
* @return {string} The CSS style encoded as a string. * @return {string} The CSS style encoded as a string.
*/ */
export function computedStyle(element: HTMLElement): string { export function computedStyle(element: HTMLElement | null): string {
if (!element) { if (!element) {
return ""; return "";
} }
@ -142,6 +142,7 @@ export default class MFileBody extends React.Component<IProps, IState> {
} }
private downloadFile(fileName: string, text: string): void { private downloadFile(fileName: string, text: string): void {
if (!this.state.decryptedBlob) return;
this.fileDownloader.download({ this.fileDownloader.download({
blob: this.state.decryptedBlob, blob: this.state.decryptedBlob,
name: fileName, name: fileName,

View file

@ -104,9 +104,10 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
const content = this.props.mxEvent.getContent<IMediaEventContent>(); const content = this.props.mxEvent.getContent<IMediaEventContent>();
const httpUrl = this.state.contentUrl; const httpUrl = this.state.contentUrl;
if (!httpUrl) return;
const params: Omit<ComponentProps<typeof ImageView>, "onFinished"> = { const params: Omit<ComponentProps<typeof ImageView>, "onFinished"> = {
src: httpUrl, src: httpUrl,
name: content.body?.length > 0 ? content.body : _t("Attachment"), name: content.body && content.body.length > 0 ? content.body : _t("Attachment"),
mxEvent: this.props.mxEvent, mxEvent: this.props.mxEvent,
permalinkCreator: this.props.permalinkCreator, permalinkCreator: this.props.permalinkCreator,
}; };
@ -135,7 +136,12 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
protected onImageEnter = (e: React.MouseEvent<HTMLImageElement>): void => { protected onImageEnter = (e: React.MouseEvent<HTMLImageElement>): void => {
this.setState({ hover: true }); this.setState({ hover: true });
if (!this.state.showImage || !this.state.isAnimated || SettingsStore.getValue("autoplayGifs")) { if (
!this.state.contentUrl ||
!this.state.showImage ||
!this.state.isAnimated ||
SettingsStore.getValue("autoplayGifs")
) {
return; return;
} }
const imgElement = e.currentTarget; const imgElement = e.currentTarget;
@ -145,11 +151,12 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
protected onImageLeave = (e: React.MouseEvent<HTMLImageElement>): void => { protected onImageLeave = (e: React.MouseEvent<HTMLImageElement>): void => {
this.setState({ hover: false }); this.setState({ hover: false });
if (!this.state.showImage || !this.state.isAnimated || SettingsStore.getValue("autoplayGifs")) { const url = this.state.thumbUrl ?? this.state.contentUrl;
if (!url || !this.state.showImage || !this.state.isAnimated || SettingsStore.getValue("autoplayGifs")) {
return; return;
} }
const imgElement = e.currentTarget; const imgElement = e.currentTarget;
imgElement.src = this.state.thumbUrl ?? this.state.contentUrl; imgElement.src = url;
}; };
private clearError = (): void => { private clearError = (): void => {
@ -285,7 +292,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
img.onerror = reject; img.onerror = reject;
}); });
img.crossOrigin = "Anonymous"; // CORS allow canvas access img.crossOrigin = "Anonymous"; // CORS allow canvas access
img.src = contentUrl; img.src = contentUrl ?? "";
try { try {
await loadPromise; await loadPromise;
@ -379,7 +386,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
protected messageContent( protected messageContent(
contentUrl: string, contentUrl: string,
thumbUrl: string, thumbUrl: string | null,
content: IMediaEventContent, content: IMediaEventContent,
forcedHeight?: number, forcedHeight?: number,
): JSX.Element { ): JSX.Element {
@ -579,7 +586,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
} }
let contentUrl = this.state.contentUrl; let contentUrl = this.state.contentUrl;
let thumbUrl: string | undefined; let thumbUrl: string | null;
if (this.props.forExport) { if (this.props.forExport) {
contentUrl = this.props.mxEvent.getContent().url ?? this.props.mxEvent.getContent().file?.url; contentUrl = this.props.mxEvent.getContent().url ?? this.props.mxEvent.getContent().file?.url;
thumbUrl = contentUrl; thumbUrl = contentUrl;
@ -589,7 +596,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
thumbUrl = this.state.thumbUrl ?? this.state.contentUrl; thumbUrl = this.state.thumbUrl ?? this.state.contentUrl;
} }
const thumbnail = this.messageContent(contentUrl, thumbUrl, content); const thumbnail = contentUrl ? this.messageContent(contentUrl, thumbUrl, content) : undefined;
const fileBody = this.getFileBody(); const fileBody = this.getFileBody();
return ( return (

View file

@ -36,7 +36,9 @@ export default class MImageReplyBody extends MImageBody {
} }
const content = this.props.mxEvent.getContent<IMediaEventContent>(); const content = this.props.mxEvent.getContent<IMediaEventContent>();
const thumbnail = this.messageContent(this.state.contentUrl, this.state.thumbUrl, content, FORCED_IMAGE_HEIGHT); const thumbnail = this.state.contentUrl
? this.messageContent(this.state.contentUrl, this.state.thumbUrl, content, FORCED_IMAGE_HEIGHT)
: undefined;
return <div className="mx_MImageReplyBody">{thumbnail}</div>; return <div className="mx_MImageReplyBody">{thumbnail}</div>;
} }

View file

@ -38,6 +38,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
const prevUrl = this.props.mxEvent.getPrevContent()["url"]; const prevUrl = this.props.mxEvent.getPrevContent()["url"];
const senderName = this.props.mxEvent.sender?.name || this.props.mxEvent.getSender(); const senderName = this.props.mxEvent.sender?.name || this.props.mxEvent.getSender();
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
if (!room) return null;
const widgetId = this.props.mxEvent.getStateKey(); const widgetId = this.props.mxEvent.getStateKey();
const widget = WidgetStore.instance.getRoom(room.roomId, true).widgets.find((w) => w.id === widgetId); const widget = WidgetStore.instance.getRoom(room.roomId, true).widgets.find((w) => w.id === widgetId);

View file

@ -114,16 +114,16 @@ export default class MKeyVerificationConclusion extends React.Component<IProps>
if (request.done) { if (request.done) {
title = _t("You verified %(name)s", { title = _t("You verified %(name)s", {
name: getNameForEventRoom(request.otherUserId, mxEvent.getRoomId()), name: getNameForEventRoom(request.otherUserId, mxEvent.getRoomId()!),
}); });
} else if (request.cancelled) { } else if (request.cancelled) {
const userId = request.cancellingUserId; const userId = request.cancellingUserId;
if (userId === myUserId) { if (userId === myUserId) {
title = _t("You cancelled verifying %(name)s", { title = _t("You cancelled verifying %(name)s", {
name: getNameForEventRoom(request.otherUserId, mxEvent.getRoomId()), name: getNameForEventRoom(request.otherUserId, mxEvent.getRoomId()!),
}); });
} else { } else if (userId) {
title = _t("%(name)s cancelled verifying", { name: getNameForEventRoom(userId, mxEvent.getRoomId()) }); title = _t("%(name)s cancelled verifying", { name: getNameForEventRoom(userId, mxEvent.getRoomId()!) });
} }
} }
@ -135,7 +135,7 @@ export default class MKeyVerificationConclusion extends React.Component<IProps>
<EventTileBubble <EventTileBubble
className={classes} className={classes}
title={title} title={title}
subtitle={userLabelForEventRoom(request.otherUserId, mxEvent.getRoomId())} subtitle={userLabelForEventRoom(request.otherUserId, mxEvent.getRoomId()!)}
timestamp={this.props.timestamp} timestamp={this.props.timestamp}
/> />
); );

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import { MatrixEvent, User } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { VerificationRequestEvent } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; import { VerificationRequestEvent } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
@ -48,8 +48,11 @@ export default class MKeyVerificationRequest extends React.Component<IProps> {
} }
private openRequest = (): void => { private openRequest = (): void => {
let member: User | undefined;
const { verificationRequest } = this.props.mxEvent; const { verificationRequest } = this.props.mxEvent;
const member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId); if (verificationRequest) {
member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId) ?? undefined;
}
RightPanelStore.instance.setCards([ RightPanelStore.instance.setCards([
{ phase: RightPanelPhases.RoomSummary }, { phase: RightPanelPhases.RoomSummary },
{ phase: RightPanelPhases.RoomMemberInfo, state: { member } }, { phase: RightPanelPhases.RoomMemberInfo, state: { member } },
@ -90,14 +93,14 @@ export default class MKeyVerificationRequest extends React.Component<IProps> {
if (userId === myUserId) { if (userId === myUserId) {
return _t("You accepted"); return _t("You accepted");
} else { } else {
return _t("%(name)s accepted", { name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId()) }); return _t("%(name)s accepted", { name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId()!) });
} }
} }
private cancelledLabel(userId: string): string { private cancelledLabel(userId: string): string {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const myUserId = client.getUserId(); const myUserId = client.getUserId();
const { cancellationCode } = this.props.mxEvent.verificationRequest; const cancellationCode = this.props.mxEvent.verificationRequest?.cancellationCode;
const declined = cancellationCode === "m.user"; const declined = cancellationCode === "m.user";
if (userId === myUserId) { if (userId === myUserId) {
if (declined) { if (declined) {
@ -107,9 +110,9 @@ export default class MKeyVerificationRequest extends React.Component<IProps> {
} }
} else { } else {
if (declined) { if (declined) {
return _t("%(name)s declined", { name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId()) }); return _t("%(name)s declined", { name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId()!) });
} else { } else {
return _t("%(name)s cancelled", { name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId()) }); return _t("%(name)s cancelled", { name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId()!) });
} }
} }
} }
@ -136,7 +139,7 @@ export default class MKeyVerificationRequest extends React.Component<IProps> {
</AccessibleButton> </AccessibleButton>
); );
} else if (request.cancelled) { } else if (request.cancelled) {
stateLabel = this.cancelledLabel(request.cancellingUserId); stateLabel = this.cancelledLabel(request.cancellingUserId!);
} else if (request.accepting) { } else if (request.accepting) {
stateLabel = _t("Accepting…"); stateLabel = _t("Accepting…");
} else if (request.declining) { } else if (request.declining) {
@ -146,9 +149,9 @@ export default class MKeyVerificationRequest extends React.Component<IProps> {
} }
if (!request.initiatedByMe) { if (!request.initiatedByMe) {
const name = getNameForEventRoom(request.requestingUserId, mxEvent.getRoomId()); const name = getNameForEventRoom(request.requestingUserId, mxEvent.getRoomId()!);
title = _t("%(name)s wants to verify", { name }); title = _t("%(name)s wants to verify", { name });
subtitle = userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId()); subtitle = userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId()!);
if (request.canAccept) { if (request.canAccept) {
stateNode = ( stateNode = (
<div className="mx_cryptoEvent_buttons"> <div className="mx_cryptoEvent_buttons">
@ -164,7 +167,7 @@ export default class MKeyVerificationRequest extends React.Component<IProps> {
} else { } else {
// request sent by us // request sent by us
title = _t("You sent a verification request"); title = _t("You sent a verification request");
subtitle = userLabelForEventRoom(request.receivingUserId, mxEvent.getRoomId()); subtitle = userLabelForEventRoom(request.receivingUserId, mxEvent.getRoomId()!);
} }
if (title) { if (title) {

View file

@ -118,16 +118,17 @@ export function pollAlreadyHasVotes(mxEvent: MatrixEvent, getRelationsForEvent?:
} }
export function launchPollEditor(mxEvent: MatrixEvent, getRelationsForEvent?: GetRelationsForEvent): void { export function launchPollEditor(mxEvent: MatrixEvent, getRelationsForEvent?: GetRelationsForEvent): void {
const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId());
if (pollAlreadyHasVotes(mxEvent, getRelationsForEvent)) { if (pollAlreadyHasVotes(mxEvent, getRelationsForEvent)) {
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t("Can't edit poll"), title: _t("Can't edit poll"),
description: _t("Sorry, you can't edit a poll after votes have been cast."), description: _t("Sorry, you can't edit a poll after votes have been cast."),
}); });
} else { } else if (room) {
Modal.createDialog( Modal.createDialog(
PollCreateDialog, PollCreateDialog,
{ {
room: MatrixClientPeg.get().getRoom(mxEvent.getRoomId()), room,
threadId: mxEvent.getThread()?.id, threadId: mxEvent.getThread()?.id,
editingMxEvent: mxEvent, editingMxEvent: mxEvent,
}, },

View file

@ -111,7 +111,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
canvas.height = height; canvas.height = height;
const pixels = decode(info[BLURHASH_FIELD], width, height); const pixels = decode(info[BLURHASH_FIELD], width, height);
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d")!;
const imgData = ctx.createImageData(width, height); const imgData = ctx.createImageData(width, height);
imgData.data.set(pixels); imgData.data.set(pixels);
ctx.putImageData(imgData, 0, 0); ctx.putImageData(imgData, 0, 0);
@ -128,7 +128,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
image.onload = () => { image.onload = () => {
this.setState({ posterLoading: false }); this.setState({ posterLoading: false });
}; };
image.src = media.thumbnailHttp; image.src = media.thumbnailHttp!;
} }
} }

View file

@ -185,9 +185,9 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
const allowRender = localStorage.getItem(key) === "true"; const allowRender = localStorage.getItem(key) === "true";
if (!allowRender) { if (!allowRender) {
const userDomain = this.props.mxEvent.getSender().split(":").slice(1).join(":"); const userDomain = this.props.mxEvent.getSender()?.split(":").slice(1).join(":");
const userBanned = Mjolnir.sharedInstance().isUserBanned(this.props.mxEvent.getSender()); const userBanned = Mjolnir.sharedInstance().isUserBanned(this.props.mxEvent.getSender()!);
const serverBanned = Mjolnir.sharedInstance().isServerBanned(userDomain); const serverBanned = userDomain && Mjolnir.sharedInstance().isServerBanned(userDomain);
if (userBanned || serverBanned) { if (userBanned || serverBanned) {
BodyType = MjolnirBody; BodyType = MjolnirBody;

View file

@ -34,8 +34,8 @@ const MAX_ITEMS_WHEN_LIMITED = 8;
const ReactButton: React.FC<IProps> = ({ mxEvent, reactions }) => { const ReactButton: React.FC<IProps> = ({ mxEvent, reactions }) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
let contextMenu; let contextMenu: JSX.Element | undefined;
if (menuDisplayed) { if (menuDisplayed && button.current) {
const buttonRect = button.current.getBoundingClientRect(); const buttonRect = button.current.getBoundingClientRect();
contextMenu = ( contextMenu = (
<ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false}> <ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false}>
@ -73,7 +73,7 @@ interface IProps {
} }
interface IState { interface IState {
myReactions: MatrixEvent[]; myReactions: MatrixEvent[] | null;
showAll: boolean; showAll: boolean;
} }
@ -147,8 +147,9 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
if (!reactions) { if (!reactions) {
return null; return null;
} }
const userId = this.context.room.client.getUserId(); const userId = this.context.room?.client.getUserId();
const myReactions = reactions.getAnnotationsBySender()[userId]; if (!userId) return null;
const myReactions = reactions.getAnnotationsBySender()?.[userId];
if (!myReactions) { if (!myReactions) {
return null; return null;
} }
@ -171,19 +172,17 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
let items = reactions let items = reactions
.getSortedAnnotationsByKey() .getSortedAnnotationsByKey()
.map(([content, events]) => { ?.map(([content, events]) => {
const count = events.size; const count = events.size;
if (!count) { if (!count) {
return null; return null;
} }
const myReactionEvent = const myReactionEvent = myReactions?.find((mxEvent) => {
myReactions && if (mxEvent.isRedacted()) {
myReactions.find((mxEvent) => { return false;
if (mxEvent.isRedacted()) { }
return false; return mxEvent.getRelation()?.key === content;
} });
return mxEvent.getRelation().key === content;
});
return ( return (
<ReactionsRowButton <ReactionsRowButton
key={content} key={content}
@ -201,7 +200,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
}) })
.filter((item) => !!item); .filter((item) => !!item);
if (!items.length) return null; if (!items?.length) return null;
// Show the first MAX_ITEMS if there are MAX_ITEMS + 1 or more items. // 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 // The "+ 1" ensure that the "show all" reveals something that takes up
@ -216,7 +215,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
); );
} }
let addReactionButton: JSX.Element; let addReactionButton: JSX.Element | undefined;
if (this.context.canReact) { if (this.context.canReact) {
addReactionButton = <ReactButton mxEvent={mxEvent} reactions={reactions} />; addReactionButton = <ReactButton mxEvent={mxEvent} reactions={reactions} />;
} }

View file

@ -24,7 +24,6 @@ import dis from "../../../dispatcher/dispatcher";
import ReactionsRowButtonTooltip from "./ReactionsRowButtonTooltip"; import ReactionsRowButtonTooltip from "./ReactionsRowButtonTooltip";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
interface IProps { interface IProps {
// The event we're displaying reactions for // The event we're displaying reactions for
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@ -57,9 +56,9 @@ export default class ReactionsRowButton extends React.PureComponent<IProps, ISta
public onClick = (): void => { public onClick = (): void => {
const { mxEvent, myReactionEvent, content } = this.props; const { mxEvent, myReactionEvent, content } = this.props;
if (myReactionEvent) { if (myReactionEvent) {
this.context.redactEvent(mxEvent.getRoomId(), myReactionEvent.getId()); this.context.redactEvent(mxEvent.getRoomId()!, myReactionEvent.getId());
} else { } else {
this.context.sendEvent(mxEvent.getRoomId(), "m.reaction", { this.context.sendEvent(mxEvent.getRoomId()!, "m.reaction", {
"m.relates_to": { "m.relates_to": {
rel_type: "m.annotation", rel_type: "m.annotation",
event_id: mxEvent.getId(), event_id: mxEvent.getId(),
@ -110,8 +109,8 @@ export default class ReactionsRowButton extends React.PureComponent<IProps, ISta
if (room) { if (room) {
const senders: string[] = []; const senders: string[] = [];
for (const reactionEvent of reactionEvents) { for (const reactionEvent of reactionEvents) {
const member = room.getMember(reactionEvent.getSender()); const member = room.getMember(reactionEvent.getSender()!);
senders.push(member?.name || reactionEvent.getSender()); senders.push(member?.name || reactionEvent.getSender()!);
} }
const reactors = formatCommaSeparatedList(senders, 6); const reactors = formatCommaSeparatedList(senders, 6);

View file

@ -22,7 +22,6 @@ import { _t } from "../../../languageHandler";
import { formatCommaSeparatedList } from "../../../utils/FormattingUtils"; import { formatCommaSeparatedList } from "../../../utils/FormattingUtils";
import Tooltip from "../elements/Tooltip"; import Tooltip from "../elements/Tooltip";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
interface IProps { interface IProps {
// The event we're displaying reactions for // The event we're displaying reactions for
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@ -45,8 +44,8 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent<IProp
if (room) { if (room) {
const senders: string[] = []; const senders: string[] = [];
for (const reactionEvent of reactionEvents) { for (const reactionEvent of reactionEvents) {
const member = room.getMember(reactionEvent.getSender()); const member = room.getMember(reactionEvent.getSender()!);
const name = member ? member.name : reactionEvent.getSender(); const name = member?.name ?? reactionEvent.getSender()!;
senders.push(name); senders.push(name);
} }
const shortName = unicodeToShortcode(content); const shortName = unicodeToShortcode(content);

View file

@ -23,7 +23,6 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { formatFullDate } from "../../../DateUtils"; import { formatFullDate } from "../../../DateUtils";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { IBodyProps } from "./IBodyProps"; import { IBodyProps } from "./IBodyProps";
interface IProps { interface IProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
} }
@ -40,8 +39,10 @@ const RedactedBody = React.forwardRef<any, IProps | IBodyProps>(({ mxEvent }, re
} }
const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps"); const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
const fullDate = formatFullDate(new Date(unsigned.redacted_because.origin_server_ts), showTwelveHour); const fullDate = unsigned.redacted_because
const titleText = _t("Message deleted on %(date)s", { date: fullDate }); ? formatFullDate(new Date(unsigned.redacted_because.origin_server_ts), showTwelveHour)
: undefined;
const titleText = fullDate ? _t("Message deleted on %(date)s", { date: fullDate }) : undefined;
return ( return (
<span className="mx_RedactedBody" ref={ref} title={titleText}> <span className="mx_RedactedBody" ref={ref} title={titleText}>

View file

@ -26,7 +26,6 @@ import AccessibleButton from "../elements/AccessibleButton";
import { mediaFromMxc } from "../../../customisations/Media"; import { mediaFromMxc } from "../../../customisations/Media";
import RoomAvatar from "../avatars/RoomAvatar"; import RoomAvatar from "../avatars/RoomAvatar";
import ImageView from "../elements/ImageView"; import ImageView from "../elements/ImageView";
interface IProps { interface IProps {
/* the MatrixEvent to show */ /* the MatrixEvent to show */
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@ -37,6 +36,7 @@ export default class RoomAvatarEvent extends React.Component<IProps> {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const ev = this.props.mxEvent; const ev = this.props.mxEvent;
const httpUrl = mediaFromMxc(ev.getContent().url).srcHttp; const httpUrl = mediaFromMxc(ev.getContent().url).srcHttp;
if (!httpUrl) return;
const room = cli.getRoom(this.props.mxEvent.getRoomId()); const room = cli.getRoom(this.props.mxEvent.getRoomId());
const text = _t("%(senderDisplayName)s changed the avatar for %(roomName)s", { const text = _t("%(senderDisplayName)s changed the avatar for %(roomName)s", {
@ -48,7 +48,7 @@ export default class RoomAvatarEvent extends React.Component<IProps> {
src: httpUrl, src: httpUrl,
name: text, name: text,
}; };
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true); Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", undefined, true);
}; };
public render(): React.ReactNode { public render(): React.ReactNode {

View file

@ -34,7 +34,6 @@ import { isPermalinkHost, tryTransformPermalinkToLocalHref } from "../../../util
import { copyPlaintext } from "../../../utils/strings"; import { copyPlaintext } from "../../../utils/strings";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import UIStore from "../../../stores/UIStore"; import UIStore from "../../../stores/UIStore";
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
import GenericTextContextMenu from "../context_menus/GenericTextContextMenu"; import GenericTextContextMenu from "../context_menus/GenericTextContextMenu";
import Spoiler from "../elements/Spoiler"; import Spoiler from "../elements/Spoiler";
@ -109,7 +108,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
for (let i = 0; i < pres.length; i++) { for (let i = 0; i < pres.length; i++) {
// If there already is a div wrapping the codeblock we want to skip this. // If there already is a div wrapping the codeblock we want to skip this.
// This happens after the codeblock was edited. // This happens after the codeblock was edited.
if (pres[i].parentElement.className == "mx_EventTile_pre_container") continue; if (pres[i].parentElement?.className == "mx_EventTile_pre_container") continue;
// Add code element if it's missing since we depend on it // Add code element if it's missing since we depend on it
if (pres[i].getElementsByTagName("code").length == 0) { if (pres[i].getElementsByTagName("code").length == 0) {
this.addCodeElement(pres[i]); this.addCodeElement(pres[i]);
@ -189,8 +188,8 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
if (expansionButtonExists.length > 0) button.className += "mx_EventTile_buttonBottom"; if (expansionButtonExists.length > 0) button.className += "mx_EventTile_buttonBottom";
button.onclick = async (): Promise<void> => { button.onclick = async (): Promise<void> => {
const copyCode = button.parentElement.getElementsByTagName("code")[0]; const copyCode = button.parentElement?.getElementsByTagName("code")[0];
const successful = await copyPlaintext(copyCode.textContent); const successful = copyCode?.textContent ? await copyPlaintext(copyCode.textContent) : false;
const buttonRect = button.getBoundingClientRect(); const buttonRect = button.getBoundingClientRect();
const { close } = ContextMenu.createMenu(GenericTextContextMenu, { const { close } = ContextMenu.createMenu(GenericTextContextMenu, {
@ -209,7 +208,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
div.className = "mx_EventTile_pre_container"; div.className = "mx_EventTile_pre_container";
// Insert containing div in place of <pre> block // Insert containing div in place of <pre> block
pre.parentNode.replaceChild(div, pre); pre.parentNode?.replaceChild(div, pre);
// Append <pre> block and copy button to container // Append <pre> block and copy button to container
div.appendChild(pre); div.appendChild(pre);
@ -238,7 +237,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
} }
private highlightCode(code: HTMLElement): void { private highlightCode(code: HTMLElement): void {
if (code.textContent.length > MAX_HIGHLIGHT_LENGTH) { if (code.textContent && code.textContent.length > MAX_HIGHLIGHT_LENGTH) {
console.log( console.log(
"Code block is bigger than highlight limit (" + "Code block is bigger than highlight limit (" +
code.textContent.length + code.textContent.length +
@ -265,7 +264,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
// We don't use highlightElement here because we can't force language detection // We don't use highlightElement here because we can't force language detection
// off. It should use the one we've found in the CSS class but we'd rather pass // off. It should use the one we've found in the CSS class but we'd rather pass
// it in explicitly to make sure. // it in explicitly to make sure.
code.innerHTML = highlight.highlight(code.textContent, { language: advertisedLang }).value; code.innerHTML = highlight.highlight(code.textContent ?? "", { language: advertisedLang }).value;
} else if ( } else if (
SettingsStore.getValue("enableSyntaxHighlightLanguageDetection") && SettingsStore.getValue("enableSyntaxHighlightLanguageDetection") &&
code.parentElement instanceof HTMLPreElement code.parentElement instanceof HTMLPreElement
@ -277,7 +276,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
// work on the DOM with highlightElement because that also adds CSS // work on the DOM with highlightElement because that also adds CSS
// classes to the pre/code element that we don't want (the CSS // classes to the pre/code element that we don't want (the CSS
// conflicts with our own). // conflicts with our own).
code.innerHTML = highlight.highlightAuto(code.textContent).value; code.innerHTML = highlight.highlightAuto(code.textContent ?? "").value;
} }
} }
@ -317,7 +316,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
private calculateUrlPreview(): void { private calculateUrlPreview(): void {
//console.info("calculateUrlPreview: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview); //console.info("calculateUrlPreview: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
if (this.props.showUrlPreview) { if (this.props.showUrlPreview && this.contentRef.current) {
// pass only the first child which is the event tile otherwise this recurses on edited events // pass only the first child which is the event tile otherwise this recurses on edited events
let links = this.findLinks([this.contentRef.current]); let links = this.findLinks([this.contentRef.current]);
if (links.length) { if (links.length) {
@ -347,7 +346,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
const spoiler = <Spoiler reason={reason} contentHtml={node.outerHTML} />; const spoiler = <Spoiler reason={reason} contentHtml={node.outerHTML} />;
ReactDOM.render(spoiler, spoilerContainer); ReactDOM.render(spoiler, spoilerContainer);
node.parentNode.replaceChild(spoilerContainer, node); node.parentNode?.replaceChild(spoilerContainer, node);
node = spoilerContainer; node = spoilerContainer;
} }
@ -395,12 +394,12 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
} }
const url = node.getAttribute("href"); const url = node.getAttribute("href");
const host = url.match(/^https?:\/\/(.*?)(\/|$)/)[1]; const host = url?.match(/^https?:\/\/(.*?)(\/|$)/)?.[1];
// never preview permalinks (if anything we should give a smart // never preview permalinks (if anything we should give a smart
// preview of the room/user they point to: nobody needs to be reminded // preview of the room/user they point to: nobody needs to be reminded
// what the matrix.to site looks like). // what the matrix.to site looks like).
if (isPermalinkHost(host)) return false; if (!host || isPermalinkHost(host)) return false;
if (node.textContent?.toLowerCase().trim().startsWith(host.toLowerCase())) { if (node.textContent?.toLowerCase().trim().startsWith(host.toLowerCase())) {
// it's a "foo.pl" style link // it's a "foo.pl" style link
@ -422,7 +421,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
private onEmoteSenderClick = (): void => { private onEmoteSenderClick = (): void => {
const mxEvent = this.props.mxEvent; const mxEvent = this.props.mxEvent;
dis.dispatch<ComposerInsertPayload>({ dis.dispatch({
action: Action.ComposerInsert, action: Action.ComposerInsert,
userId: mxEvent.getSender(), userId: mxEvent.getSender(),
timelineRenderingType: this.context.timelineRenderingType, timelineRenderingType: this.context.timelineRenderingType,
@ -482,10 +481,10 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
// Go fetch a scalar token // Go fetch a scalar token
const integrationManager = managers.getPrimaryManager(); const integrationManager = managers.getPrimaryManager();
const scalarClient = integrationManager.getScalarClient(); const scalarClient = integrationManager?.getScalarClient();
scalarClient.connect().then(() => { scalarClient?.connect().then(() => {
const completeUrl = scalarClient.getStarterLink(starterLink); const completeUrl = scalarClient.getStarterLink(starterLink);
const integrationsUrl = integrationManager.uiUrl; const integrationsUrl = integrationManager!.uiUrl;
Modal.createDialog(QuestionDialog, { Modal.createDialog(QuestionDialog, {
title: _t("Add an Integration"), title: _t("Add an Integration"),
description: ( description: (
@ -508,7 +507,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
const left = (window.screen.width - width) / 2; const left = (window.screen.width - width) / 2;
const top = (window.screen.height - height) / 2; const top = (window.screen.height - height) / 2;
const features = `height=${height}, width=${width}, top=${top}, left=${left},`; const features = `height=${height}, width=${width}, top=${top}, left=${left},`;
const wnd = window.open(completeUrl, "_blank", features); const wnd = window.open(completeUrl, "_blank", features)!;
wnd.opener = null; wnd.opener = null;
}, },
}); });

View file

@ -35,7 +35,7 @@ export enum ImageSize {
* @param {number} maxHeight Overrides the default height limit * @param {number} maxHeight Overrides the default height limit
* @returns {Dimensions} The suggested maximum dimensions for the image * @returns {Dimensions} The suggested maximum dimensions for the image
*/ */
export function suggestedSize(size: ImageSize, contentSize: Dimensions, maxHeight?: number): Dimensions { export function suggestedSize(size: ImageSize, contentSize: Dimensions, maxHeight?: number): Required<Dimensions> {
const aspectRatio = contentSize.w! / contentSize.h!; const aspectRatio = contentSize.w! / contentSize.h!;
const portrait = aspectRatio < 1; const portrait = aspectRatio < 1;

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
export type getIframeFn = () => HTMLIFrameElement; // eslint-disable-line @typescript-eslint/naming-convention export type GetIframeFn = () => HTMLIFrameElement | null;
export const DEFAULT_STYLES = { export const DEFAULT_STYLES = {
imgSrc: "", imgSrc: "",
@ -75,7 +75,7 @@ export class FileDownloader {
* @param iframeFn Function to get a pre-configured iframe. Set to null to have the downloader * @param iframeFn Function to get a pre-configured iframe. Set to null to have the downloader
* use a generic, hidden, iframe. * use a generic, hidden, iframe.
*/ */
public constructor(private iframeFn?: getIframeFn) {} public constructor(private iframeFn?: GetIframeFn) {}
private get iframe(): HTMLIFrameElement { private get iframe(): HTMLIFrameElement {
const iframe = this.iframeFn?.(); const iframe = this.iframeFn?.();