mirror of https://github.com/vector-im/riot-web
				
				
				
			Conform more of the codebase to `strictNullChecks` (#10666)
* Conform more of the codebase to `strictNullChecks` * Iterate * Iterate * Iterate * Iteratepull/28788/head^2
							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
	
	 Michael Telatynski
						Michael Telatynski