Conform more of the codebase to `strictNullChecks` (#10666)
* Conform more of the codebase to `strictNullChecks` * Iterate * Iterate * Iterate * Iteratepull/28217/head
parent
8867f1801e
commit
93b4ee654b
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -273,7 +273,9 @@ class FilePanel extends React.Component<IProps, IState> {
|
|||
withoutScrollContainer
|
||||
ref={this.card}
|
||||
>
|
||||
<Measured sensor={this.card.current} onMeasurement={this.onMeasurement} />
|
||||
{this.card.current && (
|
||||
<Measured sensor={this.card.current} onMeasurement={this.onMeasurement} />
|
||||
)}
|
||||
<SearchWarning isRoomEncrypted={isRoomEncrypted} kind={WarningKind.Files} />
|
||||
<TimelinePanel
|
||||
manageReadReceipts={false}
|
||||
|
|
|
@ -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)");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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()}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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>,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -218,36 +218,40 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
break;
|
||||
case RightPanelPhases.Timeline:
|
||||
card = (
|
||||
<TimelineCard
|
||||
classNames="mx_ThreadPanel mx_TimelineCard"
|
||||
room={this.props.room}
|
||||
timelineSet={this.props.room.getUnfilteredTimelineSet()}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
onClose={this.onClose}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
e2eStatus={this.props.e2eStatus}
|
||||
/>
|
||||
);
|
||||
if (this.props.room) {
|
||||
card = (
|
||||
<TimelineCard
|
||||
classNames="mx_ThreadPanel mx_TimelineCard"
|
||||
room={this.props.room}
|
||||
timelineSet={this.props.room.getUnfilteredTimelineSet()}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
onClose={this.onClose}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
e2eStatus={this.props.e2eStatus}
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case RightPanelPhases.FilePanel:
|
||||
card = <FilePanel roomId={roomId} resizeNotifier={this.props.resizeNotifier} onClose={this.onClose} />;
|
||||
break;
|
||||
|
||||
case RightPanelPhases.ThreadView:
|
||||
card = (
|
||||
<ThreadView
|
||||
room={this.props.room}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
onClose={this.onClose}
|
||||
mxEvent={cardState?.threadHeadEvent}
|
||||
initialEvent={cardState?.initialEvent}
|
||||
isInitialEventHighlighted={cardState?.isInitialEventHighlighted}
|
||||
initialEventScrollIntoView={cardState?.initialEventScrollIntoView}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
e2eStatus={this.props.e2eStatus}
|
||||
/>
|
||||
);
|
||||
if (this.props.room) {
|
||||
card = (
|
||||
<ThreadView
|
||||
room={this.props.room}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
onClose={this.onClose}
|
||||
mxEvent={cardState?.threadHeadEvent}
|
||||
initialEvent={cardState?.initialEvent}
|
||||
isInitialEventHighlighted={cardState?.isInitialEventHighlighted}
|
||||
initialEventScrollIntoView={cardState?.initialEventScrollIntoView}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
e2eStatus={this.props.e2eStatus}
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case RightPanelPhases.ThreadPanel:
|
||||
|
@ -262,18 +266,22 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
|||
break;
|
||||
|
||||
case RightPanelPhases.RoomSummary:
|
||||
card = (
|
||||
<RoomSummaryCard
|
||||
room={this.props.room}
|
||||
onClose={this.onClose}
|
||||
// whenever RightPanel is passed a room it is passed a permalinkcreator
|
||||
permalinkCreator={this.props.permalinkCreator!}
|
||||
/>
|
||||
);
|
||||
if (this.props.room) {
|
||||
card = (
|
||||
<RoomSummaryCard
|
||||
room={this.props.room}
|
||||
onClose={this.onClose}
|
||||
// whenever RightPanel is passed a room it is passed a permalinkcreator
|
||||
permalinkCreator={this.props.permalinkCreator!}
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case RightPanelPhases.Widget:
|
||||
card = <WidgetCard room={this.props.room} widgetId={cardState?.widgetId} onClose={this.onClose} />;
|
||||
if (this.props.room) {
|
||||
card = <WidgetCard room={this.props.room} widgetId={cardState?.widgetId} onClose={this.onClose} />;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Reference in New Issue