Conform more of the codebase to strictNullChecks (#10666)

* Conform more of the codebase to `strictNullChecks`

* Iterate

* Iterate

* Iterate

* Iterate
This commit is contained in:
Michael Telatynski 2023-04-20 09:49:10 +01:00 committed by GitHub
parent 8867f1801e
commit 93b4ee654b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 126 additions and 114 deletions

View file

@ -293,7 +293,7 @@ export default class AddThreepid {
const authClient = new IdentityAuthClient();
const supportsSeparateAddAndBind = await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind();
let result;
let result: { success: boolean } | MatrixError;
if (this.submitUrl) {
result = await MatrixClientPeg.get().submitMsisdnTokenOtherUrl(
this.submitUrl,
@ -311,7 +311,7 @@ export default class AddThreepid {
} else {
throw new UserFriendlyError("The add / bind with MSISDN flow is misconfigured");
}
if (result.errcode) {
if (result instanceof Error) {
throw result;
}

View file

@ -16,6 +16,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { Optional } from "matrix-events-sdk";
import { _t } from "./languageHandler";
function getDaysArray(): string[] {
@ -194,10 +196,7 @@ function withinCurrentYear(prevDate: Date, nextDate: Date): boolean {
return prevDate.getFullYear() === nextDate.getFullYear();
}
export function wantsDateSeparator(
prevEventDate: Date | null | undefined,
nextEventDate: Date | null | undefined,
): boolean {
export function wantsDateSeparator(prevEventDate: Optional<Date>, nextEventDate: Optional<Date>): boolean {
if (!nextEventDate || !prevEventDate) {
return false;
}

View file

@ -288,8 +288,8 @@ export default class LegacyCallHandler extends EventEmitter {
this.play(AudioID.Ring);
}
public isCallSilenced(callId: string): boolean {
return this.isForcedSilent() || this.silencedCalls.has(callId);
public isCallSilenced(callId?: string): boolean {
return this.isForcedSilent() || (!!callId && this.silencedCalls.has(callId));
}
/**
@ -395,6 +395,7 @@ export default class LegacyCallHandler extends EventEmitter {
}
const mappedRoomId = LegacyCallHandler.instance.roomIdForCall(call);
if (!mappedRoomId) return;
if (this.getCallForRoom(mappedRoomId)) {
logger.log(
"Got incoming call for room " + mappedRoomId + " but there's already a call for this room: ignoring",
@ -411,7 +412,8 @@ export default class LegacyCallHandler extends EventEmitter {
// the call, we'll be ready to send. NB. This is the protocol-level room ID not
// the mapped one: that's where we'll send the events.
const cli = MatrixClientPeg.get();
cli.prepareToEncrypt(cli.getRoom(call.roomId));
const room = cli.getRoom(call.roomId);
if (room) cli.prepareToEncrypt(room);
};
public getCallById(callId: string): MatrixCall | null {
@ -505,7 +507,7 @@ export default class LegacyCallHandler extends EventEmitter {
if (this.audioPromises.has(audioId)) {
this.audioPromises.set(
audioId,
this.audioPromises.get(audioId).then(() => {
this.audioPromises.get(audioId)!.then(() => {
audio.load();
return playAudio();
}),
@ -531,7 +533,7 @@ export default class LegacyCallHandler extends EventEmitter {
};
if (audio) {
if (this.audioPromises.has(audioId)) {
this.audioPromises.set(audioId, this.audioPromises.get(audioId).then(pauseAudio));
this.audioPromises.set(audioId, this.audioPromises.get(audioId)!.then(pauseAudio));
} else {
pauseAudio();
}
@ -546,7 +548,7 @@ export default class LegacyCallHandler extends EventEmitter {
// is the call we consider 'the' call for its room.
const mappedRoomId = this.roomIdForCall(call);
const callForThisRoom = this.getCallForRoom(mappedRoomId);
const callForThisRoom = mappedRoomId ? this.getCallForRoom(mappedRoomId) : null;
return !!callForThisRoom && call.callId === callForThisRoom.callId;
}
@ -840,7 +842,7 @@ export default class LegacyCallHandler extends EventEmitter {
cancelButton: _t("OK"),
onFinished: (allow) => {
SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, allow);
cli.setFallbackICEServerAllowed(allow);
cli.setFallbackICEServerAllowed(!!allow);
},
},
undefined,
@ -898,7 +900,7 @@ export default class LegacyCallHandler extends EventEmitter {
// previous calls that are probably stale by now, so just cancel them.
if (mappedRoomId !== roomId) {
const mappedRoom = MatrixClientPeg.get().getRoom(mappedRoomId);
if (mappedRoom.getPendingEvents().length > 0) {
if (mappedRoom?.getPendingEvents().length) {
Resend.cancelUnsentEvents(mappedRoom);
}
}
@ -933,7 +935,7 @@ export default class LegacyCallHandler extends EventEmitter {
}
}
public async placeCall(roomId: string, type?: CallType, transferee?: MatrixCall): Promise<void> {
public async placeCall(roomId: string, type: CallType, transferee?: MatrixCall): Promise<void> {
// Pause current broadcast, if any
SdkContextClass.instance.voiceBroadcastPlaybacksStore.getCurrent()?.pause();

View file

@ -273,7 +273,9 @@ class FilePanel extends React.Component<IProps, IState> {
withoutScrollContainer
ref={this.card}
>
{this.card.current && (
<Measured sensor={this.card.current} onMeasurement={this.onMeasurement} />
)}
<SearchWarning isRoomEncrypted={isRoomEncrypted} kind={WarningKind.Files} />
<TimelinePanel
manageReadReceipts={false}

View file

@ -153,6 +153,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
}
private doStickyHeaders(list: HTMLDivElement): void {
if (!list.parentElement) return;
const topEdge = list.scrollTop;
const bottomEdge = list.offsetHeight + list.scrollTop;
const sublists = list.querySelectorAll<HTMLDivElement>(".mx_RoomSublist:not(.mx_RoomSublist_hidden)");

View file

@ -123,8 +123,8 @@ export default class LegacyCallEventGrouper extends EventEmitter {
}
public get duration(): number | null {
if (!this.hangup || !this.selectAnswer) return null;
return this.hangup.getDate().getTime() - this.selectAnswer.getDate().getTime();
if (!this.hangup?.getDate() || !this.selectAnswer?.getDate()) return null;
return this.hangup.getDate()!.getTime() - this.selectAnswer.getDate()!.getTime();
}
/**

View file

@ -169,7 +169,7 @@ interface IProps {
// the initial queryParams extracted from the hash-fragment of the URI
startingFragmentQueryParams?: QueryDict;
// called when we have completed a token login
onTokenLoginCompleted?: () => void;
onTokenLoginCompleted: () => void;
// Represents the screen to display as a result of parsing the initial window.location
initialScreenAfterLogin?: IScreen;
// displayname, if any, to set on the device when logging in/registering.
@ -1200,7 +1200,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// We have to manually update the room list because the forgotten room will not
// be notified to us, therefore the room list will have no other way of knowing
// the room is forgotten.
RoomListStore.instance.manualRoomUpdate(room, RoomUpdateCause.RoomRemoved);
if (room) RoomListStore.instance.manualRoomUpdate(room, RoomUpdateCause.RoomRemoved);
})
.catch((err) => {
const errCode = err.errcode || _td("unknown error code");
@ -2124,7 +2124,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
onForgotPasswordClick={showPasswordReset ? this.onForgotPasswordClick : undefined}
onServerConfigChange={this.onServerConfigChange}
fragmentAfterLogin={fragmentAfterLogin}
defaultUsername={this.props.startingFragmentQueryParams.defaultUsername as string}
defaultUsername={this.props.startingFragmentQueryParams?.defaultUsername as string | undefined}
{...this.getServerProperties()}
/>
);

View file

@ -24,6 +24,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
import { isSupportedReceiptType } from "matrix-js-sdk/src/utils";
import { Optional } from "matrix-events-sdk";
import shouldHideEvent from "../../shouldHideEvent";
import { wantsDateSeparator } from "../../DateUtils";
@ -436,10 +437,8 @@ export default class MessagePanel extends React.Component<IProps, IState> {
* node (specifically, the bottom of it) will be positioned. If omitted, it
* defaults to 0.
*/
public scrollToEvent(eventId: string, pixelOffset: number, offsetBase: number): void {
if (this.scrollPanel.current) {
this.scrollPanel.current.scrollToToken(eventId, pixelOffset, offsetBase);
}
public scrollToEvent(eventId: string, pixelOffset?: number, offsetBase?: number): void {
this.scrollPanel.current?.scrollToToken(eventId, pixelOffset, offsetBase);
}
public scrollToEventIfNeeded(eventId: string): void {
@ -590,16 +589,16 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return { nextEventAndShouldShow, nextTile };
}
private get pendingEditItem(): string | undefined {
private get pendingEditItem(): string | null {
if (!this.props.room) {
return undefined;
return null;
}
try {
return localStorage.getItem(editorRoomKey(this.props.room.roomId, this.context.timelineRenderingType));
} catch (err) {
logger.error(err);
return undefined;
return null;
}
}
@ -815,7 +814,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return ret;
}
public wantsDateSeparator(prevEvent: MatrixEvent | null, nextEventDate: Date): boolean {
public wantsDateSeparator(prevEvent: MatrixEvent | null, nextEventDate: Optional<Date>): boolean {
if (this.context.timelineRenderingType === TimelineRenderingType.ThreadsList) {
return false;
}
@ -1174,7 +1173,7 @@ class CreationGrouper extends BaseGrouper {
const ts = createEvent.event.getTs();
ret.push(
<li key={ts + "~"}>
<DateSeparator roomId={createEvent.event.getRoomId()} ts={ts} />
<DateSeparator roomId={createEvent.event.getRoomId()!} ts={ts} />
</li>,
);
}
@ -1326,7 +1325,7 @@ class MainGrouper extends BaseGrouper {
const ts = this.events[0].getTs();
ret.push(
<li key={ts + "~"}>
<DateSeparator roomId={this.events[0].getRoomId()} ts={ts} />
<DateSeparator roomId={this.events[0].getRoomId()!} ts={ts} />
</li>,
);
}

View file

@ -71,8 +71,8 @@ interface IState {
secondaryCall: MatrixCall;
// widget candidate to be displayed in the pip view.
persistentWidgetId: string;
persistentRoomId: string;
persistentWidgetId: string | null;
persistentRoomId: string | null;
showWidgetInPip: boolean;
}
@ -225,7 +225,7 @@ class PipContainerInner extends React.Component<IProps, IState> {
if (callRoomId ?? this.state.persistentRoomId) {
dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
room_id: callRoomId ?? this.state.persistentRoomId,
room_id: callRoomId ?? this.state.persistentRoomId ?? undefined,
metricsTrigger: "WebFloatingCallWindow",
});
}
@ -318,7 +318,7 @@ class PipContainerInner extends React.Component<IProps, IState> {
pipContent.push(({ onStartMoving }) => (
<WidgetPip
widgetId={this.state.persistentWidgetId}
room={MatrixClientPeg.get().getRoom(this.state.persistentRoomId)!}
room={MatrixClientPeg.get().getRoom(this.state.persistentRoomId ?? undefined)!}
viewingRoom={this.state.viewedRoomId === this.state.persistentRoomId}
onStartMoving={onStartMoving}
movePersistedElement={this.props.movePersistedElement}

View file

@ -218,6 +218,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
}
break;
case RightPanelPhases.Timeline:
if (this.props.room) {
card = (
<TimelineCard
classNames="mx_ThreadPanel mx_TimelineCard"
@ -229,12 +230,14 @@ export default class RightPanel extends React.Component<IProps, IState> {
e2eStatus={this.props.e2eStatus}
/>
);
}
break;
case RightPanelPhases.FilePanel:
card = <FilePanel roomId={roomId} resizeNotifier={this.props.resizeNotifier} onClose={this.onClose} />;
break;
case RightPanelPhases.ThreadView:
if (this.props.room) {
card = (
<ThreadView
room={this.props.room}
@ -248,6 +251,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
e2eStatus={this.props.e2eStatus}
/>
);
}
break;
case RightPanelPhases.ThreadPanel:
@ -262,6 +266,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
break;
case RightPanelPhases.RoomSummary:
if (this.props.room) {
card = (
<RoomSummaryCard
room={this.props.room}
@ -270,10 +275,13 @@ export default class RightPanel extends React.Component<IProps, IState> {
permalinkCreator={this.props.permalinkCreator!}
/>
);
}
break;
case RightPanelPhases.Widget:
if (this.props.room) {
card = <WidgetCard room={this.props.room} widgetId={cardState?.widgetId} onClose={this.onClose} />;
}
break;
}

View file

@ -654,7 +654,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// and the root event.
// The rest will be lost for now, until the aggregation API on the server
// becomes available to fetch a whole thread
if (!initialEvent && this.context.client) {
if (!initialEvent && this.context.client && roomId) {
initialEvent = (await fetchInitialEvent(this.context.client, roomId, initialEventId)) ?? undefined;
}
@ -741,7 +741,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// If an event ID wasn't specified, default to the one saved for this room
// in the scroll state store. Assume initialEventPixelOffset should be set.
if (!newState.initialEventId) {
if (!newState.initialEventId && newState.roomId) {
const roomScrollState = RoomScrollStateStore.getScrollState(newState.roomId);
if (roomScrollState) {
newState.initialEventId = roomScrollState.focussedEvent;
@ -770,7 +770,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// callback because this would prevent the setStates from being batched,
// ie. cause it to render RoomView twice rather than the once that is necessary.
if (initial) {
this.setupRoom(newState.room, newState.roomId, newState.joining, newState.shouldPeek);
this.setupRoom(newState.room, newState.roomId, !!newState.joining, !!newState.shouldPeek);
}
};
@ -794,13 +794,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.setState({ activeCall });
};
private getRoomId = (): string => {
private getRoomId = (): string | undefined => {
// According to `onRoomViewStoreUpdate`, `state.roomId` can be null
// if we have a room alias we haven't resolved yet. To work around this,
// first we'll try the room object if it's there, and then fallback to
// the bare room ID. (We may want to update `state.roomId` after
// resolving aliases, so we could always trust it.)
return this.state.room ? this.state.room.roomId : this.state.roomId;
return this.state.room?.roomId ?? this.state.roomId;
};
private getPermalinkCreatorForRoom(room: Room): RoomPermalinkCreator {
@ -1008,7 +1008,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
SettingsStore.unwatchSetting(watcher);
}
if (this.viewsLocalRoom) {
if (this.viewsLocalRoom && this.state.room) {
// clean up if this was a local room
this.context.client?.store.removeRoom(this.state.room.roomId);
}
@ -1188,16 +1188,16 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
};
private onLocalRoomEvent(roomId: string): void {
if (roomId !== this.state.room.roomId) return;
if (!this.context.client || !this.state.room || roomId !== this.state.room.roomId) return;
createRoomFromLocalRoom(this.context.client, this.state.room as LocalRoom);
}
private onRoomTimeline = (
ev: MatrixEvent,
room: Room | undefined,
toStartOfTimeline: boolean,
toStartOfTimeline: boolean | undefined,
removed: boolean,
data?: IRoomTimelineData,
data: IRoomTimelineData,
): void => {
if (this.unmounted) return;
@ -1249,6 +1249,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
};
private handleEffects = (ev: MatrixEvent): void => {
if (!this.state.room) return;
const notifState = this.context.roomNotificationStateStore.getRoomState(this.state.room);
if (!notifState.isUnread) return;
@ -1362,7 +1363,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private updatePreviewUrlVisibility({ roomId }: Room): void {
// URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit
const key = this.context.client.isRoomEncrypted(roomId) ? "urlPreviewsEnabled_e2ee" : "urlPreviewsEnabled";
const key = this.context.client?.isRoomEncrypted(roomId) ? "urlPreviewsEnabled_e2ee" : "urlPreviewsEnabled";
this.setState({
showUrlPreview: SettingsStore.getValue(key, roomId),
});
@ -1661,7 +1662,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.setState({
rejecting: true,
});
this.context.client.leave(this.state.roomId).then(
this.context.client?.leave(this.state.roomId).then(
() => {
dis.dispatch({ action: Action.ViewHomePage });
this.setState({
@ -1691,13 +1692,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
});
try {
const myMember = this.state.room.getMember(this.context.client.getUserId());
const inviteEvent = myMember.events.member;
const ignoredUsers = this.context.client.getIgnoredUsers();
ignoredUsers.push(inviteEvent.getSender()); // de-duped internally in the js-sdk
await this.context.client.setIgnoredUsers(ignoredUsers);
const myMember = this.state.room!.getMember(this.context.client!.getSafeUserId());
const inviteEvent = myMember!.events.member;
const ignoredUsers = this.context.client!.getIgnoredUsers();
ignoredUsers.push(inviteEvent!.getSender()!); // de-duped internally in the js-sdk
await this.context.client!.setIgnoredUsers(ignoredUsers);
await this.context.client.leave(this.state.roomId);
await this.context.client!.leave(this.state.roomId!);
dis.dispatch({ action: Action.ViewHomePage });
this.setState({
rejecting: false,

View file

@ -382,13 +382,12 @@ export default class ScrollPanel extends React.Component<IProps> {
}
const itemlist = this.itemlist.current;
const firstTile = itemlist && (itemlist.firstElementChild as HTMLElement);
const contentTop = firstTile && firstTile.offsetTop;
const firstTile = itemlist?.firstElementChild as HTMLElement | undefined;
const fillPromises: Promise<void>[] = [];
// if scrollTop gets to 1 screen from the top of the first tile,
// try backward filling
if (!firstTile || sn.scrollTop - contentTop < sn.clientHeight) {
if (!firstTile || sn.scrollTop - firstTile.offsetTop < sn.clientHeight) {
// need to back-fill
fillPromises.push(this.maybeFill(depth, true));
}
@ -424,7 +423,7 @@ export default class ScrollPanel extends React.Component<IProps> {
// check if unfilling is possible and send an unfill request if necessary
private checkUnfillState(backwards: boolean): void {
let excessHeight = this.getExcessHeight(backwards);
if (excessHeight <= 0) {
if (excessHeight <= 0 || !this.itemlist.current) {
return;
}
@ -617,10 +616,7 @@ export default class ScrollPanel extends React.Component<IProps> {
* node (specifically, the bottom of it) will be positioned. If omitted, it
* defaults to 0.
*/
public scrollToToken = (scrollToken: string, pixelOffset: number, offsetBase: number): void => {
pixelOffset = pixelOffset || 0;
offsetBase = offsetBase || 0;
public scrollToToken = (scrollToken: string, pixelOffset = 0, offsetBase = 0): void => {
// set the trackedScrollToken, so we can get the node through getTrackedNode
this.scrollState = {
stuckAtBottom: false,
@ -652,6 +648,7 @@ export default class ScrollPanel extends React.Component<IProps> {
const viewportBottom = scrollNode.scrollHeight - (scrollNode.scrollTop + scrollNode.clientHeight);
const itemlist = this.itemlist.current;
if (!itemlist) return;
const messages = itemlist.children;
let node: HTMLElement | null = null;
@ -705,7 +702,7 @@ export default class ScrollPanel extends React.Component<IProps> {
this.bottomGrowth += bottomDiff;
scrollState.bottomOffset = newBottomOffset;
const newHeight = `${this.getListHeight()}px`;
if (itemlist.style.height !== newHeight) {
if (itemlist && itemlist.style.height !== newHeight) {
itemlist.style.height = newHeight;
}
debuglog("balancing height because messages below viewport grew by", bottomDiff);
@ -755,7 +752,7 @@ export default class ScrollPanel extends React.Component<IProps> {
const scrollState = this.scrollState;
if (scrollState.stuckAtBottom) {
if (itemlist.style.height !== newHeight) {
if (itemlist && itemlist.style.height !== newHeight) {
itemlist.style.height = newHeight;
}
if (sn.scrollTop !== sn.scrollHeight) {
@ -770,7 +767,7 @@ export default class ScrollPanel extends React.Component<IProps> {
// the currently filled piece of the timeline
if (trackedNode) {
const oldTop = trackedNode.offsetTop;
if (itemlist.style.height !== newHeight) {
if (itemlist && itemlist.style.height !== newHeight) {
itemlist.style.height = newHeight;
}
const newTop = trackedNode.offsetTop;
@ -823,9 +820,9 @@ export default class ScrollPanel extends React.Component<IProps> {
private getMessagesHeight(): number {
const itemlist = this.itemlist.current;
const lastNode = itemlist.lastElementChild as HTMLElement;
const lastNode = itemlist?.lastElementChild as HTMLElement;
const lastNodeBottom = lastNode ? lastNode.offsetTop + lastNode.clientHeight : 0;
const firstNodeTop = itemlist.firstElementChild ? (itemlist.firstElementChild as HTMLElement).offsetTop : 0;
const firstNodeTop = (itemlist?.firstElementChild as HTMLElement)?.offsetTop ?? 0;
// 18 is itemlist padding
return lastNodeBottom - firstNodeTop + 18 * 2;
}
@ -865,8 +862,8 @@ export default class ScrollPanel extends React.Component<IProps> {
*/
public preventShrinking = (): void => {
const messageList = this.itemlist.current;
const tiles = messageList && messageList.children;
if (!messageList) {
const tiles = messageList?.children;
if (!tiles) {
return;
}
let lastTileNode;

View file

@ -777,7 +777,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
}
};
public canResetTimeline = (): boolean => this.messagePanel?.current?.isAtBottom();
public canResetTimeline = (): boolean => this.messagePanel?.current?.isAtBottom() === true;
private onRoomRedaction = (ev: MatrixEvent, room: Room): void => {
if (this.unmounted) return;
@ -1044,7 +1044,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
const sendRRs = SettingsStore.getValue("sendReadReceipts", roomId);
debuglog(
`Sending Read Markers for ${this.props.timelineSet.room.roomId}: `,
`Sending Read Markers for ${roomId}: `,
`rm=${this.state.readMarkerEventId} `,
`rr=${sendRRs ? lastReadEvent?.getId() : null} `,
`prr=${lastReadEvent?.getId()}`,
@ -1092,7 +1092,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
// we only do this if we're right at the end, because we're just assuming
// that sending an RR for the latest message will set our notif counter
// to zero: it may not do this if we send an RR for somewhere before the end.
if (this.isAtEndOfLiveTimeline()) {
if (this.isAtEndOfLiveTimeline() && this.props.timelineSet.room) {
this.props.timelineSet.room.setUnreadNotificationCount(NotificationCountType.Total, 0);
this.props.timelineSet.room.setUnreadNotificationCount(NotificationCountType.Highlight, 0);
dis.dispatch({

View file

@ -99,7 +99,7 @@ export default class MessageComposerFormatBar extends React.PureComponent<IProps
}
public showAt(selectionRect: DOMRect): void {
if (!this.formatBarRef.current) return;
if (!this.formatBarRef.current?.parentElement) return;
this.setState({ visible: true });
const parentRect = this.formatBarRef.current.parentElement.getBoundingClientRect();

View file

@ -216,7 +216,7 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
return {};
}
const kickerMember = this.props.room?.currentState.getMember(myMember.events.member.getSender());
const memberName = kickerMember ? kickerMember.name : myMember.events.member.getSender();
const memberName = kickerMember?.name ?? myMember.events.member?.getSender();
const reason = myMember.events.member?.getContent().reason;
return { memberName, reason };
}

View file

@ -433,9 +433,11 @@ export default class RoomSublist extends React.Component<IProps, IState> {
};
private onHeaderClick = (): void => {
const possibleSticky = this.headerButton.current.parentElement;
const sublist = possibleSticky.parentElement.parentElement;
const list = sublist.parentElement.parentElement;
const possibleSticky = this.headerButton.current?.parentElement;
const sublist = possibleSticky?.parentElement?.parentElement;
const list = sublist?.parentElement?.parentElement;
if (!possibleSticky || !list) return;
// the scrollTop is capped at the height of the header in LeftPanel, the top header is always sticky
const listScrollTop = Math.round(list.scrollTop);
const isAtTop = listScrollTop <= Math.round(HEADER_HEIGHT);

View file

@ -91,7 +91,7 @@ export default class WhoIsTypingTile extends React.Component<IProps, IState> {
private onRoomTimeline = (event: MatrixEvent, room?: Room): void => {
if (room?.roomId === this.props.room.roomId) {
const userId = event.getSender();
const userId = event.getSender()!;
// remove user from usersTyping
const usersTyping = this.state.usersTyping.filter((m) => m.userId !== userId);
if (usersTyping.length !== this.state.usersTyping.length) {
@ -200,14 +200,15 @@ export default class WhoIsTypingTile extends React.Component<IProps, IState> {
}
public render(): React.ReactNode {
let usersTyping = this.state.usersTyping;
const stoppedUsersOnTimer = Object.keys(this.state.delayedStopTypingTimers).map((userId) =>
this.props.room.getMember(userId),
);
const usersTyping = this.state.usersTyping;
// append the users that have been reported not typing anymore
// but have a timeout timer running so they can disappear
// when a message comes in
usersTyping = usersTyping.concat(stoppedUsersOnTimer);
for (const userId in this.state.delayedStopTypingTimers) {
const member = this.props.room.getMember(userId);
if (member) usersTyping.push(member);
}
// sort them so the typing members don't change order when
// moved to delayedStopTypingTimers
usersTyping.sort((a, b) => compare(a.name, b.name));