* Improve typescript null checking in places * Iterate * Fix Timer.ts
This commit is contained in:
parent
97506cbcdb
commit
9743852380
43 changed files with 155 additions and 154 deletions
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue