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

View file

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

View file

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

View file

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

View file

@ -153,6 +153,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
} }
private doStickyHeaders(list: HTMLDivElement): void { private doStickyHeaders(list: HTMLDivElement): void {
if (!list.parentElement) return;
const topEdge = list.scrollTop; const topEdge = list.scrollTop;
const bottomEdge = list.offsetHeight + list.scrollTop; const bottomEdge = list.offsetHeight + list.scrollTop;
const sublists = list.querySelectorAll<HTMLDivElement>(".mx_RoomSublist:not(.mx_RoomSublist_hidden)"); 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 { public get duration(): number | null {
if (!this.hangup || !this.selectAnswer) return null; if (!this.hangup?.getDate() || !this.selectAnswer?.getDate()) return null;
return this.hangup.getDate().getTime() - this.selectAnswer.getDate().getTime(); 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 // the initial queryParams extracted from the hash-fragment of the URI
startingFragmentQueryParams?: QueryDict; startingFragmentQueryParams?: QueryDict;
// called when we have completed a token login // 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 // Represents the screen to display as a result of parsing the initial window.location
initialScreenAfterLogin?: IScreen; initialScreenAfterLogin?: IScreen;
// displayname, if any, to set on the device when logging in/registering. // 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 // 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 // be notified to us, therefore the room list will have no other way of knowing
// the room is forgotten. // the room is forgotten.
RoomListStore.instance.manualRoomUpdate(room, RoomUpdateCause.RoomRemoved); if (room) RoomListStore.instance.manualRoomUpdate(room, RoomUpdateCause.RoomRemoved);
}) })
.catch((err) => { .catch((err) => {
const errCode = err.errcode || _td("unknown error code"); 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} onForgotPasswordClick={showPasswordReset ? this.onForgotPasswordClick : undefined}
onServerConfigChange={this.onServerConfigChange} onServerConfigChange={this.onServerConfigChange}
fragmentAfterLogin={fragmentAfterLogin} fragmentAfterLogin={fragmentAfterLogin}
defaultUsername={this.props.startingFragmentQueryParams.defaultUsername as string} defaultUsername={this.props.startingFragmentQueryParams?.defaultUsername as string | undefined}
{...this.getServerProperties()} {...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 { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon"; import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
import { isSupportedReceiptType } from "matrix-js-sdk/src/utils"; import { isSupportedReceiptType } from "matrix-js-sdk/src/utils";
import { Optional } from "matrix-events-sdk";
import shouldHideEvent from "../../shouldHideEvent"; import shouldHideEvent from "../../shouldHideEvent";
import { wantsDateSeparator } from "../../DateUtils"; 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 * node (specifically, the bottom of it) will be positioned. If omitted, it
* defaults to 0. * defaults to 0.
*/ */
public scrollToEvent(eventId: string, pixelOffset: number, offsetBase: number): void { public scrollToEvent(eventId: string, pixelOffset?: number, offsetBase?: number): void {
if (this.scrollPanel.current) { this.scrollPanel.current?.scrollToToken(eventId, pixelOffset, offsetBase);
this.scrollPanel.current.scrollToToken(eventId, pixelOffset, offsetBase);
}
} }
public scrollToEventIfNeeded(eventId: string): void { public scrollToEventIfNeeded(eventId: string): void {
@ -590,16 +589,16 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return { nextEventAndShouldShow, nextTile }; return { nextEventAndShouldShow, nextTile };
} }
private get pendingEditItem(): string | undefined { private get pendingEditItem(): string | null {
if (!this.props.room) { if (!this.props.room) {
return undefined; return null;
} }
try { try {
return localStorage.getItem(editorRoomKey(this.props.room.roomId, this.context.timelineRenderingType)); return localStorage.getItem(editorRoomKey(this.props.room.roomId, this.context.timelineRenderingType));
} catch (err) { } catch (err) {
logger.error(err); logger.error(err);
return undefined; return null;
} }
} }
@ -815,7 +814,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return ret; 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) { if (this.context.timelineRenderingType === TimelineRenderingType.ThreadsList) {
return false; return false;
} }
@ -1174,7 +1173,7 @@ class CreationGrouper extends BaseGrouper {
const ts = createEvent.event.getTs(); const ts = createEvent.event.getTs();
ret.push( ret.push(
<li key={ts + "~"}> <li key={ts + "~"}>
<DateSeparator roomId={createEvent.event.getRoomId()} ts={ts} /> <DateSeparator roomId={createEvent.event.getRoomId()!} ts={ts} />
</li>, </li>,
); );
} }
@ -1326,7 +1325,7 @@ class MainGrouper extends BaseGrouper {
const ts = this.events[0].getTs(); const ts = this.events[0].getTs();
ret.push( ret.push(
<li key={ts + "~"}> <li key={ts + "~"}>
<DateSeparator roomId={this.events[0].getRoomId()} ts={ts} /> <DateSeparator roomId={this.events[0].getRoomId()!} ts={ts} />
</li>, </li>,
); );
} }

View file

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

View file

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

View file

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

View file

@ -382,13 +382,12 @@ export default class ScrollPanel extends React.Component<IProps> {
} }
const itemlist = this.itemlist.current; const itemlist = this.itemlist.current;
const firstTile = itemlist && (itemlist.firstElementChild as HTMLElement); const firstTile = itemlist?.firstElementChild as HTMLElement | undefined;
const contentTop = firstTile && firstTile.offsetTop;
const fillPromises: Promise<void>[] = []; const fillPromises: Promise<void>[] = [];
// if scrollTop gets to 1 screen from the top of the first tile, // if scrollTop gets to 1 screen from the top of the first tile,
// try backward filling // try backward filling
if (!firstTile || sn.scrollTop - contentTop < sn.clientHeight) { if (!firstTile || sn.scrollTop - firstTile.offsetTop < sn.clientHeight) {
// need to back-fill // need to back-fill
fillPromises.push(this.maybeFill(depth, true)); 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 // check if unfilling is possible and send an unfill request if necessary
private checkUnfillState(backwards: boolean): void { private checkUnfillState(backwards: boolean): void {
let excessHeight = this.getExcessHeight(backwards); let excessHeight = this.getExcessHeight(backwards);
if (excessHeight <= 0) { if (excessHeight <= 0 || !this.itemlist.current) {
return; 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 * node (specifically, the bottom of it) will be positioned. If omitted, it
* defaults to 0. * defaults to 0.
*/ */
public scrollToToken = (scrollToken: string, pixelOffset: number, offsetBase: number): void => { public scrollToToken = (scrollToken: string, pixelOffset = 0, offsetBase = 0): void => {
pixelOffset = pixelOffset || 0;
offsetBase = offsetBase || 0;
// set the trackedScrollToken, so we can get the node through getTrackedNode // set the trackedScrollToken, so we can get the node through getTrackedNode
this.scrollState = { this.scrollState = {
stuckAtBottom: false, stuckAtBottom: false,
@ -652,6 +648,7 @@ export default class ScrollPanel extends React.Component<IProps> {
const viewportBottom = scrollNode.scrollHeight - (scrollNode.scrollTop + scrollNode.clientHeight); const viewportBottom = scrollNode.scrollHeight - (scrollNode.scrollTop + scrollNode.clientHeight);
const itemlist = this.itemlist.current; const itemlist = this.itemlist.current;
if (!itemlist) return;
const messages = itemlist.children; const messages = itemlist.children;
let node: HTMLElement | null = null; let node: HTMLElement | null = null;
@ -705,7 +702,7 @@ export default class ScrollPanel extends React.Component<IProps> {
this.bottomGrowth += bottomDiff; this.bottomGrowth += bottomDiff;
scrollState.bottomOffset = newBottomOffset; scrollState.bottomOffset = newBottomOffset;
const newHeight = `${this.getListHeight()}px`; const newHeight = `${this.getListHeight()}px`;
if (itemlist.style.height !== newHeight) { if (itemlist && itemlist.style.height !== newHeight) {
itemlist.style.height = newHeight; itemlist.style.height = newHeight;
} }
debuglog("balancing height because messages below viewport grew by", bottomDiff); 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; const scrollState = this.scrollState;
if (scrollState.stuckAtBottom) { if (scrollState.stuckAtBottom) {
if (itemlist.style.height !== newHeight) { if (itemlist && itemlist.style.height !== newHeight) {
itemlist.style.height = newHeight; itemlist.style.height = newHeight;
} }
if (sn.scrollTop !== sn.scrollHeight) { if (sn.scrollTop !== sn.scrollHeight) {
@ -770,7 +767,7 @@ export default class ScrollPanel extends React.Component<IProps> {
// the currently filled piece of the timeline // the currently filled piece of the timeline
if (trackedNode) { if (trackedNode) {
const oldTop = trackedNode.offsetTop; const oldTop = trackedNode.offsetTop;
if (itemlist.style.height !== newHeight) { if (itemlist && itemlist.style.height !== newHeight) {
itemlist.style.height = newHeight; itemlist.style.height = newHeight;
} }
const newTop = trackedNode.offsetTop; const newTop = trackedNode.offsetTop;
@ -823,9 +820,9 @@ export default class ScrollPanel extends React.Component<IProps> {
private getMessagesHeight(): number { private getMessagesHeight(): number {
const itemlist = this.itemlist.current; 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 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 // 18 is itemlist padding
return lastNodeBottom - firstNodeTop + 18 * 2; return lastNodeBottom - firstNodeTop + 18 * 2;
} }
@ -865,8 +862,8 @@ export default class ScrollPanel extends React.Component<IProps> {
*/ */
public preventShrinking = (): void => { public preventShrinking = (): void => {
const messageList = this.itemlist.current; const messageList = this.itemlist.current;
const tiles = messageList && messageList.children; const tiles = messageList?.children;
if (!messageList) { if (!tiles) {
return; return;
} }
let lastTileNode; 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 => { private onRoomRedaction = (ev: MatrixEvent, room: Room): void => {
if (this.unmounted) return; if (this.unmounted) return;
@ -1044,7 +1044,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
const sendRRs = SettingsStore.getValue("sendReadReceipts", roomId); const sendRRs = SettingsStore.getValue("sendReadReceipts", roomId);
debuglog( debuglog(
`Sending Read Markers for ${this.props.timelineSet.room.roomId}: `, `Sending Read Markers for ${roomId}: `,
`rm=${this.state.readMarkerEventId} `, `rm=${this.state.readMarkerEventId} `,
`rr=${sendRRs ? lastReadEvent?.getId() : null} `, `rr=${sendRRs ? lastReadEvent?.getId() : null} `,
`prr=${lastReadEvent?.getId()}`, `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 // 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 // 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. // 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.Total, 0);
this.props.timelineSet.room.setUnreadNotificationCount(NotificationCountType.Highlight, 0); this.props.timelineSet.room.setUnreadNotificationCount(NotificationCountType.Highlight, 0);
dis.dispatch({ dis.dispatch({

View file

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

View file

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

View file

@ -433,9 +433,11 @@ export default class RoomSublist extends React.Component<IProps, IState> {
}; };
private onHeaderClick = (): void => { private onHeaderClick = (): void => {
const possibleSticky = this.headerButton.current.parentElement; const possibleSticky = this.headerButton.current?.parentElement;
const sublist = possibleSticky.parentElement.parentElement; const sublist = possibleSticky?.parentElement?.parentElement;
const list = sublist.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 // 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 listScrollTop = Math.round(list.scrollTop);
const isAtTop = listScrollTop <= Math.round(HEADER_HEIGHT); 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 => { private onRoomTimeline = (event: MatrixEvent, room?: Room): void => {
if (room?.roomId === this.props.room.roomId) { if (room?.roomId === this.props.room.roomId) {
const userId = event.getSender(); const userId = event.getSender()!;
// remove user from usersTyping // remove user from usersTyping
const usersTyping = this.state.usersTyping.filter((m) => m.userId !== userId); const usersTyping = this.state.usersTyping.filter((m) => m.userId !== userId);
if (usersTyping.length !== this.state.usersTyping.length) { if (usersTyping.length !== this.state.usersTyping.length) {
@ -200,14 +200,15 @@ export default class WhoIsTypingTile extends React.Component<IProps, IState> {
} }
public render(): React.ReactNode { public render(): React.ReactNode {
let usersTyping = this.state.usersTyping; const usersTyping = this.state.usersTyping;
const stoppedUsersOnTimer = Object.keys(this.state.delayedStopTypingTimers).map((userId) =>
this.props.room.getMember(userId),
);
// append the users that have been reported not typing anymore // append the users that have been reported not typing anymore
// but have a timeout timer running so they can disappear // but have a timeout timer running so they can disappear
// when a message comes in // 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 // sort them so the typing members don't change order when
// moved to delayedStopTypingTimers // moved to delayedStopTypingTimers
usersTyping.sort((a, b) => compare(a.name, b.name)); usersTyping.sort((a, b) => compare(a.name, b.name));