You've already forked matrix-react-sdk
							
							
				mirror of
				https://github.com/matrix-org/matrix-react-sdk.git
				synced 2025-11-04 11:51:45 +03:00 
			
		
		
		
	* Improve typescript null checking in places * Iterate * Fix Timer.ts
This commit is contained in:
		
				
					committed by
					
						
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							97506cbcdb
						
					
				
				
					commit
					9743852380
				
			@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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[];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 === "/") {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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", {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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[] {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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([]);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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();
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 }) => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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) {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user