Improve typescript null checking in places (#10073 (#10073

* Improve typescript null checking in places

* Iterate

* Fix Timer.ts
This commit is contained in:
Michael Telatynski 2023-02-03 15:27:47 +00:00 committed by GitHub
parent 97506cbcdb
commit 9743852380
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 155 additions and 154 deletions

View file

@ -31,7 +31,7 @@ export function avatarUrlForMember(
height: number, height: number,
resizeMethod: ResizeMethod, resizeMethod: ResizeMethod,
): string { ): string {
let url: string; let url: string | null | undefined;
if (member?.getMxcAvatarUrl()) { if (member?.getMxcAvatarUrl()) {
url = mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod); url = mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod);
} }
@ -118,7 +118,7 @@ export function defaultAvatarUrlForString(s: string): string {
* @param {string} name * @param {string} name
* @return {string} the first letter * @return {string} the first letter
*/ */
export function getInitialLetter(name: string): string { export function getInitialLetter(name: string): string | undefined {
if (!name) { if (!name) {
// XXX: We should find out what causes the name to sometimes be falsy. // XXX: We should find out what causes the name to sometimes be falsy.
console.trace("`name` argument to `getInitialLetter` not supplied"); console.trace("`name` argument to `getInitialLetter` not supplied");
@ -146,7 +146,7 @@ export function avatarUrlForRoom(
if (!room) return null; // null-guard if (!room) return null; // null-guard
if (room.getMxcAvatarUrl()) { if (room.getMxcAvatarUrl()) {
return mediaFromMxc(room.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod); return mediaFromMxc(room.getMxcAvatarUrl() || undefined).getThumbnailOfSourceHttp(width, height, resizeMethod);
} }
// space rooms cannot be DMs so skip the rest // space rooms cannot be DMs so skip the rest

View file

@ -130,7 +130,7 @@ export default abstract class BasePlatform {
if (MatrixClientPeg.userRegisteredWithinLastHours(24)) return false; if (MatrixClientPeg.userRegisteredWithinLastHours(24)) return false;
try { try {
const [version, deferUntil] = JSON.parse(localStorage.getItem(UPDATE_DEFER_KEY)); const [version, deferUntil] = JSON.parse(localStorage.getItem(UPDATE_DEFER_KEY)!);
return newVersion !== version || Date.now() > deferUntil; return newVersion !== version || Date.now() > deferUntil;
} catch (e) { } catch (e) {
return true; return true;
@ -211,7 +211,7 @@ export default abstract class BasePlatform {
metricsTrigger: "Notification", metricsTrigger: "Notification",
}; };
if (ev.getThread()) { if (ev?.getThread()) {
payload.event_id = ev.getId(); payload.event_id = ev.getId();
} }
@ -255,7 +255,7 @@ export default abstract class BasePlatform {
return false; return false;
} }
public getSettingValue(settingName: string): Promise<any> { public async getSettingValue(settingName: string): Promise<any> {
return undefined; return undefined;
} }
@ -278,7 +278,7 @@ export default abstract class BasePlatform {
public setSpellCheckEnabled(enabled: boolean): void {} public setSpellCheckEnabled(enabled: boolean): void {}
public async getSpellCheckEnabled(): Promise<boolean> { public async getSpellCheckEnabled(): Promise<boolean> {
return null; return false;
} }
public setSpellCheckLanguages(preferredLangs: string[]): void {} public setSpellCheckLanguages(preferredLangs: string[]): void {}
@ -333,7 +333,7 @@ export default abstract class BasePlatform {
// persist hs url and is url for when the user is returned to the app with the login token // persist hs url and is url for when the user is returned to the app with the login token
localStorage.setItem(SSO_HOMESERVER_URL_KEY, mxClient.getHomeserverUrl()); localStorage.setItem(SSO_HOMESERVER_URL_KEY, mxClient.getHomeserverUrl());
if (mxClient.getIdentityServerUrl()) { if (mxClient.getIdentityServerUrl()) {
localStorage.setItem(SSO_ID_SERVER_URL_KEY, mxClient.getIdentityServerUrl()); localStorage.setItem(SSO_ID_SERVER_URL_KEY, mxClient.getIdentityServerUrl()!);
} }
if (idpId) { if (idpId) {
localStorage.setItem(SSO_IDP_ID_KEY, idpId); localStorage.setItem(SSO_IDP_ID_KEY, idpId);

View file

@ -372,7 +372,7 @@ export default class ContentMessages {
const replyToEvent = SdkContextClass.instance.roomViewStore.getQuotingEvent(); const replyToEvent = SdkContextClass.instance.roomViewStore.getQuotingEvent();
if (!this.mediaConfig) { if (!this.mediaConfig) {
// hot-path optimization to not flash a spinner if we don't need to // hot-path optimization to not flash a spinner if we don't need to
const modal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner"); const modal = Modal.createDialog(Spinner, undefined, "mx_Dialog_spinner");
await this.ensureMediaConfigFetched(matrixClient); await this.ensureMediaConfigFetched(matrixClient);
modal.close(); modal.close();
} }

View file

@ -83,8 +83,8 @@ export class DecryptionFailureTracker {
public trackedEvents: Set<string> = new Set(); public trackedEvents: Set<string> = new Set();
// Set to an interval ID when `start` is called // Set to an interval ID when `start` is called
public checkInterval: number = null; public checkInterval: number | null = null;
public trackInterval: number = null; public trackInterval: number | null = null;
// Spread the load on `Analytics` by tracking at a low frequency, `TRACK_INTERVAL_MS`. // Spread the load on `Analytics` by tracking at a low frequency, `TRACK_INTERVAL_MS`.
public static TRACK_INTERVAL_MS = 60000; public static TRACK_INTERVAL_MS = 60000;

View file

@ -58,12 +58,12 @@ export default class DeviceListener {
private dismissedThisDeviceToast = false; private dismissedThisDeviceToast = false;
// cache of the key backup info // cache of the key backup info
private keyBackupInfo: IKeyBackupInfo | null = null; private keyBackupInfo: IKeyBackupInfo | null = null;
private keyBackupFetchedAt: number = null; private keyBackupFetchedAt: number | null = null;
private keyBackupStatusChecked = false; private keyBackupStatusChecked = false;
// We keep a list of our own device IDs so we can batch ones that were already // We keep a list of our own device IDs so we can batch ones that were already
// there the last time the app launched into a single toast, but display new // there the last time the app launched into a single toast, but display new
// ones in their own toasts. // ones in their own toasts.
private ourDeviceIdsAtStart: Set<string> = null; private ourDeviceIdsAtStart: Set<string> | null = null;
// The set of device IDs we're currently displaying toasts for // The set of device IDs we're currently displaying toasts for
private displayingToastsForDeviceIds = new Set<string>(); private displayingToastsForDeviceIds = new Set<string>();
private running = false; private running = false;
@ -203,7 +203,7 @@ export default class DeviceListener {
} }
}; };
private onSync = (state: SyncState, prevState?: SyncState): void => { private onSync = (state: SyncState, prevState: SyncState | null): void => {
if (state === "PREPARED" && prevState === null) { if (state === "PREPARED" && prevState === null) {
this.recheck(); this.recheck();
} }

View file

@ -28,7 +28,12 @@ limitations under the License.
* consume in the timeline, when performing scroll offset calculations * consume in the timeline, when performing scroll offset calculations
* (e.g. scroll locking) * (e.g. scroll locking)
*/ */
export function thumbHeight(fullWidth: number, fullHeight: number, thumbWidth: number, thumbHeight: number): number { export function thumbHeight(
fullWidth: number,
fullHeight: number,
thumbWidth: number,
thumbHeight: number,
): number | null {
if (!fullWidth || !fullHeight) { if (!fullWidth || !fullHeight) {
// Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even // Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even
// log this because it's spammy // log this because it's spammy

View file

@ -72,7 +72,7 @@ const getUIOnlyShortcuts = (): IKeyboardShortcuts => {
}, },
}; };
if (PlatformPeg.get().overrideBrowserShortcuts()) { if (PlatformPeg.get()?.overrideBrowserShortcuts()) {
// XXX: This keyboard shortcut isn't manually added to // XXX: This keyboard shortcut isn't manually added to
// KeyBindingDefaults as it can't be easily handled by the // KeyBindingDefaults as it can't be easily handled by the
// KeyBindingManager // KeyBindingManager
@ -92,7 +92,7 @@ const getUIOnlyShortcuts = (): IKeyboardShortcuts => {
* This function gets keyboard shortcuts that can be consumed by the KeyBindingDefaults. * This function gets keyboard shortcuts that can be consumed by the KeyBindingDefaults.
*/ */
export const getKeyboardShortcuts = (): IKeyboardShortcuts => { export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
const overrideBrowserShortcuts = PlatformPeg.get().overrideBrowserShortcuts(); const overrideBrowserShortcuts = PlatformPeg.get()?.overrideBrowserShortcuts();
return Object.keys(KEYBOARD_SHORTCUTS) return Object.keys(KEYBOARD_SHORTCUTS)
.filter((k: KeyBindingAction) => { .filter((k: KeyBindingAction) => {
@ -120,11 +120,11 @@ export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => {
}, {} as IKeyboardShortcuts); }, {} as IKeyboardShortcuts);
}; };
export const getKeyboardShortcutValue = (name: string): KeyCombo => { export const getKeyboardShortcutValue = (name: string): KeyCombo | undefined => {
return getKeyboardShortcutsForUI()[name]?.default; return getKeyboardShortcutsForUI()[name]?.default;
}; };
export const getKeyboardShortcutDisplayName = (name: string): string | null => { export const getKeyboardShortcutDisplayName = (name: string): string | undefined => {
const keyboardShortcutDisplayName = getKeyboardShortcutsForUI()[name]?.displayName; const keyboardShortcutDisplayName = getKeyboardShortcutsForUI()[name]?.displayName;
return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName); return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName);
}; };

View file

@ -56,7 +56,7 @@ export function checkInputableElement(el: HTMLElement): boolean {
} }
export interface IState { export interface IState {
activeRef: Ref; activeRef?: Ref;
refs: Ref[]; refs: Ref[];
} }
@ -67,7 +67,6 @@ interface IContext {
export const RovingTabIndexContext = createContext<IContext>({ export const RovingTabIndexContext = createContext<IContext>({
state: { state: {
activeRef: null,
refs: [], // list of refs in DOM order refs: [], // list of refs in DOM order
}, },
dispatch: () => {}, dispatch: () => {},
@ -102,7 +101,7 @@ export const reducer: Reducer<IState, IAction> = (state: IState, action: IAction
return 0; return 0;
} }
const position = a.current.compareDocumentPosition(b.current); const position = a.current!.compareDocumentPosition(b.current!);
if (position & Node.DOCUMENT_POSITION_FOLLOWING || position & Node.DOCUMENT_POSITION_CONTAINED_BY) { if (position & Node.DOCUMENT_POSITION_FOLLOWING || position & Node.DOCUMENT_POSITION_CONTAINED_BY) {
return -1; return -1;
@ -167,7 +166,7 @@ export const findSiblingElement = (
refs: RefObject<HTMLElement>[], refs: RefObject<HTMLElement>[],
startIndex: number, startIndex: number,
backwards = false, backwards = false,
): RefObject<HTMLElement> => { ): RefObject<HTMLElement> | undefined => {
if (backwards) { if (backwards) {
for (let i = startIndex; i < refs.length && i >= 0; i--) { for (let i = startIndex; i < refs.length && i >= 0; i--) {
if (refs[i].current?.offsetParent !== null) { if (refs[i].current?.offsetParent !== null) {
@ -191,7 +190,6 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
onKeyDown, onKeyDown,
}) => { }) => {
const [state, dispatch] = useReducer<Reducer<IState, IAction>>(reducer, { const [state, dispatch] = useReducer<Reducer<IState, IAction>>(reducer, {
activeRef: null,
refs: [], refs: [],
}); });
@ -208,7 +206,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
let handled = false; let handled = false;
const action = getKeyBindingsManager().getAccessibilityAction(ev); const action = getKeyBindingsManager().getAccessibilityAction(ev);
let focusRef: RefObject<HTMLElement>; let focusRef: RefObject<HTMLElement> | undefined;
// Don't interfere with input default keydown behaviour // Don't interfere with input default keydown behaviour
// but allow people to move focus from it with Tab. // but allow people to move focus from it with Tab.
if (checkInputableElement(ev.target as HTMLElement)) { if (checkInputableElement(ev.target as HTMLElement)) {
@ -216,7 +214,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
case KeyBindingAction.Tab: case KeyBindingAction.Tab:
handled = true; handled = true;
if (context.state.refs.length > 0) { if (context.state.refs.length > 0) {
const idx = context.state.refs.indexOf(context.state.activeRef); const idx = context.state.refs.indexOf(context.state.activeRef!);
focusRef = findSiblingElement( focusRef = findSiblingElement(
context.state.refs, context.state.refs,
idx + (ev.shiftKey ? -1 : 1), idx + (ev.shiftKey ? -1 : 1),
@ -252,7 +250,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
) { ) {
handled = true; handled = true;
if (context.state.refs.length > 0) { if (context.state.refs.length > 0) {
const idx = context.state.refs.indexOf(context.state.activeRef); const idx = context.state.refs.indexOf(context.state.activeRef!);
focusRef = findSiblingElement(context.state.refs, idx + 1); focusRef = findSiblingElement(context.state.refs, idx + 1);
} }
} }
@ -266,7 +264,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
) { ) {
handled = true; handled = true;
if (context.state.refs.length > 0) { if (context.state.refs.length > 0) {
const idx = context.state.refs.indexOf(context.state.activeRef); const idx = context.state.refs.indexOf(context.state.activeRef!);
focusRef = findSiblingElement(context.state.refs, idx - 1, true); focusRef = findSiblingElement(context.state.refs, idx - 1, true);
} }
} }

View file

@ -221,7 +221,7 @@ function createRoomTimelineAction(
action: "MatrixActions.Room.timeline", action: "MatrixActions.Room.timeline",
event: timelineEvent, event: timelineEvent,
isLiveEvent: data.liveEvent, isLiveEvent: data.liveEvent,
isLiveUnfilteredRoomTimelineEvent: room && data.timeline.getTimelineSet() === room.getUnfilteredTimelineSet(), isLiveUnfilteredRoomTimelineEvent: data.timeline.getTimelineSet() === room?.getUnfilteredTimelineSet(),
room, room,
}; };
} }

View file

@ -45,7 +45,7 @@ export class PlaybackQueue {
private playbacks = new Map<string, Playback>(); // keyed by event ID private playbacks = new Map<string, Playback>(); // keyed by event ID
private clockStates = new Map<string, number>(); // keyed by event ID private clockStates = new Map<string, number>(); // keyed by event ID
private playbackIdOrder: string[] = []; // event IDs, last == current private playbackIdOrder: string[] = []; // event IDs, last == current
private currentPlaybackId: string; // event ID, broken out from above for ease of use private currentPlaybackId: string | null = null; // event ID, broken out from above for ease of use
private recentFullPlays = new Set<string>(); // event IDs private recentFullPlays = new Set<string>(); // event IDs
public constructor(private room: Room) { public constructor(private room: Room) {
@ -68,7 +68,7 @@ export class PlaybackQueue {
const room = cli.getRoom(roomId); const room = cli.getRoom(roomId);
if (!room) throw new Error("Unknown room"); if (!room) throw new Error("Unknown room");
if (PlaybackQueue.queues.has(room.roomId)) { if (PlaybackQueue.queues.has(room.roomId)) {
return PlaybackQueue.queues.get(room.roomId); return PlaybackQueue.queues.get(room.roomId)!;
} }
const queue = new PlaybackQueue(room); const queue = new PlaybackQueue(room);
PlaybackQueue.queues.set(room.roomId, queue); PlaybackQueue.queues.set(room.roomId, queue);
@ -101,7 +101,7 @@ export class PlaybackQueue {
const wasLastPlaying = this.currentPlaybackId === mxEvent.getId(); const wasLastPlaying = this.currentPlaybackId === mxEvent.getId();
if (newState === PlaybackState.Stopped && this.clockStates.has(mxEvent.getId()) && !wasLastPlaying) { if (newState === PlaybackState.Stopped && this.clockStates.has(mxEvent.getId()) && !wasLastPlaying) {
// noinspection JSIgnoredPromiseFromCall // noinspection JSIgnoredPromiseFromCall
playback.skipTo(this.clockStates.get(mxEvent.getId())); playback.skipTo(this.clockStates.get(mxEvent.getId())!);
} else if (newState === PlaybackState.Stopped) { } else if (newState === PlaybackState.Stopped) {
// Remove the now-useless clock for some space savings // Remove the now-useless clock for some space savings
this.clockStates.delete(mxEvent.getId()); this.clockStates.delete(mxEvent.getId());

View file

@ -70,7 +70,7 @@ export default abstract class AutocompleteProvider {
* @param {boolean} force True if the user is forcing completion * @param {boolean} force True if the user is forcing completion
* @return {object} { command, range } where both objects fields are null if no match * @return {object} { command, range } where both objects fields are null if no match
*/ */
public getCurrentCommand(query: string, selection: ISelectionRange, force = false): ICommand { public getCurrentCommand(query: string, selection: ISelectionRange, force = false): ICommand | null {
let commandRegex = this.commandRegex; let commandRegex = this.commandRegex;
if (force && this.shouldForceComplete()) { if (force && this.shouldForceComplete()) {
@ -83,7 +83,7 @@ export default abstract class AutocompleteProvider {
commandRegex.lastIndex = 0; commandRegex.lastIndex = 0;
let match: RegExpExecArray; let match: RegExpExecArray | null;
while ((match = commandRegex.exec(query)) !== null) { while ((match = commandRegex.exec(query)) !== null) {
const start = match.index; const start = match.index;
const end = start + match[0].length; const end = start + match[0].length;

View file

@ -87,7 +87,7 @@ export default class Autocompleter {
to predict whether an action will actually do what is intended to predict whether an action will actually do what is intended
*/ */
// list of results from each provider, each being a list of completions or null if it times out // list of results from each provider, each being a list of completions or null if it times out
const completionsList: ICompletion[][] = await Promise.all( const completionsList: Array<ICompletion[] | null> = await Promise.all(
this.providers.map(async (provider): Promise<ICompletion[] | null> => { this.providers.map(async (provider): Promise<ICompletion[] | null> => {
return timeout( return timeout(
provider.getCompletions(query, selection, force, limit), provider.getCompletions(query, selection, force, limit),
@ -113,6 +113,6 @@ export default class Autocompleter {
command: this.providers[i].getCurrentCommand(query, selection, force), command: this.providers[i].getCurrentCommand(query, selection, force),
}; };
}) })
.filter(Boolean); .filter(Boolean) as IProviderCompletions[];
} }
} }

View file

@ -56,10 +56,10 @@ export default class CommandProvider extends AutocompleteProvider {
if (command[0] !== command[1]) { if (command[0] !== command[1]) {
// The input looks like a command with arguments, perform exact match // The input looks like a command with arguments, perform exact match
const name = command[1].slice(1); // strip leading `/` const name = command[1].slice(1); // strip leading `/`
if (CommandMap.has(name) && CommandMap.get(name).isEnabled()) { if (CommandMap.has(name) && CommandMap.get(name)!.isEnabled()) {
// some commands, namely `me` don't suit having the usage shown whilst typing their arguments // some commands, namely `me` don't suit having the usage shown whilst typing their arguments
if (CommandMap.get(name).hideCompletionAfterSpace) return []; if (CommandMap.get(name)!.hideCompletionAfterSpace) return [];
matches = [CommandMap.get(name)]; matches = [CommandMap.get(name)!];
} }
} else { } else {
if (query === "/") { if (query === "/") {

View file

@ -39,12 +39,12 @@ function canonicalScore(displayedAlias: string, room: Room): number {
function matcherObject( function matcherObject(
room: Room, room: Room,
displayedAlias: string, displayedAlias: string | null,
matchName = "", matchName = "",
): { ): {
room: Room; room: Room;
matchName: string; matchName: string;
displayedAlias: string; displayedAlias: string | null;
} { } {
return { return {
room, room,
@ -58,7 +58,7 @@ export default class RoomProvider extends AutocompleteProvider {
public constructor(room: Room, renderingType?: TimelineRenderingType) { public constructor(room: Room, renderingType?: TimelineRenderingType) {
super({ commandRegex: ROOM_REGEX, renderingType }); super({ commandRegex: ROOM_REGEX, renderingType });
this.matcher = new QueryMatcher([], { this.matcher = new QueryMatcher<ReturnType<typeof matcherObject>>([], {
keys: ["displayedAlias", "matchName"], keys: ["displayedAlias", "matchName"],
}); });
} }
@ -79,7 +79,7 @@ export default class RoomProvider extends AutocompleteProvider {
const { command, range } = this.getCurrentCommand(query, selection, force); const { command, range } = this.getCurrentCommand(query, selection, force);
if (command) { if (command) {
// the only reason we need to do this is because Fuse only matches on properties // the only reason we need to do this is because Fuse only matches on properties
let matcherObjects = this.getRooms().reduce((aliases, room) => { let matcherObjects = this.getRooms().reduce<ReturnType<typeof matcherObject>[]>((aliases, room) => {
if (room.getCanonicalAlias()) { if (room.getCanonicalAlias()) {
aliases = aliases.concat(matcherObject(room, room.getCanonicalAlias(), room.name)); aliases = aliases.concat(matcherObject(room, room.getCanonicalAlias(), room.name));
} }

View file

@ -648,7 +648,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
onFinished: (confirm) => { onFinished: (confirm) => {
if (confirm) { if (confirm) {
// FIXME: controller shouldn't be loading a view :( // FIXME: controller shouldn't be loading a view :(
const modal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner"); const modal = Modal.createDialog(Spinner, undefined, "mx_Dialog_spinner");
MatrixClientPeg.get() MatrixClientPeg.get()
.leave(payload.room_id) .leave(payload.room_id)

View file

@ -72,7 +72,7 @@ export default class MKeyVerificationConclusion extends React.Component<IProps>
this.forceUpdate(); this.forceUpdate();
}; };
public static shouldRender(mxEvent: MatrixEvent, request: VerificationRequest): boolean { public static shouldRender(mxEvent: MatrixEvent, request?: VerificationRequest): boolean {
// normally should not happen // normally should not happen
if (!request) { if (!request) {
return false; return false;
@ -99,9 +99,9 @@ export default class MKeyVerificationConclusion extends React.Component<IProps>
return true; return true;
} }
public render(): JSX.Element { public render(): JSX.Element | null {
const { mxEvent } = this.props; const { mxEvent } = this.props;
const request = mxEvent.verificationRequest; const request = mxEvent.verificationRequest!;
if (!MKeyVerificationConclusion.shouldRender(mxEvent, request)) { if (!MKeyVerificationConclusion.shouldRender(mxEvent, request)) {
return null; return null;
@ -110,7 +110,7 @@ export default class MKeyVerificationConclusion extends React.Component<IProps>
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const myUserId = client.getUserId(); const myUserId = client.getUserId();
let title; let title: string | undefined;
if (request.done) { if (request.done) {
title = _t("You verified %(name)s", { title = _t("You verified %(name)s", {

View file

@ -268,7 +268,7 @@ export default async function createRoom(opts: IOpts): Promise<string | null> {
} }
let modal; let modal;
if (opts.spinner) modal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner"); if (opts.spinner) modal = Modal.createDialog(Spinner, undefined, "mx_Dialog_spinner");
let roomId: string; let roomId: string;
let room: Promise<Room>; let room: Promise<Room>;
@ -417,9 +417,9 @@ export async function ensureVirtualRoomExists(
client: MatrixClient, client: MatrixClient,
userId: string, userId: string,
nativeRoomId: string, nativeRoomId: string,
): Promise<string> { ): Promise<string | null> {
const existingDMRoom = findDMForUser(client, userId); const existingDMRoom = findDMForUser(client, userId);
let roomId; let roomId: string | null;
if (existingDMRoom) { if (existingDMRoom) {
roomId = existingDMRoom.roomId; roomId = existingDMRoom.roomId;
} else { } else {
@ -440,13 +440,13 @@ export async function ensureVirtualRoomExists(
return roomId; return roomId;
} }
export async function ensureDMExists(client: MatrixClient, userId: string): Promise<string> { export async function ensureDMExists(client: MatrixClient, userId: string): Promise<string | null> {
const existingDMRoom = findDMForUser(client, userId); const existingDMRoom = findDMForUser(client, userId);
let roomId; let roomId: string | null;
if (existingDMRoom) { if (existingDMRoom) {
roomId = existingDMRoom.roomId; roomId = existingDMRoom.roomId;
} else { } else {
let encryption: boolean = undefined; let encryption: boolean | undefined;
if (privateShouldBeEncrypted()) { if (privateShouldBeEncrypted()) {
encryption = await canEncryptToAllUsers(client, [userId]); encryption = await canEncryptToAllUsers(client, [userId]);
} }

View file

@ -75,7 +75,7 @@ export class Media {
/** /**
* The HTTP URL for the source media. * The HTTP URL for the source media.
*/ */
public get srcHttp(): string { public get srcHttp(): string | null {
// eslint-disable-next-line no-restricted-properties // eslint-disable-next-line no-restricted-properties
return this.client.mxcUrlToHttp(this.srcMxc); return this.client.mxcUrlToHttp(this.srcMxc);
} }
@ -87,7 +87,7 @@ export class Media {
public get thumbnailHttp(): string | undefined | null { public get thumbnailHttp(): string | undefined | null {
if (!this.hasThumbnail) return null; if (!this.hasThumbnail) return null;
// eslint-disable-next-line no-restricted-properties // eslint-disable-next-line no-restricted-properties
return this.client.mxcUrlToHttp(this.thumbnailMxc); return this.client.mxcUrlToHttp(this.thumbnailMxc!);
} }
/** /**
@ -98,13 +98,13 @@ export class Media {
* @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale. * @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale.
* @returns {string} The HTTP URL which points to the thumbnail. * @returns {string} The HTTP URL which points to the thumbnail.
*/ */
public getThumbnailHttp(width: number, height: number, mode: ResizeMethod = "scale"): string | null | undefined { public getThumbnailHttp(width: number, height: number, mode: ResizeMethod = "scale"): string | null {
if (!this.hasThumbnail) return null; if (!this.hasThumbnail) return null;
// scale using the device pixel ratio to keep images clear // scale using the device pixel ratio to keep images clear
width = Math.floor(width * window.devicePixelRatio); width = Math.floor(width * window.devicePixelRatio);
height = Math.floor(height * window.devicePixelRatio); height = Math.floor(height * window.devicePixelRatio);
// eslint-disable-next-line no-restricted-properties // eslint-disable-next-line no-restricted-properties
return this.client.mxcUrlToHttp(this.thumbnailMxc, width, height, mode); return this.client.mxcUrlToHttp(this.thumbnailMxc!, width, height, mode);
} }
/** /**
@ -114,7 +114,7 @@ export class Media {
* @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale. * @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale.
* @returns {string} The HTTP URL which points to the thumbnail. * @returns {string} The HTTP URL which points to the thumbnail.
*/ */
public getThumbnailOfSourceHttp(width: number, height: number, mode: ResizeMethod = "scale"): string { public getThumbnailOfSourceHttp(width: number, height: number, mode: ResizeMethod = "scale"): string | null {
// scale using the device pixel ratio to keep images clear // scale using the device pixel ratio to keep images clear
width = Math.floor(width * window.devicePixelRatio); width = Math.floor(width * window.devicePixelRatio);
height = Math.floor(height * window.devicePixelRatio); height = Math.floor(height * window.devicePixelRatio);
@ -128,7 +128,7 @@ export class Media {
* @param {number} dim The desired width and height. * @param {number} dim The desired width and height.
* @returns {string} An HTTP URL for the thumbnail. * @returns {string} An HTTP URL for the thumbnail.
*/ */
public getSquareThumbnailHttp(dim: number): string { public getSquareThumbnailHttp(dim: number): string | null {
dim = Math.floor(dim * window.devicePixelRatio); // scale using the device pixel ratio to keep images clear dim = Math.floor(dim * window.devicePixelRatio); // scale using the device pixel ratio to keep images clear
if (this.hasThumbnail) { if (this.hasThumbnail) {
return this.getThumbnailHttp(dim, dim, "crop"); return this.getThumbnailHttp(dim, dim, "crop");
@ -161,6 +161,6 @@ export function mediaFromContent(content: Partial<IMediaEventContent>, client?:
* @param {MatrixClient} client? Optional client to use. * @param {MatrixClient} client? Optional client to use.
* @returns {Media} The media object. * @returns {Media} The media object.
*/ */
export function mediaFromMxc(mxc: string, client?: MatrixClient): Media { export function mediaFromMxc(mxc?: string, client?: MatrixClient): Media {
return mediaFromContent({ url: mxc }, client); return mediaFromContent({ url: mxc }, client);
} }

View file

@ -30,19 +30,19 @@ function persistCredentials(credentials: IMatrixClientCreds): void {
} }
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
function createSecretStorageKey(): Uint8Array { function createSecretStorageKey(): Uint8Array | null {
// E.g. generate or retrieve secret storage key somehow // E.g. generate or retrieve secret storage key somehow
return null; return null;
} }
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
function getSecretStorageKey(): Uint8Array { function getSecretStorageKey(): Uint8Array | null {
// E.g. retrieve secret storage key from some other place // E.g. retrieve secret storage key from some other place
return null; return null;
} }
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
function getDehydrationKey(keyInfo: ISecretStorageKeyInfo): Promise<Uint8Array> { function getDehydrationKey(keyInfo: ISecretStorageKeyInfo): Promise<Uint8Array | null> {
return Promise.resolve(null); return Promise.resolve(null);
} }

View file

@ -72,7 +72,7 @@ export interface IMediaObject {
* @throws Throws if the given content cannot be packaged into a prepared media object. * @throws Throws if the given content cannot be packaged into a prepared media object.
*/ */
export function prepEventContentAsMedia(content: Partial<IMediaEventContent>): IPreparedMedia { export function prepEventContentAsMedia(content: Partial<IMediaEventContent>): IPreparedMedia {
let thumbnail: IMediaObject = null; let thumbnail: IMediaObject | undefined;
if (content?.info?.thumbnail_url) { if (content?.info?.thumbnail_url) {
thumbnail = { thumbnail = {
mxc: content.info.thumbnail_url, mxc: content.info.thumbnail_url,

View file

@ -32,7 +32,7 @@ export function setSelection(editor: HTMLDivElement, model: EditorModel, selecti
} }
function setDocumentRangeSelection(editor: HTMLDivElement, model: EditorModel, range: Range): void { function setDocumentRangeSelection(editor: HTMLDivElement, model: EditorModel, range: Range): void {
const sel = document.getSelection(); const sel = document.getSelection()!;
sel.removeAllRanges(); sel.removeAllRanges();
const selectionRange = document.createRange(); const selectionRange = document.createRange();
const start = getNodeAndOffsetForPosition(editor, model, range.start); const start = getNodeAndOffsetForPosition(editor, model, range.start);
@ -50,7 +50,7 @@ export function setCaretPosition(editor: HTMLDivElement, model: EditorModel, car
range.setStart(node, offset); range.setStart(node, offset);
range.collapse(true); range.collapse(true);
const sel = document.getSelection(); const sel = document.getSelection()!;
if (sel.rangeCount === 1) { if (sel.rangeCount === 1) {
const existingRange = sel.getRangeAt(0); const existingRange = sel.getRangeAt(0);
if ( if (
@ -124,7 +124,7 @@ function findNodeInLineForPart(parts: Part[], partIndex: number): { lineIndex: n
let lineIndex = 0; let lineIndex = 0;
let nodeIndex = -1; let nodeIndex = -1;
let prevPart = null; let prevPart: Part | undefined;
// go through to parts up till (and including) the index // go through to parts up till (and including) the index
// to find newline parts // to find newline parts
for (let i = 0; i <= partIndex; ++i) { for (let i = 0; i <= partIndex; ++i) {
@ -132,7 +132,7 @@ function findNodeInLineForPart(parts: Part[], partIndex: number): { lineIndex: n
if (part.type === Type.Newline) { if (part.type === Type.Newline) {
lineIndex += 1; lineIndex += 1;
nodeIndex = -1; nodeIndex = -1;
prevPart = null; prevPart = undefined;
} else { } else {
nodeIndex += 1; nodeIndex += 1;
if (needsCaretNodeBefore(part, prevPart)) { if (needsCaretNodeBefore(part, prevPart)) {

View file

@ -51,7 +51,7 @@ export function longestBacktickSequence(text: string): number {
} }
function isListChild(n: Node): boolean { function isListChild(n: Node): boolean {
return LIST_TYPES.includes(n.parentNode?.nodeName); return LIST_TYPES.includes(n.parentNode?.nodeName || "");
} }
function parseAtRoomMentions(text: string, pc: PartCreator, opts: IParseOptions): Part[] { function parseAtRoomMentions(text: string, pc: PartCreator, opts: IParseOptions): Part[] {

View file

@ -31,7 +31,7 @@ export default class HistoryManager {
private newlyTypedCharCount = 0; private newlyTypedCharCount = 0;
private currentIndex = -1; private currentIndex = -1;
private changedSinceLastPush = false; private changedSinceLastPush = false;
private lastCaret: Caret = null; private lastCaret: Caret | null = null;
private nonWordBoundarySinceLastPush = false; private nonWordBoundarySinceLastPush = false;
private addedSinceLastPush = false; private addedSinceLastPush = false;
private removedSinceLastPush = false; private removedSinceLastPush = false;
@ -65,7 +65,7 @@ export default class HistoryManager {
// as long as you've only been adding or removing since the last push // as long as you've only been adding or removing since the last push
if (this.addedSinceLastPush !== this.removedSinceLastPush) { if (this.addedSinceLastPush !== this.removedSinceLastPush) {
// add steps by word boundary, up to MAX_STEP_LENGTH characters // add steps by word boundary, up to MAX_STEP_LENGTH characters
const str = diff.added ? diff.added : diff.removed; const str = diff.added ? diff.added : diff.removed!;
const isWordBoundary = str === " " || str === "\t" || str === "\n"; const isWordBoundary = str === " " || str === "\t" || str === "\n";
if (this.nonWordBoundarySinceLastPush && isWordBoundary) { if (this.nonWordBoundarySinceLastPush && isWordBoundary) {
return true; return true;

View file

@ -51,13 +51,13 @@ type ManualTransformCallback = () => Caret;
export default class EditorModel { export default class EditorModel {
private _parts: Part[]; private _parts: Part[];
private readonly _partCreator: PartCreator; private readonly _partCreator: PartCreator;
private activePartIdx: number = null; private activePartIdx: number | null = null;
private _autoComplete: AutocompleteWrapperModel = null; private _autoComplete: AutocompleteWrapperModel | null = null;
private autoCompletePartIdx: number = null; private autoCompletePartIdx: number | null = null;
private autoCompletePartCount = 0; private autoCompletePartCount = 0;
private transformCallback: TransformCallback = null; private transformCallback: TransformCallback | null = null;
public constructor(parts: Part[], partCreator: PartCreator, private updateCallback: UpdateCallback = null) { public constructor(parts: Part[], partCreator: PartCreator, private updateCallback: UpdateCallback | null = null) {
this._parts = parts; this._parts = parts;
this._partCreator = partCreator; this._partCreator = partCreator;
this.transformCallback = null; this.transformCallback = null;

View file

@ -455,7 +455,7 @@ class AtRoomPillPart extends RoomPillPart {
} }
class UserPillPart extends PillPart { class UserPillPart extends PillPart {
public constructor(userId, displayName, private member: RoomMember) { public constructor(userId, displayName, private member?: RoomMember) {
super(userId, displayName); super(userId, displayName);
} }
@ -625,7 +625,7 @@ export class PartCreator {
public userPill(displayName: string, userId: string): UserPillPart { public userPill(displayName: string, userId: string): UserPillPart {
const member = this.room.getMember(userId); const member = this.room.getMember(userId);
return new UserPillPart(userId, displayName, member); return new UserPillPart(userId, displayName, member || undefined);
} }
private static isRegionalIndicator(c: string): boolean { private static isRegionalIndicator(c: string): boolean {

View file

@ -79,6 +79,8 @@ export default class DocumentPosition implements IPosition {
offset = 0; offset = 0;
} }
} }
return this; // impossible but Typescript doesn't believe us
} }
public backwardsWhile(model: EditorModel, predicate: Predicate): DocumentPosition { public backwardsWhile(model: EditorModel, predicate: Predicate): DocumentPosition {
@ -104,6 +106,8 @@ export default class DocumentPosition implements IPosition {
offset = parts[index].text.length; offset = parts[index].text.length;
} }
} }
return this; // impossible but Typescript doesn't believe us
} }
public asOffset(model: EditorModel): DocumentOffset { public asOffset(model: EditorModel): DocumentOffset {

View file

@ -18,7 +18,7 @@ limitations under the License.
import { Part, Type } from "./parts"; import { Part, Type } from "./parts";
import EditorModel from "./model"; import EditorModel from "./model";
export function needsCaretNodeBefore(part: Part, prevPart: Part): boolean { export function needsCaretNodeBefore(part: Part, prevPart?: Part): boolean {
const isFirst = !prevPart || prevPart.type === Type.Newline; const isFirst = !prevPart || prevPart.type === Type.Newline;
return !part.acceptsCaret && (isFirst || !prevPart.acceptsCaret); return !part.acceptsCaret && (isFirst || !prevPart.acceptsCaret);
} }
@ -30,9 +30,9 @@ export function needsCaretNodeAfter(part: Part, isLastOfLine: boolean): boolean
function insertAfter(node: HTMLElement, nodeToInsert: HTMLElement): void { function insertAfter(node: HTMLElement, nodeToInsert: HTMLElement): void {
const next = node.nextSibling; const next = node.nextSibling;
if (next) { if (next) {
node.parentElement.insertBefore(nodeToInsert, next); node.parentElement!.insertBefore(nodeToInsert, next);
} else { } else {
node.parentElement.appendChild(nodeToInsert); node.parentElement!.appendChild(nodeToInsert);
} }
} }
@ -58,11 +58,11 @@ function updateCaretNode(node: HTMLElement): void {
} }
} }
export function isCaretNode(node: HTMLElement): boolean { export function isCaretNode(node?: Node | null): node is HTMLElement {
return node && node.tagName === "SPAN" && node.className === "caretNode"; return !!node && node instanceof HTMLElement && node.tagName === "SPAN" && node.className === "caretNode";
} }
function removeNextSiblings(node: ChildNode): void { function removeNextSiblings(node: ChildNode | null): void {
if (!node) { if (!node) {
return; return;
} }
@ -83,13 +83,13 @@ function removeChildren(parent: HTMLElement): void {
} }
function reconcileLine(lineContainer: ChildNode, parts: Part[]): void { function reconcileLine(lineContainer: ChildNode, parts: Part[]): void {
let currentNode; let currentNode: ChildNode | null = null;
let prevPart; let prevPart: Part | undefined;
const lastPart = parts[parts.length - 1]; const lastPart = parts[parts.length - 1];
for (const part of parts) { for (const part of parts) {
const isFirst = !prevPart; const isFirst = !prevPart;
currentNode = isFirst ? lineContainer.firstChild : currentNode.nextSibling; currentNode = isFirst ? lineContainer.firstChild : currentNode!.nextSibling;
if (needsCaretNodeBefore(part, prevPart)) { if (needsCaretNodeBefore(part, prevPart)) {
if (isCaretNode(currentNode)) { if (isCaretNode(currentNode)) {
@ -109,18 +109,18 @@ function reconcileLine(lineContainer: ChildNode, parts: Part[]): void {
if (currentNode && part) { if (currentNode && part) {
part.updateDOMNode(currentNode); part.updateDOMNode(currentNode);
} else if (part) { } else if (part) {
currentNode = part.toDOMNode(); currentNode = part.toDOMNode() as ChildNode;
// hooks up nextSibling for next iteration // hooks up nextSibling for next iteration
lineContainer.appendChild(currentNode); lineContainer.appendChild(currentNode);
} }
if (needsCaretNodeAfter(part, part === lastPart)) { if (needsCaretNodeAfter(part, part === lastPart)) {
if (isCaretNode(currentNode.nextSibling)) { if (isCaretNode(currentNode?.nextSibling)) {
currentNode = currentNode.nextSibling; currentNode = currentNode!.nextSibling;
updateCaretNode(currentNode); updateCaretNode(currentNode as HTMLElement);
} else { } else {
const caretNode = createCaretNode(); const caretNode = createCaretNode();
insertAfter(currentNode, caretNode); insertAfter(currentNode as HTMLElement, caretNode);
currentNode = caretNode; currentNode = caretNode;
} }
} }
@ -150,7 +150,7 @@ function reconcileEmptyLine(lineContainer: HTMLElement): void {
} }
export function renderModel(editor: HTMLDivElement, model: EditorModel): void { export function renderModel(editor: HTMLDivElement, model: EditorModel): void {
const lines = model.parts.reduce( const lines = model.parts.reduce<Part[][]>(
(linesArr, part) => { (linesArr, part) => {
if (part.type === Type.Newline) { if (part.type === Type.Newline) {
linesArr.push([]); linesArr.push([]);

View file

@ -50,8 +50,8 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
// round-trip after the client is ready, and we often load widgets in that time, and we'd // round-trip after the client is ready, and we often load widgets in that time, and we'd
// and up passing them an incorrect display name // and up passing them an incorrect display name
super(defaultDispatcher, { super(defaultDispatcher, {
displayName: window.localStorage.getItem(KEY_DISPLAY_NAME), displayName: window.localStorage.getItem(KEY_DISPLAY_NAME) || undefined,
avatarUrl: window.localStorage.getItem(KEY_AVATAR_URL), avatarUrl: window.localStorage.getItem(KEY_AVATAR_URL) || undefined,
}); });
} }

View file

@ -25,7 +25,7 @@ import { IDestroyable } from "../utils/IDestroyable";
import { Action } from "../dispatcher/actions"; import { Action } from "../dispatcher/actions";
export abstract class ReadyWatchingStore extends EventEmitter implements IDestroyable { export abstract class ReadyWatchingStore extends EventEmitter implements IDestroyable {
protected matrixClient: MatrixClient; protected matrixClient: MatrixClient | null = null;
private dispatcherRef: string | null = null; private dispatcherRef: string | null = null;
public constructor(protected readonly dispatcher: Dispatcher<ActionPayload>) { public constructor(protected readonly dispatcher: Dispatcher<ActionPayload>) {
@ -42,7 +42,7 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro
} }
} }
public get mxClient(): MatrixClient { public get mxClient(): MatrixClient | null {
return this.matrixClient; // for external readonly access return this.matrixClient; // for external readonly access
} }

View file

@ -21,7 +21,7 @@ export enum UI_EVENTS {
} }
export default class UIStore extends EventEmitter { export default class UIStore extends EventEmitter {
private static _instance: UIStore = null; private static _instance: UIStore | null = null;
private resizeObserver: ResizeObserver; private resizeObserver: ResizeObserver;
@ -58,7 +58,7 @@ export default class UIStore extends EventEmitter {
} }
} }
public getElementDimensions(name: string): DOMRectReadOnly { public getElementDimensions(name: string): DOMRectReadOnly | undefined {
return this.uiElementDimensions.get(name); return this.uiElementDimensions.get(name);
} }
@ -68,7 +68,7 @@ export default class UIStore extends EventEmitter {
} }
public stopTrackingElementDimensions(name: string): void { public stopTrackingElementDimensions(name: string): void {
let trackedElement: Element; let trackedElement: Element | undefined;
this.trackedUiElements.forEach((trackedElementName, element) => { this.trackedUiElements.forEach((trackedElementName, element) => {
if (trackedElementName === name) { if (trackedElementName === name) {
trackedElement = element; trackedElement = element;

View file

@ -66,7 +66,7 @@ export const flattenSpaceHierarchyWithCache =
useCache = true, useCache = true,
): Set<string> => { ): Set<string> => {
if (useCache && cache.has(spaceId)) { if (useCache && cache.has(spaceId)) {
return cache.get(spaceId); return cache.get(spaceId)!;
} }
const result = flattenSpaceHierarchy(spaceEntityMap, spaceDescendantMap, spaceId); const result = flattenSpaceHierarchy(spaceEntityMap, spaceDescendantMap, spaceId);
cache.set(spaceId, result); cache.set(spaceId, result);

View file

@ -37,12 +37,12 @@ function checkVersion(ver: string): boolean {
} }
function installUpdate(): void { function installUpdate(): void {
PlatformPeg.get().installUpdate(); PlatformPeg.get()?.installUpdate();
} }
export const showToast = (version: string, newVersion: string, releaseNotes?: string): void => { export const showToast = (version: string, newVersion: string, releaseNotes?: string): void => {
function onReject(): void { function onReject(): void {
PlatformPeg.get().deferUpdate(newVersion); PlatformPeg.get()?.deferUpdate(newVersion);
} }
let onAccept; let onAccept;
@ -55,7 +55,7 @@ export const showToast = (version: string, newVersion: string, releaseNotes?: st
button: _t("Update"), button: _t("Update"),
onFinished: (update) => { onFinished: (update) => {
if (update && PlatformPeg.get()) { if (update && PlatformPeg.get()) {
PlatformPeg.get().installUpdate(); PlatformPeg.get()!.installUpdate();
} }
}, },
}); });
@ -67,7 +67,7 @@ export const showToast = (version: string, newVersion: string, releaseNotes?: st
newVersion, newVersion,
onFinished: (update) => { onFinished: (update) => {
if (update && PlatformPeg.get()) { if (update && PlatformPeg.get()) {
PlatformPeg.get().installUpdate(); PlatformPeg.get()!.installUpdate();
} }
}, },
}); });

View file

@ -62,7 +62,7 @@ export class DialogOpener {
roomId: payload.room_id || SdkContextClass.instance.roomViewStore.getRoomId(), roomId: payload.room_id || SdkContextClass.instance.roomViewStore.getRoomId(),
initialTabId: payload.initial_tab_id, initialTabId: payload.initial_tab_id,
}, },
/*className=*/ null, /*className=*/ undefined,
/*isPriority=*/ false, /*isPriority=*/ false,
/*isStatic=*/ true, /*isStatic=*/ true,
); );
@ -90,7 +90,7 @@ export class DialogOpener {
initialTabId: payload.initalTabId, initialTabId: payload.initalTabId,
space: payload.space, space: payload.space,
}, },
null, undefined,
false, false,
true, true,
); );
@ -102,7 +102,7 @@ export class DialogOpener {
matrixClient: payload.space.client, matrixClient: payload.space.client,
space: payload.space, space: payload.space,
}, },
/*className=*/ null, /*className=*/ undefined,
/*isPriority=*/ false, /*isPriority=*/ false,
/*isStatic=*/ true, /*isStatic=*/ true,
); );

View file

@ -62,9 +62,9 @@ export async function upgradeRoom(
progressCallback?: (progress: IProgress) => void, progressCallback?: (progress: IProgress) => void,
): Promise<string> { ): Promise<string> {
const cli = room.client; const cli = room.client;
let spinnerModal: IHandle<any>; let spinnerModal: IHandle<any> | undefined;
if (!progressCallback) { if (!progressCallback) {
spinnerModal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner"); spinnerModal = Modal.createDialog(Spinner, undefined, "mx_Dialog_spinner");
} }
let toInvite: string[] = []; let toInvite: string[] = [];
@ -78,7 +78,9 @@ export async function upgradeRoom(
if (updateSpaces) { if (updateSpaces) {
parentsToRelink = Array.from(SpaceStore.instance.getKnownParents(room.roomId)) parentsToRelink = Array.from(SpaceStore.instance.getKnownParents(room.roomId))
.map((roomId) => cli.getRoom(roomId)) .map((roomId) => cli.getRoom(roomId))
.filter((parent) => parent?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId())); .filter((parent) =>
parent?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId()!),
) as Room[];
} }
const progress: IProgress = { const progress: IProgress = {
@ -117,7 +119,7 @@ export async function upgradeRoom(
if (toInvite.length > 0) { if (toInvite.length > 0) {
// Errors are handled internally to this function // Errors are handled internally to this function
await inviteUsersToRoom(newRoomId, toInvite, false, () => { await inviteUsersToRoom(newRoomId, toInvite, false, () => {
progress.inviteUsersProgress++; progress.inviteUsersProgress!++;
progressCallback?.(progress); progressCallback?.(progress);
}); });
} }
@ -137,7 +139,7 @@ export async function upgradeRoom(
); );
await cli.sendStateEvent(parent.roomId, EventType.SpaceChild, {}, room.roomId); await cli.sendStateEvent(parent.roomId, EventType.SpaceChild, {}, room.roomId);
progress.updateSpacesProgress++; progress.updateSpacesProgress!++;
progressCallback?.(progress); progressCallback?.(progress);
} }
} catch (e) { } catch (e) {

View file

@ -51,7 +51,7 @@ export async function shieldStatusForRoom(client: MatrixClient, room: Room): Pro
!inDMMap && // Don't alarm for self in DMs with other users !inDMMap && // Don't alarm for self in DMs with other users
members.length !== 2) || // Don't alarm for self in 1:1 chats with other users members.length !== 2) || // Don't alarm for self in 1:1 chats with other users
members.length === 1; // Do alarm for self if we're alone in a room members.length === 1; // Do alarm for self if we're alone in a room
const targets = includeUser ? [...verified, client.getUserId()] : verified; const targets = includeUser ? [...verified, client.getUserId()!] : verified;
for (const userId of targets) { for (const userId of targets) {
const devices = client.getStoredDevicesForUser(userId); const devices = client.getStoredDevicesForUser(userId);
const anyDeviceNotVerified = devices.some(({ deviceId }) => { const anyDeviceNotVerified = devices.some(({ deviceId }) => {

View file

@ -181,7 +181,7 @@ export function setCryptoInitialised(cryptoInited: boolean): void {
/* Simple wrapper functions around IndexedDB. /* Simple wrapper functions around IndexedDB.
*/ */
let idb: IDBDatabase = null; let idb: IDBDatabase | null = null;
async function idbInit(): Promise<void> { async function idbInit(): Promise<void> {
if (!indexedDB) { if (!indexedDB) {
@ -206,7 +206,7 @@ export async function idbLoad(table: string, key: string | string[]): Promise<an
await idbInit(); await idbInit();
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const txn = idb.transaction([table], "readonly"); const txn = idb!.transaction([table], "readonly");
txn.onerror = reject; txn.onerror = reject;
const objectStore = txn.objectStore(table); const objectStore = txn.objectStore(table);
@ -223,7 +223,7 @@ export async function idbSave(table: string, key: string | string[], data: any):
await idbInit(); await idbInit();
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const txn = idb.transaction([table], "readwrite"); const txn = idb!.transaction([table], "readwrite");
txn.onerror = reject; txn.onerror = reject;
const objectStore = txn.objectStore(table); const objectStore = txn.objectStore(table);
@ -240,7 +240,7 @@ export async function idbDelete(table: string, key: string | string[]): Promise<
await idbInit(); await idbInit();
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const txn = idb.transaction([table], "readwrite"); const txn = idb!.transaction([table], "readwrite");
txn.onerror = reject; txn.onerror = reject;
const objectStore = txn.objectStore(table); const objectStore = txn.objectStore(table);

View file

@ -26,8 +26,8 @@ Once a timer is finished or aborted, it can't be started again
a new one through `clone()` or `cloneIfRun()`. a new one through `clone()` or `cloneIfRun()`.
*/ */
export default class Timer { export default class Timer {
private timerHandle: number; private timerHandle?: number;
private startTs: number; private startTs?: number;
private promise: Promise<void>; private promise: Promise<void>;
private resolve: () => void; private resolve: () => void;
private reject: (Error) => void; private reject: (Error) => void;
@ -37,19 +37,19 @@ export default class Timer {
} }
private setNotStarted(): void { private setNotStarted(): void {
this.timerHandle = null; this.timerHandle = undefined;
this.startTs = null; this.startTs = undefined;
this.promise = new Promise<void>((resolve, reject) => { this.promise = new Promise<void>((resolve, reject) => {
this.resolve = resolve; this.resolve = resolve;
this.reject = reject; this.reject = reject;
}).finally(() => { }).finally(() => {
this.timerHandle = null; this.timerHandle = undefined;
}); });
} }
private onTimeout = (): void => { private onTimeout = (): void => {
const now = Date.now(); const now = Date.now();
const elapsed = now - this.startTs; const elapsed = now - this.startTs!;
if (elapsed >= this.timeout) { if (elapsed >= this.timeout) {
this.resolve(); this.resolve();
this.setNotStarted(); this.setNotStarted();
@ -124,6 +124,6 @@ export default class Timer {
} }
public isRunning(): boolean { public isRunning(): boolean {
return this.timerHandle !== null; return this.timerHandle !== undefined;
} }
} }

View file

@ -31,7 +31,7 @@ export function abbreviateUrl(u: string): string {
if (parsedUrl.path === "/") { if (parsedUrl.path === "/") {
// we ignore query / hash parts: these aren't relevant for IS server URLs // we ignore query / hash parts: these aren't relevant for IS server URLs
return parsedUrl.host; return parsedUrl.host || "";
} }
return u; return u;

View file

@ -266,7 +266,7 @@ export class ArrayUtil<T> {
const obj = this.a.reduce((rv: Map<K, T[]>, val: T) => { const obj = this.a.reduce((rv: Map<K, T[]>, val: T) => {
const k = fn(val); const k = fn(val);
if (!rv.has(k)) rv.set(k, []); if (!rv.has(k)) rv.set(k, []);
rv.get(k).push(val); rv.get(k)!.push(val);
return rv; return rv;
}, new Map<K, T[]>()); }, new Map<K, T[]>());
return new GroupedArray(obj); return new GroupedArray(obj);
@ -299,7 +299,7 @@ export class GroupedArray<K, T> {
const a: T[] = []; const a: T[] = [];
for (const k of keyOrder) { for (const k of keyOrder) {
if (!this.val.has(k)) continue; if (!this.val.has(k)) continue;
a.push(...this.val.get(k)); a.push(...this.val.get(k)!);
} }
return new ArrayUtil(a); return new ArrayUtil(a);
} }

View file

@ -39,7 +39,7 @@ import { SdkContextClass } from "../contexts/SDKContext";
export async function leaveRoomBehaviour(roomId: string, retry = true, spinner = true): Promise<void> { export async function leaveRoomBehaviour(roomId: string, retry = true, spinner = true): Promise<void> {
let spinnerModal: IHandle<any>; let spinnerModal: IHandle<any>;
if (spinner) { if (spinner) {
spinnerModal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner"); spinnerModal = Modal.createDialog(Spinner, undefined, "mx_Dialog_spinner");
} }
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();

View file

@ -79,14 +79,13 @@ const ANY_REGEX = /.*/;
// the list and magically have the link work. // the list and magically have the link work.
export class RoomPermalinkCreator { export class RoomPermalinkCreator {
private room: Room;
private roomId: string; private roomId: string;
private highestPlUserId: string; private highestPlUserId: string | null = null;
private populationMap: { [serverName: string]: number }; private populationMap: { [serverName: string]: number } | null = null;
private bannedHostsRegexps: RegExp[]; private bannedHostsRegexps: RegExp[] | null = null;
private allowedHostsRegexps: RegExp[]; private allowedHostsRegexps: RegExp[] | null = null;
private _serverCandidates: string[]; private _serverCandidates: string[] | null = null;
private started: boolean; private started = false;
// We support being given a roomId as a fallback in the event the `room` object // We support being given a roomId as a fallback in the event the `room` object
// doesn't exist or is not healthy for us to rely on. For example, loading a // doesn't exist or is not healthy for us to rely on. For example, loading a
@ -94,15 +93,8 @@ export class RoomPermalinkCreator {
// Some of the tests done by this class are relatively expensive, so normally // Some of the tests done by this class are relatively expensive, so normally
// throttled to not happen on every update. Pass false as the shouldThrottle // throttled to not happen on every update. Pass false as the shouldThrottle
// param to disable this behaviour, eg. for tests. // param to disable this behaviour, eg. for tests.
public constructor(room: Room, roomId: string | null = null, shouldThrottle = true) { public constructor(private room: Room, roomId: string | null = null, shouldThrottle = true) {
this.room = room; this.roomId = room ? room.roomId : roomId!;
this.roomId = room ? room.roomId : roomId;
this.highestPlUserId = null;
this.populationMap = null;
this.bannedHostsRegexps = null;
this.allowedHostsRegexps = null;
this._serverCandidates = null;
this.started = false;
if (!this.roomId) { if (!this.roomId) {
throw new Error("Failed to resolve a roomId for the permalink creator to use"); throw new Error("Failed to resolve a roomId for the permalink creator to use");
@ -316,7 +308,7 @@ export function isPermalinkHost(host: string): boolean {
* @param {string} entity The entity to transform. * @param {string} entity The entity to transform.
* @returns {string|null} The transformed permalink or null if unable. * @returns {string|null} The transformed permalink or null if unable.
*/ */
export function tryTransformEntityToPermalink(entity: string): string { export function tryTransformEntityToPermalink(entity: string): string | null {
if (!entity) return null; if (!entity) return null;
// Check to see if it is a bare entity for starters // Check to see if it is a bare entity for starters
@ -391,7 +383,7 @@ export function tryTransformPermalinkToLocalHref(permalink: string): string {
return permalink; return permalink;
} }
export function getPrimaryPermalinkEntity(permalink: string): string { export function getPrimaryPermalinkEntity(permalink: string): string | null {
try { try {
let permalinkParts = parsePermalink(permalink); let permalinkParts = parsePermalink(permalink);
@ -425,7 +417,7 @@ function getPermalinkConstructor(): PermalinkConstructor {
return new MatrixToPermalinkConstructor(); return new MatrixToPermalinkConstructor();
} }
export function parsePermalink(fullUrl: string): PermalinkParts { export function parsePermalink(fullUrl: string): PermalinkParts | null {
try { try {
const elementPrefix = SdkConfig.get("permalink_prefix"); const elementPrefix = SdkConfig.get("permalink_prefix");
if (decodeURIComponent(fullUrl).startsWith(matrixtoBaseUrl)) { if (decodeURIComponent(fullUrl).startsWith(matrixtoBaseUrl)) {

View file

@ -41,7 +41,7 @@ import { OpenAddExistingToSpaceDialogPayload } from "../dispatcher/payloads/Open
import { SdkContextClass } from "../contexts/SDKContext"; import { SdkContextClass } from "../contexts/SDKContext";
export const shouldShowSpaceSettings = (space: Room): boolean => { export const shouldShowSpaceSettings = (space: Room): boolean => {
const userId = space.client.getUserId(); const userId = space.client.getUserId()!;
return ( return (
space.getMyMembership() === "join" && space.getMyMembership() === "join" &&
(space.currentState.maySendStateEvent(EventType.RoomAvatar, userId) || (space.currentState.maySendStateEvent(EventType.RoomAvatar, userId) ||
@ -88,7 +88,7 @@ export const showCreateNewRoom = async (space: Room, type?: RoomType): Promise<b
}; };
export const shouldShowSpaceInvite = (space: Room): boolean => export const shouldShowSpaceInvite = (space: Room): boolean =>
((space?.getMyMembership() === "join" && space.canInvite(space.client.getUserId())) || ((space?.getMyMembership() === "join" && space.canInvite(space.client.getUserId()!)) ||
space.getJoinRule() === JoinRule.Public) && space.getJoinRule() === JoinRule.Public) &&
shouldShowComponent(UIComponent.InviteUsers); shouldShowComponent(UIComponent.InviteUsers);
@ -149,7 +149,7 @@ export const bulkSpaceBehaviour = async (
children: Room[], children: Room[],
fn: (room: Room) => Promise<unknown>, fn: (room: Room) => Promise<unknown>,
): Promise<void> => { ): Promise<void> => {
const modal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner"); const modal = Modal.createDialog(Spinner, undefined, "mx_Dialog_spinner");
try { try {
for (const room of children) { for (const room of children) {
await fn(room); await fn(room);

View file

@ -47,9 +47,9 @@ export function tooltipifyLinks(rootNodes: ArrayLike<Element>, ignoredNodes: Ele
if ( if (
node.tagName === "A" && node.tagName === "A" &&
node.getAttribute("href") && node.getAttribute("href") &&
node.getAttribute("href") !== node.textContent.trim() node.getAttribute("href") !== node.textContent?.trim()
) { ) {
let href = node.getAttribute("href"); let href = node.getAttribute("href")!;
try { try {
href = new URL(href, window.location.href).toString(); href = new URL(href, window.location.href).toString();
} catch (e) { } catch (e) {