Conform more code to strict null checking (#10167)
* Conform more code to strict null checking * Delint * Iterate PR based on feedback
This commit is contained in:
parent
f7bea2cae5
commit
4574c665ea
103 changed files with 517 additions and 495 deletions
|
@ -89,7 +89,7 @@ async function loadImageElement(imageFile: File): Promise<{
|
||||||
|
|
||||||
// check for hi-dpi PNGs and fudge display resolution as needed.
|
// check for hi-dpi PNGs and fudge display resolution as needed.
|
||||||
// this is mainly needed for macOS screencaps
|
// this is mainly needed for macOS screencaps
|
||||||
let parsePromise: Promise<boolean>;
|
let parsePromise = Promise.resolve(false);
|
||||||
if (imageFile.type === "image/png") {
|
if (imageFile.type === "image/png") {
|
||||||
// in practice macOS happens to order the chunks so they fall in
|
// in practice macOS happens to order the chunks so they fall in
|
||||||
// the first 0x1000 bytes (thanks to a massive ICC header).
|
// the first 0x1000 bytes (thanks to a massive ICC header).
|
||||||
|
@ -101,7 +101,7 @@ async function loadImageElement(imageFile: File): Promise<{
|
||||||
const chunks = extractPngChunks(buffer);
|
const chunks = extractPngChunks(buffer);
|
||||||
for (const chunk of chunks) {
|
for (const chunk of chunks) {
|
||||||
if (chunk.name === "pHYs") {
|
if (chunk.name === "pHYs") {
|
||||||
if (chunk.data.byteLength !== PHYS_HIDPI.length) return;
|
if (chunk.data.byteLength !== PHYS_HIDPI.length) return false;
|
||||||
return chunk.data.every((val, i) => val === PHYS_HIDPI[i]);
|
return chunk.data.every((val, i) => val === PHYS_HIDPI[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,10 +199,10 @@ function loadVideoElement(videoFile: File): Promise<HTMLVideoElement> {
|
||||||
reject(e);
|
reject(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
let dataUrl = ev.target.result as string;
|
let dataUrl = ev.target?.result as string;
|
||||||
// Chrome chokes on quicktime but likes mp4, and `file.type` is
|
// Chrome chokes on quicktime but likes mp4, and `file.type` is
|
||||||
// read only, so do this horrible hack to unbreak quicktime
|
// read only, so do this horrible hack to unbreak quicktime
|
||||||
if (dataUrl.startsWith("data:video/quicktime;")) {
|
if (dataUrl?.startsWith("data:video/quicktime;")) {
|
||||||
dataUrl = dataUrl.replace("data:video/quicktime;", "data:video/mp4;");
|
dataUrl = dataUrl.replace("data:video/quicktime;", "data:video/mp4;");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,7 +258,7 @@ function readFileAsArrayBuffer(file: File | Blob): Promise<ArrayBuffer> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = function (e): void {
|
reader.onload = function (e): void {
|
||||||
resolve(e.target.result as ArrayBuffer);
|
resolve(e.target?.result as ArrayBuffer);
|
||||||
};
|
};
|
||||||
reader.onerror = function (e): void {
|
reader.onerror = function (e): void {
|
||||||
reject(e);
|
reject(e);
|
||||||
|
@ -329,7 +329,7 @@ export async function uploadFile(
|
||||||
|
|
||||||
export default class ContentMessages {
|
export default class ContentMessages {
|
||||||
private inprogress: RoomUpload[] = [];
|
private inprogress: RoomUpload[] = [];
|
||||||
private mediaConfig: IMediaConfig = null;
|
private mediaConfig: IMediaConfig | null = null;
|
||||||
|
|
||||||
public sendStickerContentToRoom(
|
public sendStickerContentToRoom(
|
||||||
url: string,
|
url: string,
|
||||||
|
@ -377,8 +377,8 @@ export default class ContentMessages {
|
||||||
modal.close();
|
modal.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
const tooBigFiles = [];
|
const tooBigFiles: File[] = [];
|
||||||
const okFiles = [];
|
const okFiles: File[] = [];
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (this.isFileSizeAcceptable(file)) {
|
if (this.isFileSizeAcceptable(file)) {
|
||||||
|
@ -420,7 +420,14 @@ export default class ContentMessages {
|
||||||
}
|
}
|
||||||
|
|
||||||
promBefore = doMaybeLocalRoomAction(roomId, (actualRoomId) =>
|
promBefore = doMaybeLocalRoomAction(roomId, (actualRoomId) =>
|
||||||
this.sendContentToRoom(file, actualRoomId, relation, matrixClient, replyToEvent, loopPromiseBefore),
|
this.sendContentToRoom(
|
||||||
|
file,
|
||||||
|
actualRoomId,
|
||||||
|
relation,
|
||||||
|
matrixClient,
|
||||||
|
replyToEvent ?? undefined,
|
||||||
|
loopPromiseBefore,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -584,7 +591,7 @@ export default class ContentMessages {
|
||||||
}
|
}
|
||||||
|
|
||||||
private ensureMediaConfigFetched(matrixClient: MatrixClient): Promise<void> {
|
private ensureMediaConfigFetched(matrixClient: MatrixClient): Promise<void> {
|
||||||
if (this.mediaConfig !== null) return;
|
if (this.mediaConfig !== null) return Promise.resolve();
|
||||||
|
|
||||||
logger.log("[Media Config] Fetching");
|
logger.log("[Media Config] Fetching");
|
||||||
return matrixClient
|
return matrixClient
|
||||||
|
|
|
@ -138,7 +138,7 @@ export class DecryptionFailureTracker {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (err) {
|
if (err) {
|
||||||
this.addDecryptionFailure(new DecryptionFailure(e.getId(), err.code));
|
this.addDecryptionFailure(new DecryptionFailure(e.getId()!, err.code));
|
||||||
} else {
|
} else {
|
||||||
// Could be an event in the failures, remove it
|
// Could be an event in the failures, remove it
|
||||||
this.removeDecryptionFailuresForEvent(e);
|
this.removeDecryptionFailuresForEvent(e);
|
||||||
|
@ -146,7 +146,7 @@ export class DecryptionFailureTracker {
|
||||||
}
|
}
|
||||||
|
|
||||||
public addVisibleEvent(e: MatrixEvent): void {
|
public addVisibleEvent(e: MatrixEvent): void {
|
||||||
const eventId = e.getId();
|
const eventId = e.getId()!;
|
||||||
|
|
||||||
if (this.trackedEvents.has(eventId)) {
|
if (this.trackedEvents.has(eventId)) {
|
||||||
return;
|
return;
|
||||||
|
@ -154,7 +154,7 @@ export class DecryptionFailureTracker {
|
||||||
|
|
||||||
this.visibleEvents.add(eventId);
|
this.visibleEvents.add(eventId);
|
||||||
if (this.failures.has(eventId) && !this.visibleFailures.has(eventId)) {
|
if (this.failures.has(eventId) && !this.visibleFailures.has(eventId)) {
|
||||||
this.visibleFailures.set(eventId, this.failures.get(eventId));
|
this.visibleFailures.set(eventId, this.failures.get(eventId)!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +172,7 @@ export class DecryptionFailureTracker {
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeDecryptionFailuresForEvent(e: MatrixEvent): void {
|
public removeDecryptionFailuresForEvent(e: MatrixEvent): void {
|
||||||
const eventId = e.getId();
|
const eventId = e.getId()!;
|
||||||
this.failures.delete(eventId);
|
this.failures.delete(eventId);
|
||||||
this.visibleFailures.delete(eventId);
|
this.visibleFailures.delete(eventId);
|
||||||
}
|
}
|
||||||
|
@ -193,8 +193,8 @@ export class DecryptionFailureTracker {
|
||||||
* Clear state and stop checking for and tracking failures.
|
* Clear state and stop checking for and tracking failures.
|
||||||
*/
|
*/
|
||||||
public stop(): void {
|
public stop(): void {
|
||||||
clearInterval(this.checkInterval);
|
if (this.checkInterval) clearInterval(this.checkInterval);
|
||||||
clearInterval(this.trackInterval);
|
if (this.trackInterval) clearInterval(this.trackInterval);
|
||||||
|
|
||||||
this.failures = new Map();
|
this.failures = new Map();
|
||||||
this.visibleEvents = new Set();
|
this.visibleEvents = new Set();
|
||||||
|
|
|
@ -51,7 +51,7 @@ import { isBulkUnverifiedDeviceReminderSnoozed } from "./utils/device/snoozeBulk
|
||||||
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
|
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
|
||||||
|
|
||||||
export default class DeviceListener {
|
export default class DeviceListener {
|
||||||
private dispatcherRef: string;
|
private dispatcherRef: string | null;
|
||||||
// device IDs for which the user has dismissed the verify toast ('Later')
|
// device IDs for which the user has dismissed the verify toast ('Later')
|
||||||
private dismissed = new Set<string>();
|
private dismissed = new Set<string>();
|
||||||
// has the user dismissed any of the various nag toasts to setup encryption on this device?
|
// has the user dismissed any of the various nag toasts to setup encryption on this device?
|
||||||
|
@ -152,7 +152,7 @@ export default class DeviceListener {
|
||||||
private ensureDeviceIdsAtStartPopulated(): void {
|
private ensureDeviceIdsAtStartPopulated(): void {
|
||||||
if (this.ourDeviceIdsAtStart === null) {
|
if (this.ourDeviceIdsAtStart === null) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
this.ourDeviceIdsAtStart = new Set(cli.getStoredDevicesForUser(cli.getUserId()).map((d) => d.deviceId));
|
this.ourDeviceIdsAtStart = new Set(cli.getStoredDevicesForUser(cli.getUserId()!).map((d) => d.deviceId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +162,7 @@ export default class DeviceListener {
|
||||||
// devicesAtStart list to the devices that we see after the fetch.
|
// devicesAtStart list to the devices that we see after the fetch.
|
||||||
if (initialFetch) return;
|
if (initialFetch) return;
|
||||||
|
|
||||||
const myUserId = MatrixClientPeg.get().getUserId();
|
const myUserId = MatrixClientPeg.get().getUserId()!;
|
||||||
if (users.includes(myUserId)) this.ensureDeviceIdsAtStartPopulated();
|
if (users.includes(myUserId)) this.ensureDeviceIdsAtStartPopulated();
|
||||||
|
|
||||||
// No need to do a recheck here: we just need to get a snapshot of our devices
|
// No need to do a recheck here: we just need to get a snapshot of our devices
|
||||||
|
@ -170,7 +170,7 @@ export default class DeviceListener {
|
||||||
};
|
};
|
||||||
|
|
||||||
private onDevicesUpdated = (users: string[]): void => {
|
private onDevicesUpdated = (users: string[]): void => {
|
||||||
if (!users.includes(MatrixClientPeg.get().getUserId())) return;
|
if (!users.includes(MatrixClientPeg.get().getUserId()!)) return;
|
||||||
this.recheck();
|
this.recheck();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -225,7 +225,7 @@ export default class DeviceListener {
|
||||||
|
|
||||||
// The server doesn't tell us when key backup is set up, so we poll
|
// The server doesn't tell us when key backup is set up, so we poll
|
||||||
// & cache the result
|
// & cache the result
|
||||||
private async getKeyBackupInfo(): Promise<IKeyBackupInfo> {
|
private async getKeyBackupInfo(): Promise<IKeyBackupInfo | null> {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
|
if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
|
||||||
this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
||||||
|
@ -265,10 +265,10 @@ export default class DeviceListener {
|
||||||
this.checkKeyBackupStatus();
|
this.checkKeyBackupStatus();
|
||||||
} else if (this.shouldShowSetupEncryptionToast()) {
|
} else if (this.shouldShowSetupEncryptionToast()) {
|
||||||
// make sure our keys are finished downloading
|
// make sure our keys are finished downloading
|
||||||
await cli.downloadKeys([cli.getUserId()]);
|
await cli.downloadKeys([cli.getUserId()!]);
|
||||||
// cross signing isn't enabled - nag to enable it
|
// cross signing isn't enabled - nag to enable it
|
||||||
// There are 3 different toasts for:
|
// There are 3 different toasts for:
|
||||||
if (!cli.getCrossSigningId() && cli.getStoredCrossSigningForUser(cli.getUserId())) {
|
if (!cli.getCrossSigningId() && cli.getStoredCrossSigningForUser(cli.getUserId()!)) {
|
||||||
// Cross-signing on account but this device doesn't trust the master key (verify this session)
|
// Cross-signing on account but this device doesn't trust the master key (verify this session)
|
||||||
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
|
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
|
||||||
this.checkKeyBackupStatus();
|
this.checkKeyBackupStatus();
|
||||||
|
@ -310,13 +310,13 @@ export default class DeviceListener {
|
||||||
// as long as cross-signing isn't ready,
|
// as long as cross-signing isn't ready,
|
||||||
// you can't see or dismiss any device toasts
|
// you can't see or dismiss any device toasts
|
||||||
if (crossSigningReady) {
|
if (crossSigningReady) {
|
||||||
const devices = cli.getStoredDevicesForUser(cli.getUserId());
|
const devices = cli.getStoredDevicesForUser(cli.getUserId()!);
|
||||||
for (const device of devices) {
|
for (const device of devices) {
|
||||||
if (device.deviceId === cli.deviceId) continue;
|
if (device.deviceId === cli.deviceId) continue;
|
||||||
|
|
||||||
const deviceTrust = await cli.checkDeviceTrust(cli.getUserId()!, device.deviceId!);
|
const deviceTrust = await cli.checkDeviceTrust(cli.getUserId()!, device.deviceId!);
|
||||||
if (!deviceTrust.isCrossSigningVerified() && !this.dismissed.has(device.deviceId)) {
|
if (!deviceTrust.isCrossSigningVerified() && !this.dismissed.has(device.deviceId)) {
|
||||||
if (this.ourDeviceIdsAtStart.has(device.deviceId)) {
|
if (this.ourDeviceIdsAtStart?.has(device.deviceId)) {
|
||||||
oldUnverifiedDeviceIds.add(device.deviceId);
|
oldUnverifiedDeviceIds.add(device.deviceId);
|
||||||
} else {
|
} else {
|
||||||
newUnverifiedDeviceIds.add(device.deviceId);
|
newUnverifiedDeviceIds.add(device.deviceId);
|
||||||
|
|
|
@ -67,7 +67,7 @@ export default class IdentityAuthClient {
|
||||||
window.localStorage.setItem("mx_is_access_token", this.accessToken);
|
window.localStorage.setItem("mx_is_access_token", this.accessToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readToken(): string {
|
private readToken(): string | null {
|
||||||
if (this.tempClient) return null; // temporary client: ignore
|
if (this.tempClient) return null; // temporary client: ignore
|
||||||
return window.localStorage.getItem("mx_is_access_token");
|
return window.localStorage.getItem("mx_is_access_token");
|
||||||
}
|
}
|
||||||
|
@ -77,13 +77,13 @@ export default class IdentityAuthClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a promise that resolves to the access_token string from the IS
|
// Returns a promise that resolves to the access_token string from the IS
|
||||||
public async getAccessToken({ check = true } = {}): Promise<string> {
|
public async getAccessToken({ check = true } = {}): Promise<string | null> {
|
||||||
if (!this.authEnabled) {
|
if (!this.authEnabled) {
|
||||||
// The current IS doesn't support authentication
|
// The current IS doesn't support authentication
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let token = this.accessToken;
|
let token: string | null = this.accessToken;
|
||||||
if (!token) {
|
if (!token) {
|
||||||
token = this.readToken();
|
token = this.readToken();
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
* Gets the user-facing room associated with a call (call.roomId may be the call "virtual room"
|
* Gets the user-facing room associated with a call (call.roomId may be the call "virtual room"
|
||||||
* if a voip_mxid_translate_pattern is set in the config)
|
* if a voip_mxid_translate_pattern is set in the config)
|
||||||
*/
|
*/
|
||||||
public roomIdForCall(call: MatrixCall): string {
|
public roomIdForCall(call?: MatrixCall): string | null {
|
||||||
if (!call) return null;
|
if (!call) return null;
|
||||||
|
|
||||||
// check asserted identity: if we're not obeying asserted identity,
|
// check asserted identity: if we're not obeying asserted identity,
|
||||||
|
@ -194,7 +194,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(call.roomId) || call.roomId;
|
return VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(call.roomId) ?? call.roomId ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public start(): void {
|
public start(): void {
|
||||||
|
@ -282,7 +282,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public unSilenceCall(callId: string): void {
|
public unSilenceCall(callId: string): void {
|
||||||
if (this.isForcedSilent) return;
|
if (this.isForcedSilent()) return;
|
||||||
this.silencedCalls.delete(callId);
|
this.silencedCalls.delete(callId);
|
||||||
this.emit(LegacyCallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
|
this.emit(LegacyCallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
|
||||||
this.play(AudioID.Ring);
|
this.play(AudioID.Ring);
|
||||||
|
@ -341,14 +341,14 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private shouldObeyAssertedfIdentity(): boolean {
|
private shouldObeyAssertedfIdentity(): boolean {
|
||||||
return SdkConfig.getObject("voip")?.get("obey_asserted_identity");
|
return !!SdkConfig.getObject("voip")?.get("obey_asserted_identity");
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSupportsPstnProtocol(): boolean {
|
public getSupportsPstnProtocol(): boolean | null {
|
||||||
return this.supportsPstnProtocol;
|
return this.supportsPstnProtocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSupportsVirtualRooms(): boolean {
|
public getSupportsVirtualRooms(): boolean | null {
|
||||||
return this.supportsSipNativeVirtual;
|
return this.supportsSipNativeVirtual;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,7 +414,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
cli.prepareToEncrypt(cli.getRoom(call.roomId));
|
cli.prepareToEncrypt(cli.getRoom(call.roomId));
|
||||||
};
|
};
|
||||||
|
|
||||||
public getCallById(callId: string): MatrixCall {
|
public getCallById(callId: string): MatrixCall | null {
|
||||||
for (const call of this.calls.values()) {
|
for (const call of this.calls.values()) {
|
||||||
if (call.callId === callId) return call;
|
if (call.callId === callId) return call;
|
||||||
}
|
}
|
||||||
|
@ -435,7 +435,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAllActiveCalls(): MatrixCall[] {
|
public getAllActiveCalls(): MatrixCall[] {
|
||||||
const activeCalls = [];
|
const activeCalls: MatrixCall[] = [];
|
||||||
|
|
||||||
for (const call of this.calls.values()) {
|
for (const call of this.calls.values()) {
|
||||||
if (call.state !== CallState.Ended && call.state !== CallState.Ringing) {
|
if (call.state !== CallState.Ended && call.state !== CallState.Ringing) {
|
||||||
|
@ -446,7 +446,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAllActiveCallsNotInRoom(notInThisRoomId: string): MatrixCall[] {
|
public getAllActiveCallsNotInRoom(notInThisRoomId: string): MatrixCall[] {
|
||||||
const callsNotInThatRoom = [];
|
const callsNotInThatRoom: MatrixCall[] = [];
|
||||||
|
|
||||||
for (const [roomId, call] of this.calls.entries()) {
|
for (const [roomId, call] of this.calls.entries()) {
|
||||||
if (roomId !== notInThisRoomId && call.state !== CallState.Ended) {
|
if (roomId !== notInThisRoomId && call.state !== CallState.Ended) {
|
||||||
|
@ -547,7 +547,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
const mappedRoomId = this.roomIdForCall(call);
|
const mappedRoomId = this.roomIdForCall(call);
|
||||||
|
|
||||||
const callForThisRoom = this.getCallForRoom(mappedRoomId);
|
const callForThisRoom = this.getCallForRoom(mappedRoomId);
|
||||||
return callForThisRoom && call.callId === callForThisRoom.callId;
|
return !!callForThisRoom && call.callId === callForThisRoom.callId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private setCallListeners(call: MatrixCall): void {
|
private setCallListeners(call: MatrixCall): void {
|
||||||
|
@ -610,7 +610,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newAssertedIdentity = call.getRemoteAssertedIdentity().id;
|
const newAssertedIdentity = call.getRemoteAssertedIdentity()?.id;
|
||||||
let newNativeAssertedIdentity = newAssertedIdentity;
|
let newNativeAssertedIdentity = newAssertedIdentity;
|
||||||
if (newAssertedIdentity) {
|
if (newAssertedIdentity) {
|
||||||
const response = await this.sipNativeLookup(newAssertedIdentity);
|
const response = await this.sipNativeLookup(newAssertedIdentity);
|
||||||
|
@ -642,7 +642,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private onCallStateChanged = (newState: CallState, oldState: CallState, call: MatrixCall): void => {
|
private onCallStateChanged = (newState: CallState, oldState: CallState | null, call: MatrixCall): void => {
|
||||||
if (!this.matchesCallForThisRoom(call)) return;
|
if (!this.matchesCallForThisRoom(call)) return;
|
||||||
|
|
||||||
const mappedRoomId = this.roomIdForCall(call);
|
const mappedRoomId = this.roomIdForCall(call);
|
||||||
|
@ -830,7 +830,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
"<code>turn.matrix.org</code>, but this will not be as reliable, and " +
|
"<code>turn.matrix.org</code>, but this will not be as reliable, and " +
|
||||||
"it will share your IP address with that server. You can also manage " +
|
"it will share your IP address with that server. You can also manage " +
|
||||||
"this in Settings.",
|
"this in Settings.",
|
||||||
null,
|
undefined,
|
||||||
{ code },
|
{ code },
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
@ -843,7 +843,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
cli.setFallbackICEServerAllowed(allow);
|
cli.setFallbackICEServerAllowed(allow);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
null,
|
undefined,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -882,7 +882,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
},
|
},
|
||||||
null,
|
undefined,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
29
src/Login.ts
29
src/Login.ts
|
@ -29,20 +29,17 @@ interface ILoginOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Login {
|
export default class Login {
|
||||||
private hsUrl: string;
|
private flows: Array<LoginFlow> = [];
|
||||||
private isUrl: string;
|
private readonly defaultDeviceDisplayName?: string;
|
||||||
private fallbackHsUrl: string;
|
private tempClient: MatrixClient | null = null; // memoize
|
||||||
private flows: Array<LoginFlow>;
|
|
||||||
private defaultDeviceDisplayName: string;
|
|
||||||
private tempClient: MatrixClient;
|
|
||||||
|
|
||||||
public constructor(hsUrl: string, isUrl: string, fallbackHsUrl?: string, opts?: ILoginOptions) {
|
public constructor(
|
||||||
this.hsUrl = hsUrl;
|
private hsUrl: string,
|
||||||
this.isUrl = isUrl;
|
private isUrl: string,
|
||||||
this.fallbackHsUrl = fallbackHsUrl;
|
private fallbackHsUrl: string | null,
|
||||||
this.flows = [];
|
opts: ILoginOptions,
|
||||||
|
) {
|
||||||
this.defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
|
this.defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
|
||||||
this.tempClient = null; // memoize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getHomeserverUrl(): string {
|
public getHomeserverUrl(): string {
|
||||||
|
@ -96,7 +93,7 @@ export default class Login {
|
||||||
phoneNumber: string | undefined,
|
phoneNumber: string | undefined,
|
||||||
password: string,
|
password: string,
|
||||||
): Promise<IMatrixClientCreds> {
|
): Promise<IMatrixClientCreds> {
|
||||||
const isEmail = username?.indexOf("@") > 0;
|
const isEmail = !!username && username.indexOf("@") > 0;
|
||||||
|
|
||||||
let identifier;
|
let identifier;
|
||||||
if (phoneCountry && phoneNumber) {
|
if (phoneCountry && phoneNumber) {
|
||||||
|
@ -127,7 +124,7 @@ export default class Login {
|
||||||
};
|
};
|
||||||
|
|
||||||
const tryFallbackHs = (originalError: Error): Promise<IMatrixClientCreds> => {
|
const tryFallbackHs = (originalError: Error): Promise<IMatrixClientCreds> => {
|
||||||
return sendLoginRequest(this.fallbackHsUrl, this.isUrl, "m.login.password", loginParams).catch(
|
return sendLoginRequest(this.fallbackHsUrl!, this.isUrl, "m.login.password", loginParams).catch(
|
||||||
(fallbackError) => {
|
(fallbackError) => {
|
||||||
logger.log("fallback HS login failed", fallbackError);
|
logger.log("fallback HS login failed", fallbackError);
|
||||||
// throw the original error
|
// throw the original error
|
||||||
|
@ -136,13 +133,13 @@ export default class Login {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let originalLoginError = null;
|
let originalLoginError: Error | null = null;
|
||||||
return sendLoginRequest(this.hsUrl, this.isUrl, "m.login.password", loginParams)
|
return sendLoginRequest(this.hsUrl, this.isUrl, "m.login.password", loginParams)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
originalLoginError = error;
|
originalLoginError = error;
|
||||||
if (error.httpStatus === 403) {
|
if (error.httpStatus === 403) {
|
||||||
if (this.fallbackHsUrl) {
|
if (this.fallbackHsUrl) {
|
||||||
return tryFallbackHs(originalLoginError);
|
return tryFallbackHs(originalLoginError!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw originalLoginError;
|
throw originalLoginError;
|
||||||
|
|
|
@ -256,7 +256,7 @@ export default class ScalarAuthClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getScalarInterfaceUrlForRoom(room: Room, screen: string, id: string): string {
|
public getScalarInterfaceUrlForRoom(room: Room, screen?: string, id?: string): string {
|
||||||
const roomId = room.roomId;
|
const roomId = room.roomId;
|
||||||
const roomName = room.name;
|
const roomName = room.name;
|
||||||
let url = this.uiUrl;
|
let url = this.uiUrl;
|
||||||
|
|
|
@ -102,7 +102,7 @@ async function getSecretStorageKey({
|
||||||
}): Promise<[string, Uint8Array]> {
|
}): Promise<[string, Uint8Array]> {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
let keyId = await cli.getDefaultSecretStorageKeyId();
|
let keyId = await cli.getDefaultSecretStorageKeyId();
|
||||||
let keyInfo: ISecretStorageKeyInfo;
|
let keyInfo!: ISecretStorageKeyInfo;
|
||||||
if (keyId) {
|
if (keyId) {
|
||||||
// use the default SSSS key if set
|
// use the default SSSS key if set
|
||||||
keyInfo = keyInfos[keyId];
|
keyInfo = keyInfos[keyId];
|
||||||
|
|
|
@ -82,6 +82,7 @@ const singleMxcUpload = async (): Promise<string | null> => {
|
||||||
fileSelector.setAttribute("type", "file");
|
fileSelector.setAttribute("type", "file");
|
||||||
fileSelector.onchange = (ev: HTMLInputEvent) => {
|
fileSelector.onchange = (ev: HTMLInputEvent) => {
|
||||||
const file = ev.target.files?.[0];
|
const file = ev.target.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
Modal.createDialog(UploadConfirmDialog, {
|
Modal.createDialog(UploadConfirmDialog, {
|
||||||
file,
|
file,
|
||||||
|
@ -304,7 +305,7 @@ export const Commands = [
|
||||||
if (args) {
|
if (args) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const room = cli.getRoom(roomId);
|
const room = cli.getRoom(roomId);
|
||||||
if (!room.currentState.mayClientSendStateEvent("m.room.tombstone", cli)) {
|
if (!room?.currentState.mayClientSendStateEvent("m.room.tombstone", cli)) {
|
||||||
return reject(
|
return reject(
|
||||||
newTranslatableError("You do not have the required permissions to use this command."),
|
newTranslatableError("You do not have the required permissions to use this command."),
|
||||||
);
|
);
|
||||||
|
@ -313,7 +314,7 @@ export const Commands = [
|
||||||
const { finished } = Modal.createDialog(
|
const { finished } = Modal.createDialog(
|
||||||
RoomUpgradeWarningDialog,
|
RoomUpgradeWarningDialog,
|
||||||
{ roomId: roomId, targetVersion: args },
|
{ roomId: roomId, targetVersion: args },
|
||||||
/*className=*/ null,
|
/*className=*/ undefined,
|
||||||
/*isPriority=*/ false,
|
/*isPriority=*/ false,
|
||||||
/*isStatic=*/ true,
|
/*isStatic=*/ true,
|
||||||
);
|
);
|
||||||
|
@ -1199,7 +1200,7 @@ export const Commands = [
|
||||||
description: _td("Switches to this room's virtual room, if it has one"),
|
description: _td("Switches to this room's virtual room, if it has one"),
|
||||||
category: CommandCategories.advanced,
|
category: CommandCategories.advanced,
|
||||||
isEnabled(): boolean {
|
isEnabled(): boolean {
|
||||||
return LegacyCallHandler.instance.getSupportsVirtualRooms() && !isCurrentLocalRoom();
|
return !!LegacyCallHandler.instance.getSupportsVirtualRooms() && !isCurrentLocalRoom();
|
||||||
},
|
},
|
||||||
runFn: (roomId) => {
|
runFn: (roomId) => {
|
||||||
return success(
|
return success(
|
||||||
|
@ -1389,7 +1390,7 @@ export function parseCommandString(input: string): { cmd?: string; args?: string
|
||||||
|
|
||||||
const bits = input.match(/^(\S+?)(?:[ \n]+((.|\n)*))?$/);
|
const bits = input.match(/^(\S+?)(?:[ \n]+((.|\n)*))?$/);
|
||||||
let cmd: string;
|
let cmd: string;
|
||||||
let args: string;
|
let args: string | undefined;
|
||||||
if (bits) {
|
if (bits) {
|
||||||
cmd = bits[1].substring(1).toLowerCase();
|
cmd = bits[1].substring(1).toLowerCase();
|
||||||
args = bits[2];
|
args = bits[2];
|
||||||
|
@ -1414,7 +1415,7 @@ interface ICmd {
|
||||||
export function getCommand(input: string): ICmd {
|
export function getCommand(input: string): ICmd {
|
||||||
const { cmd, args } = parseCommandString(input);
|
const { cmd, args } = parseCommandString(input);
|
||||||
|
|
||||||
if (CommandMap.has(cmd) && CommandMap.get(cmd)!.isEnabled()) {
|
if (cmd && CommandMap.has(cmd) && CommandMap.get(cmd)!.isEnabled()) {
|
||||||
return {
|
return {
|
||||||
cmd: CommandMap.get(cmd),
|
cmd: CommandMap.get(cmd),
|
||||||
args,
|
args,
|
||||||
|
|
|
@ -120,20 +120,20 @@ function textForMemberEvent(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents
|
||||||
// We're taking the display namke directly from the event content here so we need
|
// We're taking the display namke directly from the event content here so we need
|
||||||
// to strip direction override chars which the js-sdk would normally do when
|
// to strip direction override chars which the js-sdk would normally do when
|
||||||
// calculating the display name
|
// calculating the display name
|
||||||
oldDisplayName: removeDirectionOverrideChars(prevContent.displayname),
|
oldDisplayName: removeDirectionOverrideChars(prevContent.displayname!),
|
||||||
displayName: removeDirectionOverrideChars(content.displayname),
|
displayName: removeDirectionOverrideChars(content.displayname!),
|
||||||
});
|
});
|
||||||
} else if (!prevContent.displayname && content.displayname) {
|
} else if (!prevContent.displayname && content.displayname) {
|
||||||
return () =>
|
return () =>
|
||||||
_t("%(senderName)s set their display name to %(displayName)s", {
|
_t("%(senderName)s set their display name to %(displayName)s", {
|
||||||
senderName: ev.getSender(),
|
senderName: ev.getSender(),
|
||||||
displayName: removeDirectionOverrideChars(content.displayname),
|
displayName: removeDirectionOverrideChars(content.displayname!),
|
||||||
});
|
});
|
||||||
} else if (prevContent.displayname && !content.displayname) {
|
} else if (prevContent.displayname && !content.displayname) {
|
||||||
return () =>
|
return () =>
|
||||||
_t("%(senderName)s removed their display name (%(oldDisplayName)s)", {
|
_t("%(senderName)s removed their display name (%(oldDisplayName)s)", {
|
||||||
senderName,
|
senderName,
|
||||||
oldDisplayName: removeDirectionOverrideChars(prevContent.displayname),
|
oldDisplayName: removeDirectionOverrideChars(prevContent.displayname!),
|
||||||
});
|
});
|
||||||
} else if (prevContent.avatar_url && !content.avatar_url) {
|
} else if (prevContent.avatar_url && !content.avatar_url) {
|
||||||
return () => _t("%(senderName)s removed their profile picture", { senderName });
|
return () => _t("%(senderName)s removed their profile picture", { senderName });
|
||||||
|
@ -545,7 +545,7 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): (() => Rende
|
||||||
if (newlyPinned.length === 1 && newlyUnpinned.length === 0) {
|
if (newlyPinned.length === 1 && newlyUnpinned.length === 0) {
|
||||||
// A single message was pinned, include a link to that message.
|
// A single message was pinned, include a link to that message.
|
||||||
if (allowJSX) {
|
if (allowJSX) {
|
||||||
const messageId = newlyPinned.pop();
|
const messageId = newlyPinned.pop()!;
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<span>
|
<span>
|
||||||
|
@ -578,7 +578,7 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): (() => Rende
|
||||||
if (newlyUnpinned.length === 1 && newlyPinned.length === 0) {
|
if (newlyUnpinned.length === 1 && newlyPinned.length === 0) {
|
||||||
// A single message was unpinned, include a link to that message.
|
// A single message was unpinned, include a link to that message.
|
||||||
if (allowJSX) {
|
if (allowJSX) {
|
||||||
const messageId = newlyUnpinned.pop();
|
const messageId = newlyUnpinned.pop()!;
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<span>
|
<span>
|
||||||
|
|
|
@ -105,7 +105,7 @@ export interface IProps extends MenuProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
contextMenuElem: HTMLDivElement;
|
contextMenuElem?: HTMLDivElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic ContextMenu Portal wrapper
|
// Generic ContextMenu Portal wrapper
|
||||||
|
@ -122,9 +122,7 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||||
public constructor(props: IProps) {
|
public constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {};
|
||||||
contextMenuElem: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
// persist what had focus when we got initialized so we can return it after
|
// persist what had focus when we got initialized so we can return it after
|
||||||
this.initialFocus = document.activeElement as HTMLElement;
|
this.initialFocus = document.activeElement as HTMLElement;
|
||||||
|
@ -181,7 +179,7 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||||
button: 0, // Left
|
button: 0, // Left
|
||||||
relatedTarget: null,
|
relatedTarget: null,
|
||||||
});
|
});
|
||||||
document.elementFromPoint(x, y).dispatchEvent(clickEvent);
|
document.elementFromPoint(x, y)?.dispatchEvent(clickEvent);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -239,7 +237,7 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||||
// MessageActionBar), we should close any ContextMenu that is open.
|
// MessageActionBar), we should close any ContextMenu that is open.
|
||||||
KeyBindingAction.ArrowLeft,
|
KeyBindingAction.ArrowLeft,
|
||||||
KeyBindingAction.ArrowRight,
|
KeyBindingAction.ArrowRight,
|
||||||
].includes(action)
|
].includes(action!)
|
||||||
) {
|
) {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
}
|
}
|
||||||
|
@ -312,12 +310,12 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||||
position.top = Math.min(position.top, maxTop);
|
position.top = Math.min(position.top, maxTop);
|
||||||
// Adjust the chevron if necessary
|
// Adjust the chevron if necessary
|
||||||
if (chevronOffset.top !== undefined) {
|
if (chevronOffset.top !== undefined) {
|
||||||
chevronOffset.top = propsChevronOffset + top - position.top;
|
chevronOffset.top = propsChevronOffset! + top! - position.top;
|
||||||
}
|
}
|
||||||
} else if (position.bottom !== undefined) {
|
} else if (position.bottom !== undefined) {
|
||||||
position.bottom = Math.min(position.bottom, windowHeight - contextMenuRect.height - WINDOW_PADDING);
|
position.bottom = Math.min(position.bottom, windowHeight - contextMenuRect.height - WINDOW_PADDING);
|
||||||
if (chevronOffset.top !== undefined) {
|
if (chevronOffset.top !== undefined) {
|
||||||
chevronOffset.top = propsChevronOffset + position.bottom - bottom;
|
chevronOffset.top = propsChevronOffset! + position.bottom - bottom!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (position.left !== undefined) {
|
if (position.left !== undefined) {
|
||||||
|
@ -327,12 +325,12 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
position.left = Math.min(position.left, maxLeft);
|
position.left = Math.min(position.left, maxLeft);
|
||||||
if (chevronOffset.left !== undefined) {
|
if (chevronOffset.left !== undefined) {
|
||||||
chevronOffset.left = propsChevronOffset + left - position.left;
|
chevronOffset.left = propsChevronOffset! + left! - position.left;
|
||||||
}
|
}
|
||||||
} else if (position.right !== undefined) {
|
} else if (position.right !== undefined) {
|
||||||
position.right = Math.min(position.right, windowWidth - contextMenuRect.width - WINDOW_PADDING);
|
position.right = Math.min(position.right, windowWidth - contextMenuRect.width - WINDOW_PADDING);
|
||||||
if (chevronOffset.left !== undefined) {
|
if (chevronOffset.left !== undefined) {
|
||||||
chevronOffset.left = propsChevronOffset + position.right - right;
|
chevronOffset.left = propsChevronOffset! + position.right - right!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -389,7 +387,7 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
const wrapperStyle: CSSProperties = {};
|
const wrapperStyle: CSSProperties = {};
|
||||||
if (!isNaN(Number(zIndex))) {
|
if (!isNaN(Number(zIndex))) {
|
||||||
menuStyle["zIndex"] = zIndex + 1;
|
menuStyle["zIndex"] = zIndex! + 1;
|
||||||
wrapperStyle["zIndex"] = zIndex;
|
wrapperStyle["zIndex"] = zIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,7 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
this.props.onRemove(this.props.member);
|
this.props.onRemove!(this.props.member);
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
|
@ -132,7 +132,7 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
|
||||||
|
|
||||||
interface IDMRoomTileProps {
|
interface IDMRoomTileProps {
|
||||||
member: Member;
|
member: Member;
|
||||||
lastActiveTs: number;
|
lastActiveTs?: number;
|
||||||
onToggle(member: Member): void;
|
onToggle(member: Member): void;
|
||||||
highlightWord: string;
|
highlightWord: string;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
|
@ -188,7 +188,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
let timestamp = null;
|
let timestamp: JSX.Element | undefined;
|
||||||
if (this.props.lastActiveTs) {
|
if (this.props.lastActiveTs) {
|
||||||
const humanTs = humanizeTime(this.props.lastActiveTs);
|
const humanTs = humanizeTime(this.props.lastActiveTs);
|
||||||
timestamp = <span className="mx_InviteDialog_tile--room_time">{humanTs}</span>;
|
timestamp = <span className="mx_InviteDialog_tile--room_time">{humanTs}</span>;
|
||||||
|
@ -201,7 +201,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
url={
|
url={
|
||||||
this.props.member.getMxcAvatarUrl()
|
this.props.member.getMxcAvatarUrl()
|
||||||
? mediaFromMxc(this.props.member.getMxcAvatarUrl()).getSquareThumbnailHttp(avatarSize)
|
? mediaFromMxc(this.props.member.getMxcAvatarUrl()!).getSquareThumbnailHttp(avatarSize)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
name={this.props.member.name}
|
name={this.props.member.name}
|
||||||
|
@ -211,7 +211,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
let checkmark = null;
|
let checkmark: JSX.Element | undefined;
|
||||||
if (this.props.isSelected) {
|
if (this.props.isSelected) {
|
||||||
// To reduce flickering we put the 'selected' room tile above the real avatar
|
// To reduce flickering we put the 'selected' room tile above the real avatar
|
||||||
checkmark = <div className="mx_InviteDialog_tile--room_selected" />;
|
checkmark = <div className="mx_InviteDialog_tile--room_selected" />;
|
||||||
|
@ -301,7 +301,7 @@ interface IInviteDialogState {
|
||||||
|
|
||||||
// These two flags are used for the 'Go' button to communicate what is going on.
|
// These two flags are used for the 'Go' button to communicate what is going on.
|
||||||
busy: boolean;
|
busy: boolean;
|
||||||
errorText: string;
|
errorText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class InviteDialog extends React.PureComponent<Props, IInviteDialogState> {
|
export default class InviteDialog extends React.PureComponent<Props, IInviteDialogState> {
|
||||||
|
@ -324,7 +324,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
throw new Error("When using KIND_CALL_TRANSFER a call is required for an InviteDialog");
|
throw new Error("When using KIND_CALL_TRANSFER a call is required for an InviteDialog");
|
||||||
}
|
}
|
||||||
|
|
||||||
const alreadyInvited = new Set([MatrixClientPeg.get().getUserId(), SdkConfig.get("welcome_user_id")]);
|
const alreadyInvited = new Set([MatrixClientPeg.get().getUserId()!, SdkConfig.get("welcome_user_id")]);
|
||||||
if (isRoomInvite(props)) {
|
if (isRoomInvite(props)) {
|
||||||
const room = MatrixClientPeg.get().getRoom(props.roomId);
|
const room = MatrixClientPeg.get().getRoom(props.roomId);
|
||||||
if (!room) throw new Error("Room ID given to InviteDialog does not look like a room");
|
if (!room) throw new Error("Room ID given to InviteDialog does not look like a room");
|
||||||
|
@ -351,7 +351,6 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
|
|
||||||
// These two flags are used for the 'Go' button to communicate what is going on.
|
// These two flags are used for the 'Go' button to communicate what is going on.
|
||||||
busy: false,
|
busy: false,
|
||||||
errorText: null,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,7 +385,11 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const recents = [];
|
const recents: {
|
||||||
|
userId: string;
|
||||||
|
user: RoomMember;
|
||||||
|
lastActive: number;
|
||||||
|
}[] = [];
|
||||||
for (const userId in rooms) {
|
for (const userId in rooms) {
|
||||||
// Filter out user IDs that are already in the room / should be excluded
|
// Filter out user IDs that are already in the room / should be excluded
|
||||||
if (excludedTargetIds.has(userId)) {
|
if (excludedTargetIds.has(userId)) {
|
||||||
|
@ -455,14 +458,16 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
// Check to see if there's anything to convert first
|
// Check to see if there's anything to convert first
|
||||||
if (!this.state.filterText || !this.state.filterText.includes("@")) return this.state.targets || [];
|
if (!this.state.filterText || !this.state.filterText.includes("@")) return this.state.targets || [];
|
||||||
|
|
||||||
let newMember: Member;
|
let newMember: Member | undefined;
|
||||||
if (this.state.filterText.startsWith("@")) {
|
if (this.state.filterText.startsWith("@")) {
|
||||||
// Assume mxid
|
// Assume mxid
|
||||||
newMember = new DirectoryMember({ user_id: this.state.filterText, display_name: null, avatar_url: null });
|
newMember = new DirectoryMember({ user_id: this.state.filterText });
|
||||||
} else if (SettingsStore.getValue(UIFeature.IdentityServer)) {
|
} else if (SettingsStore.getValue(UIFeature.IdentityServer)) {
|
||||||
// Assume email
|
// Assume email
|
||||||
newMember = new ThreepidMember(this.state.filterText);
|
newMember = new ThreepidMember(this.state.filterText);
|
||||||
}
|
}
|
||||||
|
if (!newMember) return this.state.targets;
|
||||||
|
|
||||||
const newTargets = [...(this.state.targets || []), newMember];
|
const newTargets = [...(this.state.targets || []), newMember];
|
||||||
this.setState({ targets: newTargets, filterText: "" });
|
this.setState({ targets: newTargets, filterText: "" });
|
||||||
return newTargets;
|
return newTargets;
|
||||||
|
@ -778,8 +783,8 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
...this.state.serverResultsMixin,
|
...this.state.serverResultsMixin,
|
||||||
...this.state.threepidResultsMixin,
|
...this.state.threepidResultsMixin,
|
||||||
];
|
];
|
||||||
const toAdd = [];
|
const toAdd: Member[] = [];
|
||||||
const failed = [];
|
const failed: string[] = [];
|
||||||
const potentialAddresses = text
|
const potentialAddresses = text
|
||||||
.split(/[\s,]+/)
|
.split(/[\s,]+/)
|
||||||
.map((p) => p.trim())
|
.map((p) => p.trim())
|
||||||
|
@ -803,13 +808,11 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const profile = await MatrixClientPeg.get().getProfileInfo(address);
|
const profile = await MatrixClientPeg.get().getProfileInfo(address);
|
||||||
const displayName = profile ? profile.displayname : null;
|
|
||||||
const avatarUrl = profile ? profile.avatar_url : null;
|
|
||||||
toAdd.push(
|
toAdd.push(
|
||||||
new DirectoryMember({
|
new DirectoryMember({
|
||||||
user_id: address,
|
user_id: address,
|
||||||
display_name: displayName,
|
display_name: profile?.displayname,
|
||||||
avatar_url: avatarUrl,
|
avatar_url: profile?.avatar_url,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -859,11 +862,11 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
private renderSection(kind: "recents" | "suggestions"): JSX.Element {
|
private renderSection(kind: "recents" | "suggestions"): ReactNode {
|
||||||
let sourceMembers = kind === "recents" ? this.state.recents : this.state.suggestions;
|
let sourceMembers = kind === "recents" ? this.state.recents : this.state.suggestions;
|
||||||
let showNum = kind === "recents" ? this.state.numRecentsShown : this.state.numSuggestionsShown;
|
let showNum = kind === "recents" ? this.state.numRecentsShown : this.state.numSuggestionsShown;
|
||||||
const showMoreFn = kind === "recents" ? this.showMoreRecents.bind(this) : this.showMoreSuggestions.bind(this);
|
const showMoreFn = kind === "recents" ? this.showMoreRecents.bind(this) : this.showMoreSuggestions.bind(this);
|
||||||
const lastActive = (m: Result): number | null => (kind === "recents" ? m.lastActive : null);
|
const lastActive = (m: Result): number | undefined => (kind === "recents" ? m.lastActive : undefined);
|
||||||
let sectionName = kind === "recents" ? _t("Recent Conversations") : _t("Suggestions");
|
let sectionName = kind === "recents" ? _t("Recent Conversations") : _t("Suggestions");
|
||||||
|
|
||||||
if (this.props.kind === KIND_INVITE) {
|
if (this.props.kind === KIND_INVITE) {
|
||||||
|
@ -924,7 +927,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
const toRender = sourceMembers.slice(0, showNum);
|
const toRender = sourceMembers.slice(0, showNum);
|
||||||
const hasMore = toRender.length < sourceMembers.length;
|
const hasMore = toRender.length < sourceMembers.length;
|
||||||
|
|
||||||
let showMore = null;
|
let showMore: JSX.Element | undefined;
|
||||||
if (hasMore) {
|
if (hasMore) {
|
||||||
showMore = (
|
showMore = (
|
||||||
<div className="mx_InviteDialog_section_showMore">
|
<div className="mx_InviteDialog_section_showMore">
|
||||||
|
@ -960,7 +963,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
this.state.targets.length === 0 &&
|
this.state.targets.length === 0 &&
|
||||||
this.state.filterText.length === 0;
|
this.state.filterText.length === 0;
|
||||||
const targets = this.state.targets.map((t) => (
|
const targets = this.state.targets.map((t) => (
|
||||||
<DMUserTile member={t} onRemove={!this.state.busy && this.removeMember} key={t.userId} />
|
<DMUserTile member={t} onRemove={this.state.busy ? undefined : this.removeMember} key={t.userId} />
|
||||||
));
|
));
|
||||||
const input = (
|
const input = (
|
||||||
<input
|
<input
|
||||||
|
@ -973,7 +976,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
disabled={this.state.busy || (this.props.kind == KIND_CALL_TRANSFER && this.state.targets.length > 0)}
|
disabled={this.state.busy || (this.props.kind == KIND_CALL_TRANSFER && this.state.targets.length > 0)}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder={hasPlaceholder ? _t("Search") : null}
|
placeholder={hasPlaceholder ? _t("Search") : undefined}
|
||||||
data-testid="invite-dialog-input"
|
data-testid="invite-dialog-input"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -985,7 +988,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderIdentityServerWarning(): JSX.Element {
|
private renderIdentityServerWarning(): ReactNode {
|
||||||
if (
|
if (
|
||||||
!this.state.tryingIdentityServer ||
|
!this.state.tryingIdentityServer ||
|
||||||
this.state.canUseIdentityServer ||
|
this.state.canUseIdentityServer ||
|
||||||
|
@ -1080,7 +1083,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
selectText(e.currentTarget);
|
selectText(e.currentTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get screenName(): ScreenName {
|
private get screenName(): ScreenName | undefined {
|
||||||
switch (this.props.kind) {
|
switch (this.props.kind) {
|
||||||
case KIND_DM:
|
case KIND_DM:
|
||||||
return "StartChat";
|
return "StartChat";
|
||||||
|
@ -1088,7 +1091,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
let spinner = null;
|
let spinner: JSX.Element | undefined;
|
||||||
if (this.state.busy) {
|
if (this.state.busy) {
|
||||||
spinner = <Spinner w={20} h={20} />;
|
spinner = <Spinner w={20} h={20} />;
|
||||||
}
|
}
|
||||||
|
@ -1108,7 +1111,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
this.state.targets.length > 0 || (this.state.filterText && this.state.filterText.includes("@"));
|
this.state.targets.length > 0 || (this.state.filterText && this.state.filterText.includes("@"));
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const userId = cli.getUserId();
|
const userId = cli.getUserId()!;
|
||||||
if (this.props.kind === KIND_DM) {
|
if (this.props.kind === KIND_DM) {
|
||||||
title = _t("Direct Messages");
|
title = _t("Direct Messages");
|
||||||
|
|
||||||
|
@ -1150,11 +1153,11 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
<p>{_t("If you can't see who you're looking for, send them your invite link below.")}</p>
|
<p>{_t("If you can't see who you're looking for, send them your invite link below.")}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
const link = makeUserPermalink(MatrixClientPeg.get().getUserId());
|
const link = makeUserPermalink(MatrixClientPeg.get().getUserId()!);
|
||||||
footer = (
|
footer = (
|
||||||
<div className="mx_InviteDialog_footer">
|
<div className="mx_InviteDialog_footer">
|
||||||
<h3>{_t("Or send invite link")}</h3>
|
<h3>{_t("Or send invite link")}</h3>
|
||||||
<CopyableText getTextToCopy={() => makeUserPermalink(MatrixClientPeg.get().getUserId())}>
|
<CopyableText getTextToCopy={() => makeUserPermalink(MatrixClientPeg.get().getUserId()!)}>
|
||||||
<a href={link} onClick={this.onLinkClick}>
|
<a href={link} onClick={this.onLinkClick}>
|
||||||
{link}
|
{link}
|
||||||
</a>
|
</a>
|
||||||
|
@ -1296,7 +1299,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
|
|
||||||
let dialogContent;
|
let dialogContent;
|
||||||
if (this.props.kind === KIND_CALL_TRANSFER) {
|
if (this.props.kind === KIND_CALL_TRANSFER) {
|
||||||
const tabs = [];
|
const tabs: Tab[] = [];
|
||||||
tabs.push(
|
tabs.push(
|
||||||
new Tab(TabId.UserDirectory, _td("User Directory"), "mx_InviteDialog_userDirectoryIcon", usersSection),
|
new Tab(TabId.UserDirectory, _td("User Directory"), "mx_InviteDialog_userDirectoryIcon", usersSection),
|
||||||
);
|
);
|
||||||
|
|
|
@ -179,7 +179,7 @@ const toPublicRoomResult = (publicRoom: IPublicRoomsChunkRoom): IPublicRoomResul
|
||||||
publicRoom.name?.toLowerCase(),
|
publicRoom.name?.toLowerCase(),
|
||||||
sanitizeHtml(publicRoom.topic?.toLowerCase() ?? "", { allowedTags: [] }),
|
sanitizeHtml(publicRoom.topic?.toLowerCase() ?? "", { allowedTags: [] }),
|
||||||
...(publicRoom.aliases?.map((it) => it.toLowerCase()) || []),
|
...(publicRoom.aliases?.map((it) => it.toLowerCase()) || []),
|
||||||
].filter(Boolean),
|
].filter(Boolean) as string[],
|
||||||
});
|
});
|
||||||
|
|
||||||
const toRoomResult = (room: Room): IRoomResult => {
|
const toRoomResult = (room: Room): IRoomResult => {
|
||||||
|
@ -310,7 +310,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
}, [cli]);
|
}, [cli]);
|
||||||
const msc3946ProcessDynamicPredecessor = useFeatureEnabled("feature_dynamic_room_predecessors");
|
const msc3946ProcessDynamicPredecessor = useFeatureEnabled("feature_dynamic_room_predecessors");
|
||||||
|
|
||||||
const ownInviteLink = makeUserPermalink(cli.getUserId());
|
const ownInviteLink = makeUserPermalink(cli.getUserId()!);
|
||||||
const [inviteLinkCopied, setInviteLinkCopied] = useState<boolean>(false);
|
const [inviteLinkCopied, setInviteLinkCopied] = useState<boolean>(false);
|
||||||
const trimmedQuery = useMemo(() => query.trim(), [query]);
|
const trimmedQuery = useMemo(() => query.trim(), [query]);
|
||||||
|
|
||||||
|
@ -465,7 +465,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
useWebSearchMetrics(numResults, query.length, true);
|
useWebSearchMetrics(numResults, query.length, true);
|
||||||
|
|
||||||
const activeSpace = SpaceStore.instance.activeSpaceRoom;
|
const activeSpace = SpaceStore.instance.activeSpaceRoom;
|
||||||
const [spaceResults, spaceResultsLoading] = useSpaceResults(activeSpace, query);
|
const [spaceResults, spaceResultsLoading] = useSpaceResults(activeSpace ?? undefined, query);
|
||||||
|
|
||||||
const setQuery = (e: ChangeEvent<HTMLInputElement>): void => {
|
const setQuery = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
const newQuery = e.currentTarget.value;
|
const newQuery = e.currentTarget.value;
|
||||||
|
@ -473,7 +473,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
let ref: Ref;
|
let ref: Ref | undefined;
|
||||||
if (rovingContext.state.refs) {
|
if (rovingContext.state.refs) {
|
||||||
ref = rovingContext.state.refs[0];
|
ref = rovingContext.state.refs[0];
|
||||||
}
|
}
|
||||||
|
@ -521,7 +521,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
onFinished();
|
onFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
let otherSearchesSection: JSX.Element;
|
let otherSearchesSection: JSX.Element | undefined;
|
||||||
if (trimmedQuery || filter !== Filter.PublicRooms) {
|
if (trimmedQuery || filter !== Filter.PublicRooms) {
|
||||||
otherSearchesSection = (
|
otherSearchesSection = (
|
||||||
<div
|
<div
|
||||||
|
@ -693,7 +693,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let peopleSection: JSX.Element;
|
let peopleSection: JSX.Element | undefined;
|
||||||
if (results[Section.People].length) {
|
if (results[Section.People].length) {
|
||||||
peopleSection = (
|
peopleSection = (
|
||||||
<div
|
<div
|
||||||
|
@ -707,7 +707,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let suggestionsSection: JSX.Element;
|
let suggestionsSection: JSX.Element | undefined;
|
||||||
if (results[Section.Suggestions].length && filter === Filter.People) {
|
if (results[Section.Suggestions].length && filter === Filter.People) {
|
||||||
suggestionsSection = (
|
suggestionsSection = (
|
||||||
<div
|
<div
|
||||||
|
@ -721,7 +721,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let roomsSection: JSX.Element;
|
let roomsSection: JSX.Element | undefined;
|
||||||
if (results[Section.Rooms].length) {
|
if (results[Section.Rooms].length) {
|
||||||
roomsSection = (
|
roomsSection = (
|
||||||
<div
|
<div
|
||||||
|
@ -735,7 +735,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let spacesSection: JSX.Element;
|
let spacesSection: JSX.Element | undefined;
|
||||||
if (results[Section.Spaces].length) {
|
if (results[Section.Spaces].length) {
|
||||||
spacesSection = (
|
spacesSection = (
|
||||||
<div
|
<div
|
||||||
|
@ -749,7 +749,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let publicRoomsSection: JSX.Element;
|
let publicRoomsSection: JSX.Element | undefined;
|
||||||
if (filter === Filter.PublicRooms) {
|
if (filter === Filter.PublicRooms) {
|
||||||
publicRoomsSection = (
|
publicRoomsSection = (
|
||||||
<div
|
<div
|
||||||
|
@ -791,7 +791,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let spaceRoomsSection: JSX.Element;
|
let spaceRoomsSection: JSX.Element | undefined;
|
||||||
if (spaceResults.length && activeSpace && filter === null) {
|
if (spaceResults.length && activeSpace && filter === null) {
|
||||||
spaceRoomsSection = (
|
spaceRoomsSection = (
|
||||||
<div
|
<div
|
||||||
|
@ -836,7 +836,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let joinRoomSection: JSX.Element;
|
let joinRoomSection: JSX.Element | undefined;
|
||||||
if (
|
if (
|
||||||
trimmedQuery.startsWith("#") &&
|
trimmedQuery.startsWith("#") &&
|
||||||
trimmedQuery.includes(":") &&
|
trimmedQuery.includes(":") &&
|
||||||
|
@ -868,7 +868,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let hiddenResultsSection: JSX.Element;
|
let hiddenResultsSection: JSX.Element | undefined;
|
||||||
if (filter === Filter.People) {
|
if (filter === Filter.People) {
|
||||||
hiddenResultsSection = (
|
hiddenResultsSection = (
|
||||||
<div className="mx_SpotlightDialog_section mx_SpotlightDialog_hiddenResults" role="group">
|
<div className="mx_SpotlightDialog_section mx_SpotlightDialog_hiddenResults" role="group">
|
||||||
|
@ -921,7 +921,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let groupChatSection: JSX.Element;
|
let groupChatSection: JSX.Element | undefined;
|
||||||
if (filter === Filter.People) {
|
if (filter === Filter.People) {
|
||||||
groupChatSection = (
|
groupChatSection = (
|
||||||
<div
|
<div
|
||||||
|
@ -941,7 +941,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let messageSearchSection: JSX.Element;
|
let messageSearchSection: JSX.Element | undefined;
|
||||||
if (filter === null) {
|
if (filter === null) {
|
||||||
messageSearchSection = (
|
messageSearchSection = (
|
||||||
<div
|
<div
|
||||||
|
@ -977,7 +977,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let recentSearchesSection: JSX.Element;
|
let recentSearchesSection: JSX.Element | undefined;
|
||||||
if (recentSearches.length) {
|
if (recentSearches.length) {
|
||||||
recentSearchesSection = (
|
recentSearchesSection = (
|
||||||
<div
|
<div
|
||||||
|
@ -1075,7 +1075,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ref: RefObject<HTMLElement>;
|
let ref: RefObject<HTMLElement> | undefined;
|
||||||
const accessibilityAction = getKeyBindingsManager().getAccessibilityAction(ev);
|
const accessibilityAction = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||||
switch (accessibilityAction) {
|
switch (accessibilityAction) {
|
||||||
case KeyBindingAction.Escape:
|
case KeyBindingAction.Escape:
|
||||||
|
|
|
@ -24,11 +24,11 @@ import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds";
|
||||||
interface IProps {
|
interface IProps {
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
avatarDisabled?: boolean;
|
avatarDisabled?: boolean;
|
||||||
name?: string;
|
name: string;
|
||||||
nameDisabled?: boolean;
|
nameDisabled?: boolean;
|
||||||
topic?: string;
|
topic?: string;
|
||||||
topicDisabled?: boolean;
|
topicDisabled?: boolean;
|
||||||
setAvatar(avatar: File): void;
|
setAvatar(avatar?: File): void;
|
||||||
setName(name: string): void;
|
setName(name: string): void;
|
||||||
setTopic(topic: string): void;
|
setTopic(topic: string): void;
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ export const SpaceAvatar: React.FC<Pick<IProps, "avatarUrl" | "avatarDisabled" |
|
||||||
setAvatar(file);
|
setAvatar(file);
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (ev) => {
|
reader.onload = (ev) => {
|
||||||
setAvatarDataUrl(ev.target.result as string);
|
setAvatarDataUrl(ev.target?.result as string);
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -53,7 +53,7 @@ const SpecificChildrenPicker: React.FC<ISpecificChildrenPickerProps> = ({
|
||||||
|
|
||||||
const matcher = new QueryMatcher<Room>(rooms, {
|
const matcher = new QueryMatcher<Room>(rooms, {
|
||||||
keys: ["name"],
|
keys: ["name"],
|
||||||
funcs: [(r) => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)],
|
funcs: [(r) => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean) as string[]],
|
||||||
shouldMatchWordsOnly: false,
|
shouldMatchWordsOnly: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -245,14 +245,14 @@ export const SpaceCreateForm: React.FC<ISpaceCreateFormProps> = ({
|
||||||
const SpaceCreateMenu: React.FC<{
|
const SpaceCreateMenu: React.FC<{
|
||||||
onFinished(): void;
|
onFinished(): void;
|
||||||
}> = ({ onFinished }) => {
|
}> = ({ onFinished }) => {
|
||||||
const [visibility, setVisibility] = useState<Visibility>(null);
|
const [visibility, setVisibility] = useState<Visibility | null>(null);
|
||||||
const [busy, setBusy] = useState<boolean>(false);
|
const [busy, setBusy] = useState<boolean>(false);
|
||||||
|
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const spaceNameField = useRef<Field>();
|
const spaceNameField = useRef<Field>();
|
||||||
const [alias, setAlias] = useState("");
|
const [alias, setAlias] = useState("");
|
||||||
const spaceAliasField = useRef<RoomAliasField>();
|
const spaceAliasField = useRef<RoomAliasField>();
|
||||||
const [avatar, setAvatar] = useState<File>(null);
|
const [avatar, setAvatar] = useState<File | undefined>(undefined);
|
||||||
const [topic, setTopic] = useState<string>("");
|
const [topic, setTopic] = useState<string>("");
|
||||||
|
|
||||||
const onSpaceCreateClick = async (e: ButtonEvent): Promise<void> => {
|
const onSpaceCreateClick = async (e: ButtonEvent): Promise<void> => {
|
||||||
|
|
|
@ -215,7 +215,7 @@ const CreateSpaceButton: React.FC<Pick<IInnerSpacePanelProps, "isPanelCollapsed"
|
||||||
}
|
}
|
||||||
}, [isPanelCollapsed]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [isPanelCollapsed]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
let contextMenu = null;
|
let contextMenu: JSX.Element | undefined;
|
||||||
if (menuDisplayed) {
|
if (menuDisplayed) {
|
||||||
contextMenu = <SpaceCreateMenu onFinished={closeMenu} />;
|
contextMenu = <SpaceCreateMenu onFinished={closeMenu} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,11 +121,11 @@ export const SpaceButton = forwardRef<HTMLElement, IButtonProps>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let contextMenu: JSX.Element;
|
let contextMenu: JSX.Element | undefined;
|
||||||
if (menuDisplayed && ContextMenuComponent) {
|
if (menuDisplayed && handle.current && ContextMenuComponent) {
|
||||||
contextMenu = (
|
contextMenu = (
|
||||||
<ContextMenuComponent
|
<ContextMenuComponent
|
||||||
{...toRightOf(handle.current?.getBoundingClientRect(), 0)}
|
{...toRightOf(handle.current.getBoundingClientRect(), 0)}
|
||||||
space={space}
|
space={space}
|
||||||
onFinished={closeMenu}
|
onFinished={closeMenu}
|
||||||
/>
|
/>
|
||||||
|
@ -242,7 +242,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private get isCollapsed(): boolean {
|
private get isCollapsed(): boolean {
|
||||||
return this.state.collapsed || this.props.isPanelCollapsed;
|
return this.state.collapsed || !!this.props.isPanelCollapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private toggleCollapse = (evt: ButtonEvent): void => {
|
private toggleCollapse = (evt: ButtonEvent): void => {
|
||||||
|
|
|
@ -30,7 +30,7 @@ const onClickSendDm = (ev: ButtonEvent): void => {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
useCase: UseCase;
|
useCase: UseCase | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserOnboardingHeader({ useCase }: Props): JSX.Element {
|
export function UserOnboardingHeader({ useCase }: Props): JSX.Element {
|
||||||
|
|
|
@ -38,7 +38,7 @@ interface Props {
|
||||||
// We decided to only show the new user onboarding page to new users
|
// We decided to only show the new user onboarding page to new users
|
||||||
// For now, that means we set the cutoff at 2022-07-01 00:00 UTC
|
// For now, that means we set the cutoff at 2022-07-01 00:00 UTC
|
||||||
const USER_ONBOARDING_CUTOFF_DATE = new Date(1_656_633_600);
|
const USER_ONBOARDING_CUTOFF_DATE = new Date(1_656_633_600);
|
||||||
export function showUserOnboardingPage(useCase: UseCase): boolean {
|
export function showUserOnboardingPage(useCase: UseCase | null): boolean {
|
||||||
return useCase !== null || MatrixClientPeg.userRegisteredAfter(USER_ONBOARDING_CUTOFF_DATE);
|
return useCase !== null || MatrixClientPeg.userRegisteredAfter(USER_ONBOARDING_CUTOFF_DATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,13 +55,11 @@ export function UserOnboardingPage({ justRegistered = false }: Props): JSX.Eleme
|
||||||
const [showList, setShowList] = useState<boolean>(false);
|
const [showList, setShowList] = useState<boolean>(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialSyncComplete) {
|
if (initialSyncComplete) {
|
||||||
let handler: number | null = window.setTimeout(() => {
|
const handler = window.setTimeout(() => {
|
||||||
handler = null;
|
|
||||||
setShowList(true);
|
setShowList(true);
|
||||||
}, ANIMATION_DURATION);
|
}, ANIMATION_DURATION);
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(handler);
|
clearTimeout(handler);
|
||||||
handler = null;
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
setShowList(false);
|
setShowList(false);
|
||||||
|
|
|
@ -18,6 +18,7 @@ import * as React from "react";
|
||||||
|
|
||||||
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
|
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
|
import { XOR } from "../../../@types/common";
|
||||||
|
|
||||||
const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "#"];
|
const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "#"];
|
||||||
const BUTTON_LETTERS = ["", "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ", "", "+", ""];
|
const BUTTON_LETTERS = ["", "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ", "", "+", ""];
|
||||||
|
@ -31,7 +32,7 @@ interface IButtonProps {
|
||||||
kind: DialPadButtonKind;
|
kind: DialPadButtonKind;
|
||||||
digit?: string;
|
digit?: string;
|
||||||
digitSubtext?: string;
|
digitSubtext?: string;
|
||||||
onButtonPress: (digit: string, ev: ButtonEvent) => void;
|
onButtonPress: (digit: string | undefined, ev: ButtonEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DialPadButton extends React.PureComponent<IButtonProps> {
|
class DialPadButton extends React.PureComponent<IButtonProps> {
|
||||||
|
@ -60,16 +61,24 @@ class DialPadButton extends React.PureComponent<IButtonProps> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IProps {
|
interface IBaseProps {
|
||||||
onDigitPress: (digit: string, ev: ButtonEvent) => void;
|
onDigitPress: (digit: string, ev: ButtonEvent) => void;
|
||||||
hasDial: boolean;
|
|
||||||
onDeletePress?: (ev: ButtonEvent) => void;
|
onDeletePress?: (ev: ButtonEvent) => void;
|
||||||
onDialPress?: () => void;
|
hasDial: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Dialpad extends React.PureComponent<IProps> {
|
interface IProps extends IBaseProps {
|
||||||
|
hasDial: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IDialProps extends IBaseProps {
|
||||||
|
hasDial: true;
|
||||||
|
onDialPress: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Dialpad extends React.PureComponent<XOR<IProps, IDialProps>> {
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const buttonNodes = [];
|
const buttonNodes: JSX.Element[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < BUTTONS.length; i++) {
|
for (let i = 0; i < BUTTONS.length; i++) {
|
||||||
const button = BUTTONS[i];
|
const button = BUTTONS[i];
|
||||||
|
|
|
@ -76,7 +76,7 @@ interface IState {
|
||||||
sidebarShown: boolean;
|
sidebarShown: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFullScreenElement(): Element | undefined {
|
function getFullScreenElement(): Element | null {
|
||||||
return (
|
return (
|
||||||
document.fullscreenElement ||
|
document.fullscreenElement ||
|
||||||
// moz omitted because firefox supports this unprefixed now (webkit here for safari)
|
// moz omitted because firefox supports this unprefixed now (webkit here for safari)
|
||||||
|
@ -180,7 +180,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private updateCallListeners(oldCall: MatrixCall, newCall: MatrixCall | null): void {
|
private updateCallListeners(oldCall: MatrixCall | null, newCall: MatrixCall | null): void {
|
||||||
if (oldCall === newCall) return;
|
if (oldCall === newCall) return;
|
||||||
|
|
||||||
if (oldCall) {
|
if (oldCall) {
|
||||||
|
@ -245,7 +245,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let primary: CallFeed;
|
let primary: CallFeed | undefined;
|
||||||
|
|
||||||
// Try to use a screensharing as primary, a remote one if possible
|
// Try to use a screensharing as primary, a remote one if possible
|
||||||
const screensharingFeeds = feeds.filter((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare);
|
const screensharingFeeds = feeds.filter((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare);
|
||||||
|
@ -289,7 +289,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||||
if (this.state.screensharing) {
|
if (this.state.screensharing) {
|
||||||
isScreensharing = await this.props.call.setScreensharingEnabled(false);
|
isScreensharing = await this.props.call.setScreensharingEnabled(false);
|
||||||
} else {
|
} else {
|
||||||
if (PlatformPeg.get().supportsDesktopCapturer()) {
|
if (PlatformPeg.get()?.supportsDesktopCapturer()) {
|
||||||
const { finished } = Modal.createDialog<[string]>(DesktopCapturerSourcePicker);
|
const { finished } = Modal.createDialog<[string]>(DesktopCapturerSourcePicker);
|
||||||
const [source] = await finished;
|
const [source] = await finished;
|
||||||
if (!source) return;
|
if (!source) return;
|
||||||
|
@ -403,7 +403,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderToast(): JSX.Element {
|
private renderToast(): JSX.Element | null {
|
||||||
const { call } = this.props;
|
const { call } = this.props;
|
||||||
const someoneIsScreensharing = call.getFeeds().some((feed) => {
|
const someoneIsScreensharing = call.getFeeds().some((feed) => {
|
||||||
return feed.purpose === SDPStreamMetadataPurpose.Screenshare;
|
return feed.purpose === SDPStreamMetadataPurpose.Screenshare;
|
||||||
|
@ -413,8 +413,8 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const isScreensharing = call.isScreensharing();
|
const isScreensharing = call.isScreensharing();
|
||||||
const { primaryFeed, sidebarShown } = this.state;
|
const { primaryFeed, sidebarShown } = this.state;
|
||||||
const sharerName = primaryFeed?.getMember().name;
|
const sharerName = primaryFeed?.getMember()?.name;
|
||||||
if (!sharerName) return;
|
if (!sharerName) return null;
|
||||||
|
|
||||||
let text = isScreensharing ? _t("You are presenting") : _t("%(sharerName)s is presenting", { sharerName });
|
let text = isScreensharing ? _t("You are presenting") : _t("%(sharerName)s is presenting", { sharerName });
|
||||||
if (!sidebarShown) {
|
if (!sidebarShown) {
|
||||||
|
@ -495,7 +495,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
} else if (isLocalOnHold) {
|
} else if (isLocalOnHold) {
|
||||||
onHoldText = _t("%(peerName)s held the call", {
|
onHoldText = _t("%(peerName)s held the call", {
|
||||||
peerName: call.getOpponentMember().name,
|
peerName: call.getOpponentMember()?.name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -556,8 +556,8 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const callRoomId = LegacyCallHandler.instance.roomIdForCall(call);
|
const callRoomId = LegacyCallHandler.instance.roomIdForCall(call);
|
||||||
const secondaryCallRoomId = LegacyCallHandler.instance.roomIdForCall(secondaryCall);
|
const secondaryCallRoomId = LegacyCallHandler.instance.roomIdForCall(secondaryCall);
|
||||||
const callRoom = client.getRoom(callRoomId);
|
const callRoom = callRoomId ? client.getRoom(callRoomId) : null;
|
||||||
const secCallRoom = secondaryCall ? client.getRoom(secondaryCallRoomId) : null;
|
const secCallRoom = secondaryCallRoomId ? client.getRoom(secondaryCallRoomId) : null;
|
||||||
|
|
||||||
const callViewClasses = classNames({
|
const callViewClasses = classNames({
|
||||||
mx_LegacyCallView: true,
|
mx_LegacyCallView: true,
|
||||||
|
|
|
@ -104,9 +104,9 @@ const LegacyCallViewDropdownButton: React.FC<IDropdownButtonProps> = ({ state, d
|
||||||
onHover={(hovering) => setHoveringDropdown(hovering)}
|
onHover={(hovering) => setHoveringDropdown(hovering)}
|
||||||
state={state}
|
state={state}
|
||||||
/>
|
/>
|
||||||
{menuDisplayed && (
|
{menuDisplayed && buttonRef.current && (
|
||||||
<DeviceContextMenu
|
<DeviceContextMenu
|
||||||
{...alwaysAboveRightOf(buttonRef.current?.getBoundingClientRect())}
|
{...alwaysAboveRightOf(buttonRef.current.getBoundingClientRect())}
|
||||||
onFinished={closeMenu}
|
onFinished={closeMenu}
|
||||||
deviceKinds={deviceKinds}
|
deviceKinds={deviceKinds}
|
||||||
/>
|
/>
|
||||||
|
@ -117,7 +117,7 @@ const LegacyCallViewDropdownButton: React.FC<IDropdownButtonProps> = ({ state, d
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
call: MatrixCall;
|
call: MatrixCall;
|
||||||
pipMode: boolean;
|
pipMode?: boolean;
|
||||||
handlers: {
|
handlers: {
|
||||||
onHangupClick: () => void;
|
onHangupClick: () => void;
|
||||||
onScreenshareClick: () => void;
|
onScreenshareClick: () => void;
|
||||||
|
@ -150,7 +150,7 @@ interface IState {
|
||||||
export default class LegacyCallViewButtons extends React.Component<IProps, IState> {
|
export default class LegacyCallViewButtons extends React.Component<IProps, IState> {
|
||||||
private dialpadButton = createRef<HTMLDivElement>();
|
private dialpadButton = createRef<HTMLDivElement>();
|
||||||
private contextMenuButton = createRef<HTMLDivElement>();
|
private contextMenuButton = createRef<HTMLDivElement>();
|
||||||
private controlsHideTimer: number = null;
|
private controlsHideTimer: number | null = null;
|
||||||
|
|
||||||
public constructor(props: IProps) {
|
public constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -223,7 +223,7 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
|
||||||
});
|
});
|
||||||
|
|
||||||
let dialPad;
|
let dialPad;
|
||||||
if (this.state.showDialpad) {
|
if (this.state.showDialpad && this.dialpadButton.current) {
|
||||||
dialPad = (
|
dialPad = (
|
||||||
<DialpadContextMenu
|
<DialpadContextMenu
|
||||||
{...alwaysMenuProps(
|
{...alwaysMenuProps(
|
||||||
|
@ -231,7 +231,7 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
|
||||||
ChevronFace.None,
|
ChevronFace.None,
|
||||||
CONTEXT_MENU_VPADDING,
|
CONTEXT_MENU_VPADDING,
|
||||||
)}
|
)}
|
||||||
// We mount the context menus as a as a child typically in order to include the
|
// We mount the context menus as a child typically in order to include the
|
||||||
// context menus when fullscreening the call content.
|
// context menus when fullscreening the call content.
|
||||||
// However, this does not work as well when the call is embedded in a
|
// However, this does not work as well when the call is embedded in a
|
||||||
// picture-in-picture frame. Thus, only mount as child when we are *not* in PiP.
|
// picture-in-picture frame. Thus, only mount as child when we are *not* in PiP.
|
||||||
|
@ -243,7 +243,7 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
|
||||||
}
|
}
|
||||||
|
|
||||||
let contextMenu;
|
let contextMenu;
|
||||||
if (this.state.showMoreMenu) {
|
if (this.state.showMoreMenu && this.contextMenuButton.current) {
|
||||||
contextMenu = (
|
contextMenu = (
|
||||||
<LegacyCallContextMenu
|
<LegacyCallContextMenu
|
||||||
{...alwaysMenuProps(
|
{...alwaysMenuProps(
|
||||||
|
|
|
@ -71,9 +71,9 @@ const SecondaryCallInfo: React.FC<ISecondaryCallInfoProps> = ({ callRoom }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface LegacyCallViewHeaderProps {
|
interface LegacyCallViewHeaderProps {
|
||||||
pipMode: boolean;
|
pipMode?: boolean;
|
||||||
callRooms?: Room[];
|
callRooms: [Room, Room | null];
|
||||||
onPipMouseDown: (event: React.MouseEvent<Element, MouseEvent>) => void;
|
onPipMouseDown?: (event: React.MouseEvent<Element, MouseEvent>) => void;
|
||||||
onExpand?: () => void;
|
onExpand?: () => void;
|
||||||
onPin?: () => void;
|
onPin?: () => void;
|
||||||
onMaximize?: () => void;
|
onMaximize?: () => void;
|
||||||
|
@ -81,7 +81,7 @@ interface LegacyCallViewHeaderProps {
|
||||||
|
|
||||||
const LegacyCallViewHeader: React.FC<LegacyCallViewHeaderProps> = ({
|
const LegacyCallViewHeader: React.FC<LegacyCallViewHeaderProps> = ({
|
||||||
pipMode = false,
|
pipMode = false,
|
||||||
callRooms = [],
|
callRooms,
|
||||||
onPipMouseDown,
|
onPipMouseDown,
|
||||||
onExpand,
|
onExpand,
|
||||||
onPin,
|
onPin,
|
||||||
|
|
|
@ -95,7 +95,7 @@ export default class VideoFeed extends React.PureComponent<IProps, IState> {
|
||||||
element.addEventListener("resize", this.onResize);
|
element.addEventListener("resize", this.onResize);
|
||||||
};
|
};
|
||||||
|
|
||||||
private updateFeed(oldFeed: CallFeed, newFeed: CallFeed): void {
|
private updateFeed(oldFeed: CallFeed | null, newFeed: CallFeed | null): void {
|
||||||
if (oldFeed === newFeed) return;
|
if (oldFeed === newFeed) return;
|
||||||
|
|
||||||
if (oldFeed) {
|
if (oldFeed) {
|
||||||
|
|
|
@ -84,7 +84,7 @@ export class Media {
|
||||||
* The HTTP URL for the thumbnail media (without any specified width, height, etc). Null/undefined
|
* The HTTP URL for the thumbnail media (without any specified width, height, etc). Null/undefined
|
||||||
* if no thumbnail media recorded.
|
* if no thumbnail media recorded.
|
||||||
*/
|
*/
|
||||||
public get thumbnailHttp(): string | undefined | null {
|
public get thumbnailHttp(): string | 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!);
|
||||||
|
|
|
@ -45,7 +45,7 @@ export function isSlashCommand(model: EditorModel): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSlashCommand(model: EditorModel): [Command, string, string] {
|
export function getSlashCommand(model: EditorModel): [Command | undefined, string | undefined, string] {
|
||||||
const commandText = model.parts.reduce((text, part) => {
|
const commandText = model.parts.reduce((text, part) => {
|
||||||
// use mxid to textify user pills in a command and room alias/id for room pills
|
// use mxid to textify user pills in a command and room alias/id for room pills
|
||||||
if (part.type === Type.UserPill || part.type === Type.RoomPill) {
|
if (part.type === Type.UserPill || part.type === Type.RoomPill) {
|
||||||
|
@ -69,7 +69,7 @@ export async function runSlashCommand(
|
||||||
if (result.promise) {
|
if (result.promise) {
|
||||||
try {
|
try {
|
||||||
if (cmd.category === CommandCategories.messages || cmd.category === CommandCategories.effects) {
|
if (cmd.category === CommandCategories.messages || cmd.category === CommandCategories.effects) {
|
||||||
messageContent = await result.promise;
|
messageContent = (await result.promise) ?? null;
|
||||||
} else {
|
} else {
|
||||||
await result.promise;
|
await result.promise;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ export function walkDOMDepthFirst(rootNode: Node, enterNodeCallback: Predicate,
|
||||||
} else if (node.nextSibling) {
|
} else if (node.nextSibling) {
|
||||||
node = node.nextSibling;
|
node = node.nextSibling;
|
||||||
} else {
|
} else {
|
||||||
while (!node.nextSibling && node !== rootNode) {
|
while (node && !node.nextSibling && node !== rootNode) {
|
||||||
node = node.parentElement;
|
node = node.parentElement;
|
||||||
if (node !== rootNode) {
|
if (node !== rootNode) {
|
||||||
leaveNodeCallback(node);
|
leaveNodeCallback(node);
|
||||||
|
|
|
@ -127,7 +127,7 @@ export default class EditorModel {
|
||||||
return this._parts;
|
return this._parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get autoComplete(): AutocompleteWrapperModel {
|
public get autoComplete(): AutocompleteWrapperModel | null {
|
||||||
if (this.activePartIdx === this.autoCompletePartIdx) {
|
if (this.activePartIdx === this.autoCompletePartIdx) {
|
||||||
return this._autoComplete;
|
return this._autoComplete;
|
||||||
}
|
}
|
||||||
|
@ -212,12 +212,12 @@ export default class EditorModel {
|
||||||
const transformAddedLen = this.getTransformAddedLen(newPosition, inputType, diff);
|
const transformAddedLen = this.getTransformAddedLen(newPosition, inputType, diff);
|
||||||
newPosition = this.positionForOffset(caretOffset + transformAddedLen, true);
|
newPosition = this.positionForOffset(caretOffset + transformAddedLen, true);
|
||||||
}
|
}
|
||||||
this.updateCallback(newPosition, inputType, diff);
|
this.updateCallback?.(newPosition, inputType, diff);
|
||||||
return acPromise;
|
return acPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTransformAddedLen(newPosition: DocumentPosition, inputType: string, diff: IDiff): number {
|
private getTransformAddedLen(newPosition: DocumentPosition, inputType: string, diff: IDiff): number {
|
||||||
const result = this.transformCallback(newPosition, inputType, diff);
|
const result = this.transformCallback?.(newPosition, inputType, diff);
|
||||||
return Number.isFinite(result) ? (result as number) : 0;
|
return Number.isFinite(result) ? (result as number) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,13 +268,13 @@ export default class EditorModel {
|
||||||
// rerender even if editor contents didn't change
|
// rerender even if editor contents didn't change
|
||||||
// to make sure the MessageEditor checks
|
// to make sure the MessageEditor checks
|
||||||
// model.autoComplete being empty and closes it
|
// model.autoComplete being empty and closes it
|
||||||
this.updateCallback(pos);
|
this.updateCallback?.(pos);
|
||||||
};
|
};
|
||||||
|
|
||||||
private mergeAdjacentParts(): void {
|
private mergeAdjacentParts(): void {
|
||||||
let prevPart: Part | undefined;
|
let prevPart: Part | undefined;
|
||||||
for (let i = 0; i < this._parts.length; ++i) {
|
for (let i = 0; i < this._parts.length; ++i) {
|
||||||
let part = this._parts[i];
|
let part: Part | undefined = this._parts[i];
|
||||||
const isEmpty = !part.text.length;
|
const isEmpty = !part.text.length;
|
||||||
const isMerged = !isEmpty && prevPart && prevPart.merge?.(part);
|
const isMerged = !isEmpty && prevPart && prevPart.merge?.(part);
|
||||||
if (isEmpty || isMerged) {
|
if (isEmpty || isMerged) {
|
||||||
|
@ -452,13 +452,13 @@ export default class EditorModel {
|
||||||
*/
|
*/
|
||||||
public transform(callback: ManualTransformCallback): Promise<void> {
|
public transform(callback: ManualTransformCallback): Promise<void> {
|
||||||
const pos = callback();
|
const pos = callback();
|
||||||
let acPromise: Promise<void> = null;
|
let acPromise: Promise<void> | null = null;
|
||||||
if (!(pos instanceof Range)) {
|
if (!(pos instanceof Range)) {
|
||||||
acPromise = this.setActivePart(pos, true);
|
acPromise = this.setActivePart(pos, true);
|
||||||
} else {
|
} else {
|
||||||
acPromise = Promise.resolve();
|
acPromise = Promise.resolve();
|
||||||
}
|
}
|
||||||
this.updateCallback(pos);
|
this.updateCallback?.(pos);
|
||||||
return acPromise;
|
return acPromise;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -422,7 +422,7 @@ class RoomPillPart extends PillPart {
|
||||||
|
|
||||||
protected setAvatar(node: HTMLElement): void {
|
protected setAvatar(node: HTMLElement): void {
|
||||||
let initialLetter = "";
|
let initialLetter = "";
|
||||||
let avatarUrl = Avatar.avatarUrlForRoom(this.room, 16, 16, "crop");
|
let avatarUrl = Avatar.avatarUrlForRoom(this.room ?? null, 16, 16, "crop");
|
||||||
if (!avatarUrl) {
|
if (!avatarUrl) {
|
||||||
initialLetter = Avatar.getInitialLetter(this.room?.name || this.resourceId) ?? "";
|
initialLetter = Avatar.getInitialLetter(this.room?.name || this.resourceId) ?? "";
|
||||||
avatarUrl = Avatar.defaultAvatarUrlForString(this.room?.roomId ?? this.resourceId);
|
avatarUrl = Avatar.defaultAvatarUrlForString(this.room?.roomId ?? this.resourceId);
|
||||||
|
@ -541,7 +541,7 @@ export class PartCreator {
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly room: Room,
|
private readonly room: Room,
|
||||||
private readonly client: MatrixClient,
|
private readonly client: MatrixClient,
|
||||||
autoCompleteCreator: AutoCompleteCreator = null,
|
autoCompleteCreator: AutoCompleteCreator | null = null,
|
||||||
) {
|
) {
|
||||||
// pre-create the creator as an object even without callback so it can already be passed
|
// pre-create the creator as an object even without callback so it can already be passed
|
||||||
// to PillCandidatePart (e.g. while deserializing) and set later on
|
// to PillCandidatePart (e.g. while deserializing) and set later on
|
||||||
|
@ -574,7 +574,7 @@ export class PartCreator {
|
||||||
return this.plain(text);
|
return this.plain(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public deserializePart(part: SerializedPart): Part {
|
public deserializePart(part: SerializedPart): Part | undefined {
|
||||||
switch (part.type) {
|
switch (part.type) {
|
||||||
case Type.Plain:
|
case Type.Plain:
|
||||||
return this.plain(part.text);
|
return this.plain(part.text);
|
||||||
|
@ -612,7 +612,7 @@ export class PartCreator {
|
||||||
public roomPill(alias: string, roomId?: string): RoomPillPart {
|
public roomPill(alias: string, roomId?: string): RoomPillPart {
|
||||||
let room: Room | undefined;
|
let room: Room | undefined;
|
||||||
if (roomId || alias[0] !== "#") {
|
if (roomId || alias[0] !== "#") {
|
||||||
room = this.client.getRoom(roomId || alias);
|
room = this.client.getRoom(roomId || alias) ?? undefined;
|
||||||
} else {
|
} else {
|
||||||
room = this.client.getRooms().find((r) => {
|
room = this.client.getRooms().find((r) => {
|
||||||
return r.getCanonicalAlias() === alias || r.getAltAliases().includes(alias);
|
return r.getCanonicalAlias() === alias || r.getAltAliases().includes(alias);
|
||||||
|
@ -691,7 +691,7 @@ export class CommandPartCreator extends PartCreator {
|
||||||
return new CommandPart(text, this.autoCompleteCreator);
|
return new CommandPart(text, this.autoCompleteCreator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public deserializePart(part: SerializedPart): Part {
|
public deserializePart(part: SerializedPart): Part | undefined {
|
||||||
if (part.type === Type.Command) {
|
if (part.type === Type.Command) {
|
||||||
return this.command(part.text);
|
return this.command(part.text);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -93,8 +93,8 @@ function reconcileLine(lineContainer: ChildNode, parts: Part[]): void {
|
||||||
|
|
||||||
if (needsCaretNodeBefore(part, prevPart)) {
|
if (needsCaretNodeBefore(part, prevPart)) {
|
||||||
if (isCaretNode(currentNode as Element)) {
|
if (isCaretNode(currentNode as Element)) {
|
||||||
updateCaretNode(currentNode);
|
updateCaretNode(currentNode!);
|
||||||
currentNode = currentNode.nextSibling;
|
currentNode = currentNode!.nextSibling;
|
||||||
} else {
|
} else {
|
||||||
lineContainer.insertBefore(createCaretNode(), currentNode);
|
lineContainer.insertBefore(createCaretNode(), currentNode);
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ interface ISerializeOpts {
|
||||||
export function htmlSerializeIfNeeded(
|
export function htmlSerializeIfNeeded(
|
||||||
model: EditorModel,
|
model: EditorModel,
|
||||||
{ forceHTML = false, useMarkdown = true }: ISerializeOpts = {},
|
{ forceHTML = false, useMarkdown = true }: ISerializeOpts = {},
|
||||||
): string {
|
): string | undefined {
|
||||||
if (!useMarkdown) {
|
if (!useMarkdown) {
|
||||||
return escapeHtml(textSerialize(model)).replace(/\n/g, "<br/>");
|
return escapeHtml(textSerialize(model)).replace(/\n/g, "<br/>");
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ export function htmlSerializeIfNeeded(
|
||||||
return htmlSerializeFromMdIfNeeded(md, { forceHTML });
|
return htmlSerializeFromMdIfNeeded(md, { forceHTML });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function htmlSerializeFromMdIfNeeded(md: string, { forceHTML = false } = {}): string {
|
export function htmlSerializeFromMdIfNeeded(md: string, { forceHTML = false } = {}): string | undefined {
|
||||||
// copy of raw input to remove unwanted math later
|
// copy of raw input to remove unwanted math later
|
||||||
const orig = md;
|
const orig = md;
|
||||||
|
|
||||||
|
|
|
@ -139,7 +139,7 @@ export const usePublicRoomDirectory = (): {
|
||||||
SdkConfig.getObject("room_directory")?.get("servers")?.includes(lsRoomServer) ||
|
SdkConfig.getObject("room_directory")?.get("servers")?.includes(lsRoomServer) ||
|
||||||
SettingsStore.getValue("room_directory_servers")?.includes(lsRoomServer)
|
SettingsStore.getValue("room_directory_servers")?.includes(lsRoomServer)
|
||||||
) {
|
) {
|
||||||
roomServer = lsRoomServer;
|
roomServer = lsRoomServer!;
|
||||||
}
|
}
|
||||||
|
|
||||||
let instanceId: string | undefined = undefined;
|
let instanceId: string | undefined = undefined;
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { useEffect, useState } from "react";
|
||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
|
|
||||||
// Hook to fetch the value of a setting and dynamically update when it changes
|
// Hook to fetch the value of a setting and dynamically update when it changes
|
||||||
export const useSettingValue = <T>(settingName: string, roomId: string = null, excludeDefault = false): T => {
|
export const useSettingValue = <T>(settingName: string, roomId: string | null = null, excludeDefault = false): T => {
|
||||||
const [value, setValue] = useState(SettingsStore.getValue<T>(settingName, roomId, excludeDefault));
|
const [value, setValue] = useState(SettingsStore.getValue<T>(settingName, roomId, excludeDefault));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -36,7 +36,7 @@ export const useSettingValue = <T>(settingName: string, roomId: string = null, e
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hook to fetch whether a feature is enabled and dynamically update when that changes
|
// Hook to fetch whether a feature is enabled and dynamically update when that changes
|
||||||
export const useFeatureEnabled = (featureName: string, roomId: string = null): boolean => {
|
export const useFeatureEnabled = (featureName: string, roomId: string | null = null): boolean => {
|
||||||
const [enabled, setEnabled] = useState(SettingsStore.getValue<boolean>(featureName, roomId));
|
const [enabled, setEnabled] = useState(SettingsStore.getValue<boolean>(featureName, roomId));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { SlidingSyncManager } from "../SlidingSyncManager";
|
||||||
|
|
||||||
export interface SlidingSyncRoomSearchOpts {
|
export interface SlidingSyncRoomSearchOpts {
|
||||||
limit: number;
|
limit: number;
|
||||||
query?: string;
|
query: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSlidingSyncRoomSearch = (): {
|
export const useSlidingSyncRoomSearch = (): {
|
||||||
|
@ -55,7 +55,7 @@ export const useSlidingSyncRoomSearch = (): {
|
||||||
room_name_like: term,
|
room_name_like: term,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const rooms = [];
|
const rooms: Room[] = [];
|
||||||
const { roomIndexToRoomId } = SlidingSyncManager.instance.slidingSync.getListData(
|
const { roomIndexToRoomId } = SlidingSyncManager.instance.slidingSync.getListData(
|
||||||
SlidingSyncManager.ListSearch,
|
SlidingSyncManager.ListSearch,
|
||||||
)!;
|
)!;
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const useTimeout = (handler: Handler, timeoutMs: number): void => {
|
||||||
// Set up timer
|
// Set up timer
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timeoutID = window.setTimeout(() => {
|
const timeoutID = window.setTimeout(() => {
|
||||||
savedHandler.current();
|
savedHandler.current?.();
|
||||||
}, timeoutMs);
|
}, timeoutMs);
|
||||||
return () => clearTimeout(timeoutID);
|
return () => clearTimeout(timeoutID);
|
||||||
}, [timeoutMs]);
|
}, [timeoutMs]);
|
||||||
|
@ -50,7 +50,7 @@ export const useInterval = (handler: Handler, intervalMs: number): void => {
|
||||||
// Set up timer
|
// Set up timer
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const intervalID = window.setInterval(() => {
|
const intervalID = window.setInterval(() => {
|
||||||
savedHandler.current();
|
savedHandler.current?.();
|
||||||
}, intervalMs);
|
}, intervalMs);
|
||||||
return () => clearInterval(intervalID);
|
return () => clearInterval(intervalID);
|
||||||
}, [intervalMs]);
|
}, [intervalMs]);
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { useLatestResult } from "./useLatestResult";
|
||||||
|
|
||||||
export interface IUserDirectoryOpts {
|
export interface IUserDirectoryOpts {
|
||||||
limit: number;
|
limit: number;
|
||||||
query?: string;
|
query: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useUserDirectory = (): {
|
export const useUserDirectory = (): {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import { IEventWithRoomId, IMatrixProfile, IResultRoomEvents } from "matrix-js-s
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
|
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync";
|
||||||
|
|
||||||
import PlatformPeg from "../PlatformPeg";
|
import PlatformPeg from "../PlatformPeg";
|
||||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||||
|
@ -50,11 +51,11 @@ interface ICrawler {
|
||||||
*/
|
*/
|
||||||
export default class EventIndex extends EventEmitter {
|
export default class EventIndex extends EventEmitter {
|
||||||
private crawlerCheckpoints: ICrawlerCheckpoint[] = [];
|
private crawlerCheckpoints: ICrawlerCheckpoint[] = [];
|
||||||
private crawler: ICrawler = null;
|
private crawler: ICrawler | null = null;
|
||||||
private currentCheckpoint: ICrawlerCheckpoint = null;
|
private currentCheckpoint: ICrawlerCheckpoint | null = null;
|
||||||
|
|
||||||
public async init(): Promise<void> {
|
public async init(): Promise<void> {
|
||||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
||||||
|
|
||||||
this.crawlerCheckpoints = await indexManager.loadCheckpoints();
|
this.crawlerCheckpoints = await indexManager.loadCheckpoints();
|
||||||
logger.log("EventIndex: Loaded checkpoints", this.crawlerCheckpoints);
|
logger.log("EventIndex: Loaded checkpoints", this.crawlerCheckpoints);
|
||||||
|
@ -91,7 +92,7 @@ export default class EventIndex extends EventEmitter {
|
||||||
* Get crawler checkpoints for the encrypted rooms and store them in the index.
|
* Get crawler checkpoints for the encrypted rooms and store them in the index.
|
||||||
*/
|
*/
|
||||||
public async addInitialCheckpoints(): Promise<void> {
|
public async addInitialCheckpoints(): Promise<void> {
|
||||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const rooms = client.getRooms();
|
const rooms = client.getRooms();
|
||||||
|
|
||||||
|
@ -157,8 +158,8 @@ export default class EventIndex extends EventEmitter {
|
||||||
* - Every other sync, tell the event index to commit all the queued up
|
* - Every other sync, tell the event index to commit all the queued up
|
||||||
* live events
|
* live events
|
||||||
*/
|
*/
|
||||||
private onSync = async (state: string, prevState: string, data: object): Promise<void> => {
|
private onSync = async (state: SyncState, prevState: SyncState | null, data?: ISyncStateData): Promise<void> => {
|
||||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
||||||
|
|
||||||
if (prevState === "PREPARED" && state === "SYNCING") {
|
if (prevState === "PREPARED" && state === "SYNCING") {
|
||||||
// If our indexer is empty we're most likely running Element the
|
// If our indexer is empty we're most likely running Element the
|
||||||
|
@ -188,7 +189,7 @@ export default class EventIndex extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
private onRoomTimeline = async (
|
private onRoomTimeline = async (
|
||||||
ev: MatrixEvent,
|
ev: MatrixEvent,
|
||||||
room: Room | null,
|
room: Room | undefined,
|
||||||
toStartOfTimeline: boolean,
|
toStartOfTimeline: boolean,
|
||||||
removed: boolean,
|
removed: boolean,
|
||||||
data: IRoomTimelineData,
|
data: IRoomTimelineData,
|
||||||
|
@ -198,7 +199,7 @@ export default class EventIndex extends EventEmitter {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
|
|
||||||
// We only index encrypted rooms locally.
|
// We only index encrypted rooms locally.
|
||||||
if (!client.isRoomEncrypted(ev.getRoomId())) return;
|
if (!client.isRoomEncrypted(ev.getRoomId()!)) return;
|
||||||
|
|
||||||
if (ev.isRedaction()) {
|
if (ev.isRedaction()) {
|
||||||
return this.redactEvent(ev);
|
return this.redactEvent(ev);
|
||||||
|
@ -228,7 +229,7 @@ export default class EventIndex extends EventEmitter {
|
||||||
* We cannot rely on Room.redaction as this only fires if the redaction applied to an event the js-sdk has loaded.
|
* We cannot rely on Room.redaction as this only fires if the redaction applied to an event the js-sdk has loaded.
|
||||||
*/
|
*/
|
||||||
private redactEvent = async (ev: MatrixEvent): Promise<void> => {
|
private redactEvent = async (ev: MatrixEvent): Promise<void> => {
|
||||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await indexManager.deleteEvent(ev.getAssociatedId());
|
await indexManager.deleteEvent(ev.getAssociatedId());
|
||||||
|
@ -321,15 +322,15 @@ export default class EventIndex extends EventEmitter {
|
||||||
* @param {MatrixEvent} ev The event that should be added to the index.
|
* @param {MatrixEvent} ev The event that should be added to the index.
|
||||||
*/
|
*/
|
||||||
private async addLiveEventToIndex(ev: MatrixEvent): Promise<void> {
|
private async addLiveEventToIndex(ev: MatrixEvent): Promise<void> {
|
||||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
||||||
|
|
||||||
if (!this.isValidEvent(ev)) return;
|
if (!indexManager || !this.isValidEvent(ev)) return;
|
||||||
|
|
||||||
const e = this.eventToJson(ev);
|
const e = this.eventToJson(ev);
|
||||||
|
|
||||||
const profile = {
|
const profile = {
|
||||||
displayname: ev.sender.rawDisplayName,
|
displayname: ev.sender?.rawDisplayName,
|
||||||
avatar_url: ev.sender.getMxcAvatarUrl(),
|
avatar_url: ev.sender?.getMxcAvatarUrl(),
|
||||||
};
|
};
|
||||||
|
|
||||||
await indexManager.addEventToIndex(e, profile);
|
await indexManager.addEventToIndex(e, profile);
|
||||||
|
@ -353,7 +354,7 @@ export default class EventIndex extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async addRoomCheckpoint(roomId: string, fullCrawl = false): Promise<void> {
|
private async addRoomCheckpoint(roomId: string, fullCrawl = false): Promise<void> {
|
||||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const room = client.getRoom(roomId);
|
const room = client.getRoom(roomId);
|
||||||
|
|
||||||
|
@ -401,7 +402,7 @@ export default class EventIndex extends EventEmitter {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
||||||
|
|
||||||
this.crawler = {
|
this.crawler = {
|
||||||
cancel: () => {
|
cancel: () => {
|
||||||
|
@ -649,7 +650,7 @@ export default class EventIndex extends EventEmitter {
|
||||||
* task, and closes the index.
|
* task, and closes the index.
|
||||||
*/
|
*/
|
||||||
public async close(): Promise<void> {
|
public async close(): Promise<void> {
|
||||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
||||||
this.removeListeners();
|
this.removeListeners();
|
||||||
this.stopCrawler();
|
this.stopCrawler();
|
||||||
await indexManager.closeEventIndex();
|
await indexManager.closeEventIndex();
|
||||||
|
@ -665,7 +666,7 @@ export default class EventIndex extends EventEmitter {
|
||||||
* of search results once the search is done.
|
* of search results once the search is done.
|
||||||
*/
|
*/
|
||||||
public async search(searchArgs: ISearchArgs): Promise<IResultRoomEvents> {
|
public async search(searchArgs: ISearchArgs): Promise<IResultRoomEvents> {
|
||||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
||||||
return indexManager.searchEventIndex(searchArgs);
|
return indexManager.searchEventIndex(searchArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -693,11 +694,11 @@ export default class EventIndex extends EventEmitter {
|
||||||
public async loadFileEvents(
|
public async loadFileEvents(
|
||||||
room: Room,
|
room: Room,
|
||||||
limit = 10,
|
limit = 10,
|
||||||
fromEvent: string = null,
|
fromEvent?: string,
|
||||||
direction: string = EventTimeline.BACKWARDS,
|
direction: string = EventTimeline.BACKWARDS,
|
||||||
): Promise<MatrixEvent[]> {
|
): Promise<MatrixEvent[]> {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
||||||
|
|
||||||
const loadArgs: ILoadArgs = {
|
const loadArgs: ILoadArgs = {
|
||||||
roomId: room.roomId,
|
roomId: room.roomId,
|
||||||
|
@ -790,7 +791,7 @@ export default class EventIndex extends EventEmitter {
|
||||||
timeline: EventTimeline,
|
timeline: EventTimeline,
|
||||||
room: Room,
|
room: Room,
|
||||||
limit = 10,
|
limit = 10,
|
||||||
fromEvent: string = null,
|
fromEvent?: string,
|
||||||
direction: string = EventTimeline.BACKWARDS,
|
direction: string = EventTimeline.BACKWARDS,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction);
|
const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction);
|
||||||
|
@ -807,7 +808,7 @@ export default class EventIndex extends EventEmitter {
|
||||||
|
|
||||||
// Add the events to the timeline of the file panel.
|
// Add the events to the timeline of the file panel.
|
||||||
matrixEvents.forEach((e) => {
|
matrixEvents.forEach((e) => {
|
||||||
if (!timelineSet.eventIdToTimeline(e.getId())) {
|
if (!timelineSet.eventIdToTimeline(e.getId()!)) {
|
||||||
timelineSet.addEventToTimeline(e, timeline, direction == EventTimeline.BACKWARDS);
|
timelineSet.addEventToTimeline(e, timeline, direction == EventTimeline.BACKWARDS);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -817,7 +818,7 @@ export default class EventIndex extends EventEmitter {
|
||||||
|
|
||||||
// Set the pagination token to the oldest event that we retrieved.
|
// Set the pagination token to the oldest event that we retrieved.
|
||||||
if (matrixEvents.length > 0) {
|
if (matrixEvents.length > 0) {
|
||||||
paginationToken = matrixEvents[matrixEvents.length - 1].getId();
|
paginationToken = matrixEvents[matrixEvents.length - 1].getId()!;
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -878,11 +879,11 @@ export default class EventIndex extends EventEmitter {
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
const timeline = timelineIndex.timeline;
|
const timeline = timelineIndex.timeline;
|
||||||
const timelineSet = timeline.getTimelineSet();
|
const timelineSet = timeline.getTimelineSet();
|
||||||
const token = timeline.getPaginationToken(direction);
|
const token = timeline.getPaginationToken(direction) ?? undefined;
|
||||||
|
|
||||||
const ret = await this.populateFileTimeline(timelineSet, timeline, room, limit, token, direction);
|
const ret = await this.populateFileTimeline(timelineSet, timeline, room, limit, token, direction);
|
||||||
|
|
||||||
timelineIndex.pendingPaginate = null;
|
timelineIndex.pendingPaginate = undefined;
|
||||||
timelineWindow.extend(direction, limit);
|
timelineWindow.extend(direction, limit);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -900,9 +901,9 @@ export default class EventIndex extends EventEmitter {
|
||||||
* @return {Promise<IIndexStats>} A promise that will resolve to the index
|
* @return {Promise<IIndexStats>} A promise that will resolve to the index
|
||||||
* statistics.
|
* statistics.
|
||||||
*/
|
*/
|
||||||
public async getStats(): Promise<IIndexStats> {
|
public async getStats(): Promise<IIndexStats | undefined> {
|
||||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
||||||
return indexManager.getStats();
|
return indexManager?.getStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -914,9 +915,9 @@ export default class EventIndex extends EventEmitter {
|
||||||
* @return {Promise<boolean>} Returns true if the index contains events for
|
* @return {Promise<boolean>} Returns true if the index contains events for
|
||||||
* the given room, false otherwise.
|
* the given room, false otherwise.
|
||||||
*/
|
*/
|
||||||
public async isRoomIndexed(roomId: string): Promise<boolean> {
|
public async isRoomIndexed(roomId: string): Promise<boolean | undefined> {
|
||||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
||||||
return indexManager.isRoomIndexed(roomId);
|
return indexManager?.isRoomIndexed(roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -36,8 +36,8 @@ const INDEX_VERSION = 1;
|
||||||
* you'll find a `EventIndex` hanging on the `EventIndexPeg`.
|
* you'll find a `EventIndex` hanging on the `EventIndexPeg`.
|
||||||
*/
|
*/
|
||||||
export class EventIndexPeg {
|
export class EventIndexPeg {
|
||||||
public index: EventIndex = null;
|
public index: EventIndex | null = null;
|
||||||
public error: Error = null;
|
public error: Error | null = null;
|
||||||
|
|
||||||
private _supportIsInstalled = false;
|
private _supportIsInstalled = false;
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ export class EventIndexPeg {
|
||||||
* EventIndex was successfully initialized, false otherwise.
|
* EventIndex was successfully initialized, false otherwise.
|
||||||
*/
|
*/
|
||||||
public async init(): Promise<boolean> {
|
public async init(): Promise<boolean> {
|
||||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
||||||
if (!indexManager) {
|
if (!indexManager) {
|
||||||
logger.log("EventIndex: Platform doesn't support event indexing, not initializing.");
|
logger.log("EventIndex: Platform doesn't support event indexing, not initializing.");
|
||||||
return false;
|
return false;
|
||||||
|
@ -78,11 +78,14 @@ export class EventIndexPeg {
|
||||||
*/
|
*/
|
||||||
public async initEventIndex(): Promise<boolean> {
|
public async initEventIndex(): Promise<boolean> {
|
||||||
const index = new EventIndex();
|
const index = new EventIndex();
|
||||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
|
if (!indexManager || !client) {
|
||||||
|
throw new Error("Unable to init event index");
|
||||||
|
}
|
||||||
|
|
||||||
const userId = client.getUserId();
|
const userId = client.getUserId()!;
|
||||||
const deviceId = client.getDeviceId();
|
const deviceId = client.getDeviceId()!;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await indexManager.initEventIndex(userId, deviceId);
|
await indexManager.initEventIndex(userId, deviceId);
|
||||||
|
@ -120,7 +123,7 @@ export class EventIndexPeg {
|
||||||
* does not mean that support is installed.
|
* does not mean that support is installed.
|
||||||
*/
|
*/
|
||||||
public platformHasSupport(): boolean {
|
public platformHasSupport(): boolean {
|
||||||
return PlatformPeg.get().getEventIndexingManager() !== null;
|
return PlatformPeg.get()?.getEventIndexingManager() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -141,7 +144,7 @@ export class EventIndexPeg {
|
||||||
*
|
*
|
||||||
* @return {EventIndex} The current event index.
|
* @return {EventIndex} The current event index.
|
||||||
*/
|
*/
|
||||||
public get(): EventIndex {
|
public get(): EventIndex | null {
|
||||||
return this.index;
|
return this.index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,9 +181,9 @@ export class EventIndexPeg {
|
||||||
* deleted.
|
* deleted.
|
||||||
*/
|
*/
|
||||||
public async deleteEventIndex(): Promise<void> {
|
public async deleteEventIndex(): Promise<void> {
|
||||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
||||||
|
|
||||||
if (indexManager !== null) {
|
if (indexManager) {
|
||||||
await this.unset();
|
await this.unset();
|
||||||
logger.log("EventIndex: Deleting event index.");
|
logger.log("EventIndex: Deleting event index.");
|
||||||
await indexManager.deleteEventIndex();
|
await indexManager.deleteEventIndex();
|
||||||
|
|
|
@ -33,18 +33,13 @@ export enum Kind {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IntegrationManagerInstance {
|
export class IntegrationManagerInstance {
|
||||||
public readonly apiUrl: string;
|
|
||||||
public readonly uiUrl: string;
|
|
||||||
public readonly kind: string;
|
|
||||||
public readonly id: string; // only applicable in some cases
|
|
||||||
|
|
||||||
// Per the spec: UI URL is optional.
|
// Per the spec: UI URL is optional.
|
||||||
public constructor(kind: string, apiUrl: string, uiUrl: string = apiUrl, id?: string) {
|
public constructor(
|
||||||
this.kind = kind;
|
public readonly kind: string,
|
||||||
this.apiUrl = apiUrl;
|
public readonly apiUrl: string,
|
||||||
this.uiUrl = uiUrl;
|
public readonly uiUrl: string = apiUrl,
|
||||||
this.id = id;
|
public readonly id?: string, // only applicable in some cases
|
||||||
}
|
) {}
|
||||||
|
|
||||||
public get name(): string {
|
public get name(): string {
|
||||||
const parsed = url.parse(this.uiUrl);
|
const parsed = url.parse(this.uiUrl);
|
||||||
|
@ -62,7 +57,7 @@ export class IntegrationManagerInstance {
|
||||||
return new ScalarAuthClient(this.apiUrl, this.uiUrl);
|
return new ScalarAuthClient(this.apiUrl, this.uiUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async open(room: Room = null, screen: string = null, integrationId: string = null): Promise<void> {
|
public async open(room: Room, screen?: string, integrationId?: string): Promise<void> {
|
||||||
if (!SettingsStore.getValue("integrationProvisioning")) {
|
if (!SettingsStore.getValue("integrationProvisioning")) {
|
||||||
return IntegrationManagers.sharedInstance().showDisabledDialog();
|
return IntegrationManagers.sharedInstance().showDisabledDialog();
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ export class IntegrationManagers {
|
||||||
|
|
||||||
private managers: IntegrationManagerInstance[] = [];
|
private managers: IntegrationManagerInstance[] = [];
|
||||||
private client: MatrixClient;
|
private client: MatrixClient;
|
||||||
private primaryManager: IntegrationManagerInstance;
|
private primaryManager: IntegrationManagerInstance | null;
|
||||||
|
|
||||||
public static sharedInstance(): IntegrationManagers {
|
public static sharedInstance(): IntegrationManagers {
|
||||||
if (!IntegrationManagers.instance) {
|
if (!IntegrationManagers.instance) {
|
||||||
|
@ -146,7 +146,7 @@ export class IntegrationManagers {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getOrderedManagers(): IntegrationManagerInstance[] {
|
public getOrderedManagers(): IntegrationManagerInstance[] {
|
||||||
const ordered = [];
|
const ordered: IntegrationManagerInstance[] = [];
|
||||||
for (const kind of KIND_PREFERENCE) {
|
for (const kind of KIND_PREFERENCE) {
|
||||||
const managers = this.managers.filter((m) => m.kind === kind);
|
const managers = this.managers.filter((m) => m.kind === kind);
|
||||||
if (!managers || !managers.length) continue;
|
if (!managers || !managers.length) continue;
|
||||||
|
@ -161,7 +161,7 @@ export class IntegrationManagers {
|
||||||
return ordered;
|
return ordered;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPrimaryManager(): IntegrationManagerInstance {
|
public getPrimaryManager(): IntegrationManagerInstance | null {
|
||||||
if (this.hasManager()) {
|
if (this.hasManager()) {
|
||||||
if (this.primaryManager) return this.primaryManager;
|
if (this.primaryManager) return this.primaryManager;
|
||||||
|
|
||||||
|
@ -195,7 +195,7 @@ export class IntegrationManagers {
|
||||||
* @returns {Promise<IntegrationManagerInstance>} Resolves to an integration manager instance,
|
* @returns {Promise<IntegrationManagerInstance>} Resolves to an integration manager instance,
|
||||||
* or null if none was found.
|
* or null if none was found.
|
||||||
*/
|
*/
|
||||||
public async tryDiscoverManager(domainName: string): Promise<IntegrationManagerInstance> {
|
public async tryDiscoverManager(domainName: string): Promise<IntegrationManagerInstance | null> {
|
||||||
logger.log("Looking up integration manager via .well-known");
|
logger.log("Looking up integration manager via .well-known");
|
||||||
if (domainName.startsWith("http:") || domainName.startsWith("https:")) {
|
if (domainName.startsWith("http:") || domainName.startsWith("https:")) {
|
||||||
// trim off the scheme and just use the domain
|
// trim off the scheme and just use the domain
|
||||||
|
|
|
@ -194,7 +194,7 @@ const annotateStrings = (result: TranslatedString, translationKey: string): Tran
|
||||||
*/
|
*/
|
||||||
// eslint-next-line @typescript-eslint/naming-convention
|
// eslint-next-line @typescript-eslint/naming-convention
|
||||||
export function _t(text: string, variables?: IVariables): string;
|
export function _t(text: string, variables?: IVariables): string;
|
||||||
export function _t(text: string, variables: IVariables, tags: Tags): React.ReactNode;
|
export function _t(text: string, variables: IVariables | undefined, tags: Tags): React.ReactNode;
|
||||||
export function _t(text: string, variables?: IVariables, tags?: Tags): TranslatedString {
|
export function _t(text: string, variables?: IVariables, tags?: Tags): TranslatedString {
|
||||||
// The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution)
|
// The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution)
|
||||||
const { translated } = safeCounterpartTranslate(text, variables);
|
const { translated } = safeCounterpartTranslate(text, variables);
|
||||||
|
|
|
@ -19,7 +19,7 @@ import * as linkifyjs from "linkifyjs";
|
||||||
import { Opts, registerCustomProtocol, registerPlugin } from "linkifyjs";
|
import { Opts, registerCustomProtocol, registerPlugin } from "linkifyjs";
|
||||||
import linkifyElement from "linkify-element";
|
import linkifyElement from "linkify-element";
|
||||||
import linkifyString from "linkify-string";
|
import linkifyString from "linkify-string";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { User } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
parsePermalink,
|
parsePermalink,
|
||||||
|
@ -105,13 +105,9 @@ function matrixOpaqueIdLinkifyParser({
|
||||||
|
|
||||||
function onUserClick(event: MouseEvent, userId: string): void {
|
function onUserClick(event: MouseEvent, userId: string): void {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const member = new RoomMember(null, userId);
|
|
||||||
if (!member) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dis.dispatch<ViewUserPayload>({
|
dis.dispatch<ViewUserPayload>({
|
||||||
action: Action.ViewUser,
|
action: Action.ViewUser,
|
||||||
member: member,
|
member: new User(userId),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -101,10 +101,10 @@ export class ProxiedModuleApi implements ModuleApi {
|
||||||
password: string,
|
password: string,
|
||||||
displayName?: string,
|
displayName?: string,
|
||||||
): Promise<AccountAuthInfo> {
|
): Promise<AccountAuthInfo> {
|
||||||
const hsUrl = SdkConfig.get("validated_server_config").hsUrl;
|
const hsUrl = SdkConfig.get("validated_server_config")?.hsUrl;
|
||||||
const client = Matrix.createClient({ baseUrl: hsUrl });
|
const client = Matrix.createClient({ baseUrl: hsUrl });
|
||||||
const deviceName =
|
const deviceName =
|
||||||
SdkConfig.get("default_device_display_name") || PlatformPeg.get().getDefaultDeviceDisplayName();
|
SdkConfig.get("default_device_display_name") || PlatformPeg.get()?.getDefaultDeviceDisplayName();
|
||||||
const req: IRegisterRequestParams = {
|
const req: IRegisterRequestParams = {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
|
@ -134,9 +134,9 @@ export class ProxiedModuleApi implements ModuleApi {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
homeserverUrl: hsUrl,
|
homeserverUrl: hsUrl,
|
||||||
userId: creds.user_id,
|
userId: creds.user_id!,
|
||||||
deviceId: creds.device_id,
|
deviceId: creds.device_id!,
|
||||||
accessToken: creds.access_token,
|
accessToken: creds.access_token!,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,8 +163,8 @@ export class ProxiedModuleApi implements ModuleApi {
|
||||||
navigateToPermalink(uri);
|
navigateToPermalink(uri);
|
||||||
|
|
||||||
const parts = parsePermalink(uri);
|
const parts = parsePermalink(uri);
|
||||||
if (parts.roomIdOrAlias && andJoin) {
|
if (parts?.roomIdOrAlias && andJoin) {
|
||||||
let roomId = parts.roomIdOrAlias;
|
let roomId: string | undefined = parts.roomIdOrAlias;
|
||||||
let servers = parts.viaServers;
|
let servers = parts.viaServers;
|
||||||
if (roomId.startsWith("#")) {
|
if (roomId.startsWith("#")) {
|
||||||
roomId = getCachedRoomIDForAlias(parts.roomIdOrAlias);
|
roomId = getCachedRoomIDForAlias(parts.roomIdOrAlias);
|
||||||
|
|
|
@ -53,6 +53,7 @@ export class PushRuleVectorState {
|
||||||
} else if (pushRuleVectorState === VectorState.Loud) {
|
} else if (pushRuleVectorState === VectorState.Loud) {
|
||||||
return StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND;
|
return StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND;
|
||||||
}
|
}
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -76,7 +76,7 @@ export class ConsoleLogger {
|
||||||
}
|
}
|
||||||
|
|
||||||
public bypassRageshake(fnName: LogFunctionName, ...args: (Error | DOMException | object | string)[]): void {
|
public bypassRageshake(fnName: LogFunctionName, ...args: (Error | DOMException | object | string)[]): void {
|
||||||
this.originalFunctions[fnName](...args);
|
this.originalFunctions[fnName]?.(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public log(level: string, ...args: (Error | DOMException | object | string)[]): void {
|
public log(level: string, ...args: (Error | DOMException | object | string)[]): void {
|
||||||
|
@ -152,7 +152,7 @@ export class IndexedDBLogStore {
|
||||||
};
|
};
|
||||||
|
|
||||||
req.onerror = () => {
|
req.onerror = () => {
|
||||||
const err = "Failed to open log database: " + req.error.name;
|
const err = "Failed to open log database: " + req.error?.name;
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
reject(new Error(err));
|
reject(new Error(err));
|
||||||
};
|
};
|
||||||
|
@ -234,7 +234,7 @@ export class IndexedDBLogStore {
|
||||||
};
|
};
|
||||||
txn.onerror = () => {
|
txn.onerror = () => {
|
||||||
logger.error("Failed to flush logs : ", txn.error);
|
logger.error("Failed to flush logs : ", txn.error);
|
||||||
reject(new Error("Failed to write logs: " + txn.error.message));
|
reject(new Error("Failed to write logs: " + txn.error?.message));
|
||||||
};
|
};
|
||||||
objStore.add(this.generateLogEntry(lines));
|
objStore.add(this.generateLogEntry(lines));
|
||||||
const lastModStore = txn.objectStore("logslastmod");
|
const lastModStore = txn.objectStore("logslastmod");
|
||||||
|
@ -267,7 +267,7 @@ export class IndexedDBLogStore {
|
||||||
const query = objectStore.index("id").openCursor(IDBKeyRange.only(id), "prev");
|
const query = objectStore.index("id").openCursor(IDBKeyRange.only(id), "prev");
|
||||||
let lines = "";
|
let lines = "";
|
||||||
query.onerror = () => {
|
query.onerror = () => {
|
||||||
reject(new Error("Query failed: " + query.error.message));
|
reject(new Error("Query failed: " + query.error?.message));
|
||||||
};
|
};
|
||||||
query.onsuccess = () => {
|
query.onsuccess = () => {
|
||||||
const cursor = query.result;
|
const cursor = query.result;
|
||||||
|
@ -322,7 +322,7 @@ export class IndexedDBLogStore {
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
txn.onerror = () => {
|
txn.onerror = () => {
|
||||||
reject(new Error("Failed to delete logs for " + `'${id}' : ${query.error.message}`));
|
reject(new Error("Failed to delete logs for " + `'${id}' : ${query.error?.message}`));
|
||||||
};
|
};
|
||||||
// delete last modified entries
|
// delete last modified entries
|
||||||
const lastModStore = txn.objectStore("logslastmod");
|
const lastModStore = txn.objectStore("logslastmod");
|
||||||
|
@ -401,14 +401,14 @@ export class IndexedDBLogStore {
|
||||||
*/
|
*/
|
||||||
function selectQuery<T>(
|
function selectQuery<T>(
|
||||||
store: IDBIndex | IDBObjectStore,
|
store: IDBIndex | IDBObjectStore,
|
||||||
keyRange: IDBKeyRange,
|
keyRange: IDBKeyRange | undefined,
|
||||||
resultMapper: (cursor: IDBCursorWithValue) => T,
|
resultMapper: (cursor: IDBCursorWithValue) => T,
|
||||||
): Promise<T[]> {
|
): Promise<T[]> {
|
||||||
const query = store.openCursor(keyRange);
|
const query = store.openCursor(keyRange);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const results: T[] = [];
|
const results: T[] = [];
|
||||||
query.onerror = () => {
|
query.onerror = () => {
|
||||||
reject(new Error("Query failed: " + query.error.message));
|
reject(new Error("Query failed: " + query.error?.message));
|
||||||
};
|
};
|
||||||
// collect results
|
// collect results
|
||||||
query.onsuccess = () => {
|
query.onsuccess = () => {
|
||||||
|
|
|
@ -437,7 +437,7 @@ export default class SettingsStore {
|
||||||
level: SettingLevel,
|
level: SettingLevel,
|
||||||
roomId: string | null,
|
roomId: string | null,
|
||||||
calculatedValue: any,
|
calculatedValue: any,
|
||||||
calculatedAtLevel: SettingLevel,
|
calculatedAtLevel: SettingLevel | null,
|
||||||
): any {
|
): any {
|
||||||
let resultingValue = calculatedValue;
|
let resultingValue = calculatedValue;
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ export default class IncompatibleController extends SettingController {
|
||||||
level: SettingLevel,
|
level: SettingLevel,
|
||||||
roomId: string,
|
roomId: string,
|
||||||
calculatedValue: any,
|
calculatedValue: any,
|
||||||
calculatedAtLevel: SettingLevel,
|
calculatedAtLevel: SettingLevel | null,
|
||||||
): any {
|
): any {
|
||||||
if (this.incompatibleSetting) {
|
if (this.incompatibleSetting) {
|
||||||
return this.forcedValue;
|
return this.forcedValue;
|
||||||
|
|
|
@ -53,7 +53,7 @@ export class NotificationsEnabledController extends SettingController {
|
||||||
level: SettingLevel,
|
level: SettingLevel,
|
||||||
roomId: string,
|
roomId: string,
|
||||||
calculatedValue: any,
|
calculatedValue: any,
|
||||||
calculatedAtLevel: SettingLevel,
|
calculatedAtLevel: SettingLevel | null,
|
||||||
): any {
|
): any {
|
||||||
if (!getNotifier().isPossible()) return false;
|
if (!getNotifier().isPossible()) return false;
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ export class OrderedMultiController extends SettingController {
|
||||||
level: SettingLevel,
|
level: SettingLevel,
|
||||||
roomId: string,
|
roomId: string,
|
||||||
calculatedValue: any,
|
calculatedValue: any,
|
||||||
calculatedAtLevel: SettingLevel,
|
calculatedAtLevel: SettingLevel | null,
|
||||||
): any {
|
): any {
|
||||||
for (const controller of this.controllers) {
|
for (const controller of this.controllers) {
|
||||||
const override = controller.getValueOverride(level, roomId, calculatedValue, calculatedAtLevel);
|
const override = controller.getValueOverride(level, roomId, calculatedValue, calculatedAtLevel);
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default class ReducedMotionController extends SettingController {
|
||||||
level: SettingLevel,
|
level: SettingLevel,
|
||||||
roomId: string,
|
roomId: string,
|
||||||
calculatedValue: any,
|
calculatedValue: any,
|
||||||
calculatedAtLevel: SettingLevel,
|
calculatedAtLevel: SettingLevel | null,
|
||||||
): any {
|
): any {
|
||||||
if (this.prefersReducedMotion()) {
|
if (this.prefersReducedMotion()) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -41,7 +41,7 @@ export default abstract class SettingController {
|
||||||
level: SettingLevel,
|
level: SettingLevel,
|
||||||
roomId: string | null,
|
roomId: string | null,
|
||||||
calculatedValue: any,
|
calculatedValue: any,
|
||||||
calculatedAtLevel: SettingLevel,
|
calculatedAtLevel: SettingLevel | null,
|
||||||
): any {
|
): any {
|
||||||
return null; // no override
|
return null; // no override
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ export default class ThemeController extends SettingController {
|
||||||
level: SettingLevel,
|
level: SettingLevel,
|
||||||
roomId: string,
|
roomId: string,
|
||||||
calculatedValue: any,
|
calculatedValue: any,
|
||||||
calculatedAtLevel: SettingLevel,
|
calculatedAtLevel: SettingLevel | null,
|
||||||
): any {
|
): any {
|
||||||
if (!calculatedValue) return null; // Don't override null themes
|
if (!calculatedValue) return null; // Don't override null themes
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default class UIFeatureController extends SettingController {
|
||||||
level: SettingLevel,
|
level: SettingLevel,
|
||||||
roomId: string,
|
roomId: string,
|
||||||
calculatedValue: any,
|
calculatedValue: any,
|
||||||
calculatedAtLevel: SettingLevel,
|
calculatedAtLevel: SettingLevel | null,
|
||||||
): any {
|
): any {
|
||||||
if (this.settingDisabled) {
|
if (this.settingDisabled) {
|
||||||
// per the docs: we force a disabled state when the feature isn't active
|
// per the docs: we force a disabled state when the feature isn't active
|
||||||
|
|
|
@ -22,7 +22,7 @@ const SIZE_LARGE = { w: 800, h: 600 };
|
||||||
const SIZE_NORMAL_LANDSCAPE = { w: 324, h: 324 }; // for w > h
|
const SIZE_NORMAL_LANDSCAPE = { w: 324, h: 324 }; // for w > h
|
||||||
const SIZE_NORMAL_PORTRAIT = { w: Math.ceil(324 * (9 / 16)), h: 324 }; // for h > w
|
const SIZE_NORMAL_PORTRAIT = { w: Math.ceil(324 * (9 / 16)), h: 324 }; // for h > w
|
||||||
|
|
||||||
type Dimensions = { w: number; h: number };
|
type Dimensions = { w?: number; h?: number };
|
||||||
|
|
||||||
export enum ImageSize {
|
export enum ImageSize {
|
||||||
Normal = "normal",
|
Normal = "normal",
|
||||||
|
@ -36,7 +36,7 @@ export enum ImageSize {
|
||||||
* @returns {Dimensions} The suggested maximum dimensions for the image
|
* @returns {Dimensions} The suggested maximum dimensions for the image
|
||||||
*/
|
*/
|
||||||
export function suggestedSize(size: ImageSize, contentSize: Dimensions, maxHeight?: number): Dimensions {
|
export function suggestedSize(size: ImageSize, contentSize: Dimensions, maxHeight?: number): Dimensions {
|
||||||
const aspectRatio = contentSize.w / contentSize.h;
|
const aspectRatio = contentSize.w! / contentSize.h!;
|
||||||
const portrait = aspectRatio < 1;
|
const portrait = aspectRatio < 1;
|
||||||
|
|
||||||
const maxSize = size === ImageSize.Large ? SIZE_LARGE : portrait ? SIZE_NORMAL_PORTRAIT : SIZE_NORMAL_LANDSCAPE;
|
const maxSize = size === ImageSize.Large ? SIZE_LARGE : portrait ? SIZE_NORMAL_PORTRAIT : SIZE_NORMAL_LANDSCAPE;
|
||||||
|
|
|
@ -38,7 +38,7 @@ export class RoomScrollStateStore {
|
||||||
// from the focussedEvent.
|
// from the focussedEvent.
|
||||||
private scrollStateMap = new Map<string, ScrollState>();
|
private scrollStateMap = new Map<string, ScrollState>();
|
||||||
|
|
||||||
public getScrollState(roomId: string): ScrollState {
|
public getScrollState(roomId: string): ScrollState | undefined {
|
||||||
return this.scrollStateMap.get(roomId);
|
return this.scrollStateMap.get(roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -712,7 +712,7 @@ export class RoomViewStore extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The mxEvent if one is currently being replied to/quoted
|
// The mxEvent if one is currently being replied to/quoted
|
||||||
public getQuotingEvent(): Optional<MatrixEvent> {
|
public getQuotingEvent(): MatrixEvent | null {
|
||||||
return this.state.replyingToEvent;
|
return this.state.replyingToEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { MSC3575Filter, SlidingSyncEvent } from "matrix-js-sdk/src/sliding-sync";
|
import { MSC3575Filter, SlidingSyncEvent } from "matrix-js-sdk/src/sliding-sync";
|
||||||
|
import { Optional } from "matrix-events-sdk";
|
||||||
|
|
||||||
import { RoomUpdateCause, TagID, OrderedDefaultTagIDs, DefaultTagID } from "./models";
|
import { RoomUpdateCause, TagID, OrderedDefaultTagIDs, DefaultTagID } from "./models";
|
||||||
import { ITagMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models";
|
import { ITagMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models";
|
||||||
|
@ -79,12 +80,11 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
|
||||||
private tagIdToSortAlgo: Record<TagID, SortAlgorithm> = {};
|
private tagIdToSortAlgo: Record<TagID, SortAlgorithm> = {};
|
||||||
private tagMap: ITagMap = {};
|
private tagMap: ITagMap = {};
|
||||||
private counts: Record<TagID, number> = {};
|
private counts: Record<TagID, number> = {};
|
||||||
private stickyRoomId: string | null;
|
private stickyRoomId: Optional<string>;
|
||||||
|
|
||||||
public constructor(dis: MatrixDispatcher, private readonly context: SdkContextClass) {
|
public constructor(dis: MatrixDispatcher, private readonly context: SdkContextClass) {
|
||||||
super(dis);
|
super(dis);
|
||||||
this.setMaxListeners(20); // RoomList + LeftPanel + 8xRoomSubList + spares
|
this.setMaxListeners(20); // RoomList + LeftPanel + 8xRoomSubList + spares
|
||||||
this.stickyRoomId = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setTagSorting(tagId: TagID, sort: SortAlgorithm): Promise<void> {
|
public async setTagSorting(tagId: TagID, sort: SortAlgorithm): Promise<void> {
|
||||||
|
|
|
@ -24,14 +24,17 @@ import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||||
|
|
||||||
export class ReactionEventPreview implements IPreview {
|
export class ReactionEventPreview implements IPreview {
|
||||||
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string {
|
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null {
|
||||||
const showDms = SettingsStore.getValue("feature_roomlist_preview_reactions_dms");
|
const showDms = SettingsStore.getValue("feature_roomlist_preview_reactions_dms");
|
||||||
const showAll = SettingsStore.getValue("feature_roomlist_preview_reactions_all");
|
const showAll = SettingsStore.getValue("feature_roomlist_preview_reactions_all");
|
||||||
|
|
||||||
|
const roomId = event.getRoomId();
|
||||||
|
if (!roomId) return null; // not a room event
|
||||||
|
|
||||||
// If we're not showing all reactions, see if we're showing DMs instead
|
// If we're not showing all reactions, see if we're showing DMs instead
|
||||||
if (!showAll) {
|
if (!showAll) {
|
||||||
// If we're not showing reactions on DMs, or we are and the room isn't a DM, skip
|
// If we're not showing reactions on DMs, or we are and the room isn't a DM, skip
|
||||||
if (!(showDms && DMRoomMap.shared().getUserIdForRoomId(event.getRoomId()))) {
|
if (!(showDms && DMRoomMap.shared().getUserIdForRoomId(roomId))) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +45,7 @@ export class ReactionEventPreview implements IPreview {
|
||||||
const reaction = relation.key;
|
const reaction = relation.key;
|
||||||
if (!reaction) return null; // invalid reaction (unknown format)
|
if (!reaction) return null; // invalid reaction (unknown format)
|
||||||
|
|
||||||
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
|
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(roomId, tagId)) {
|
||||||
return reaction;
|
return reaction;
|
||||||
} else {
|
} else {
|
||||||
return _t("%(senderName)s: %(reaction)s", { senderName: getSenderName(event), reaction });
|
return _t("%(senderName)s: %(reaction)s", { senderName: getSenderName(event), reaction });
|
||||||
|
|
|
@ -27,7 +27,7 @@ export function isSelf(event: MatrixEvent): boolean {
|
||||||
return event.getSender() === selfUserId;
|
return event.getSender() === selfUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shouldPrefixMessagesIn(roomId: string, tagId: TagID): boolean {
|
export function shouldPrefixMessagesIn(roomId: string, tagId?: TagID): boolean {
|
||||||
if (tagId !== DefaultTagID.DM) return true;
|
if (tagId !== DefaultTagID.DM) return true;
|
||||||
|
|
||||||
// We don't prefix anything in 1:1s
|
// We don't prefix anything in 1:1s
|
||||||
|
|
|
@ -137,7 +137,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
userIdsBySpace: new Map<Room["roomId"], Set<string>>(),
|
userIdsBySpace: new Map<Room["roomId"], Set<string>>(),
|
||||||
};
|
};
|
||||||
// The space currently selected in the Space Panel
|
// The space currently selected in the Space Panel
|
||||||
private _activeSpace?: SpaceKey = MetaSpace.Home; // set properly by onReady
|
private _activeSpace: SpaceKey = MetaSpace.Home; // set properly by onReady
|
||||||
private _suggestedRooms: ISuggestedRoom[] = [];
|
private _suggestedRooms: ISuggestedRoom[] = [];
|
||||||
private _invitedSpaces = new Set<Room>();
|
private _invitedSpaces = new Set<Room>();
|
||||||
private spaceOrderLocalEchoMap = new Map<string, string>();
|
private spaceOrderLocalEchoMap = new Map<string, string>();
|
||||||
|
@ -812,13 +812,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
const spaceDiff = mapDiff(prevChildSpacesBySpace, this.childSpacesBySpace);
|
const spaceDiff = mapDiff(prevChildSpacesBySpace, this.childSpacesBySpace);
|
||||||
// filter out keys which changed by reference only by checking whether the sets differ
|
// filter out keys which changed by reference only by checking whether the sets differ
|
||||||
const roomsChanged = roomDiff.changed.filter((k) => {
|
const roomsChanged = roomDiff.changed.filter((k) => {
|
||||||
return setHasDiff(prevRoomsBySpace.get(k), this.roomIdsBySpace.get(k));
|
return setHasDiff(prevRoomsBySpace.get(k)!, this.roomIdsBySpace.get(k)!);
|
||||||
});
|
});
|
||||||
const usersChanged = userDiff.changed.filter((k) => {
|
const usersChanged = userDiff.changed.filter((k) => {
|
||||||
return setHasDiff(prevUsersBySpace.get(k), this.userIdsBySpace.get(k));
|
return setHasDiff(prevUsersBySpace.get(k)!, this.userIdsBySpace.get(k)!);
|
||||||
});
|
});
|
||||||
const spacesChanged = spaceDiff.changed.filter((k) => {
|
const spacesChanged = spaceDiff.changed.filter((k) => {
|
||||||
return setHasDiff(prevChildSpacesBySpace.get(k), this.childSpacesBySpace.get(k));
|
return setHasDiff(prevChildSpacesBySpace.get(k)!, this.childSpacesBySpace.get(k)!);
|
||||||
});
|
});
|
||||||
|
|
||||||
const changeSet = new Set([
|
const changeSet = new Set([
|
||||||
|
@ -1142,9 +1142,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
// restore selected state from last session if any and still valid
|
// restore selected state from last session if any and still valid
|
||||||
const lastSpaceId = window.localStorage.getItem(ACTIVE_SPACE_LS_KEY);
|
const lastSpaceId = window.localStorage.getItem(ACTIVE_SPACE_LS_KEY);
|
||||||
const valid =
|
const valid =
|
||||||
lastSpaceId && !isMetaSpace(lastSpaceId)
|
lastSpaceId &&
|
||||||
? this.matrixClient.getRoom(lastSpaceId)
|
(!isMetaSpace(lastSpaceId) ? this.matrixClient.getRoom(lastSpaceId) : enabledMetaSpaces[lastSpaceId]);
|
||||||
: enabledMetaSpaces[lastSpaceId];
|
|
||||||
if (valid) {
|
if (valid) {
|
||||||
// don't context switch here as it may break permalinks
|
// don't context switch here as it may break permalinks
|
||||||
this.setActiveSpace(lastSpaceId, false);
|
this.setActiveSpace(lastSpaceId, false);
|
||||||
|
@ -1285,7 +1284,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
|
|
||||||
public getNotificationState(key: SpaceKey): SpaceNotificationState {
|
public getNotificationState(key: SpaceKey): SpaceNotificationState {
|
||||||
if (this.notificationStateMap.has(key)) {
|
if (this.notificationStateMap.has(key)) {
|
||||||
return this.notificationStateMap.get(key);
|
return this.notificationStateMap.get(key)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = new SpaceNotificationState(getRoomFn);
|
const state = new SpaceNotificationState(getRoomFn);
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const getSpaceCollapsedKey = (roomId: string, parents: Set<string>): string => {
|
const getSpaceCollapsedKey = (roomId: string, parents?: Set<string>): string => {
|
||||||
const separator = "/";
|
const separator = "/";
|
||||||
let path = "";
|
let path = "";
|
||||||
if (parents) {
|
if (parents) {
|
||||||
|
@ -35,12 +35,12 @@ export default class SpaceTreeLevelLayoutStore {
|
||||||
return SpaceTreeLevelLayoutStore.internalInstance;
|
return SpaceTreeLevelLayoutStore.internalInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setSpaceCollapsedState(roomId: string, parents: Set<string>, collapsed: boolean): void {
|
public setSpaceCollapsedState(roomId: string, parents: Set<string> | undefined, collapsed: boolean): void {
|
||||||
// XXX: localStorage doesn't allow booleans
|
// XXX: localStorage doesn't allow booleans
|
||||||
localStorage.setItem(getSpaceCollapsedKey(roomId, parents), collapsed.toString());
|
localStorage.setItem(getSpaceCollapsedKey(roomId, parents), collapsed.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSpaceCollapsedState(roomId: string, parents: Set<string>, fallback: boolean): boolean {
|
public getSpaceCollapsedState(roomId: string, parents: Set<string> | undefined, fallback: boolean): boolean {
|
||||||
const collapsedLocalStorage = localStorage.getItem(getSpaceCollapsedKey(roomId, parents));
|
const collapsedLocalStorage = localStorage.getItem(getSpaceCollapsedKey(roomId, parents));
|
||||||
// XXX: localStorage doesn't allow booleans
|
// XXX: localStorage doesn't allow booleans
|
||||||
return collapsedLocalStorage ? collapsedLocalStorage === "true" : fallback;
|
return collapsedLocalStorage ? collapsedLocalStorage === "true" : fallback;
|
||||||
|
|
|
@ -216,8 +216,8 @@ export class StopGapWidget extends EventEmitter {
|
||||||
const defaults: ITemplateParams = {
|
const defaults: ITemplateParams = {
|
||||||
widgetRoomId: this.roomId,
|
widgetRoomId: this.roomId,
|
||||||
currentUserId: this.client.getUserId()!,
|
currentUserId: this.client.getUserId()!,
|
||||||
userDisplayName: OwnProfileStore.instance.displayName,
|
userDisplayName: OwnProfileStore.instance.displayName ?? undefined,
|
||||||
userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(),
|
userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl() ?? undefined,
|
||||||
clientId: ELEMENT_CLIENT_ID,
|
clientId: ELEMENT_CLIENT_ID,
|
||||||
clientTheme: SettingsStore.getValue("theme"),
|
clientTheme: SettingsStore.getValue("theme"),
|
||||||
clientLanguage: getUserLanguage(),
|
clientLanguage: getUserLanguage(),
|
||||||
|
|
|
@ -477,7 +477,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
||||||
this.updateUserLayout(room, newLayout);
|
this.updateUserLayout(room, newLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasMaximisedWidget(room: Room): boolean {
|
public hasMaximisedWidget(room?: Room): boolean {
|
||||||
return this.getContainerWidgets(room, Container.Center).length > 0;
|
return this.getContainerWidgets(room, Container.Center).length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,11 +85,11 @@ export const showToast = (kind: Kind): void => {
|
||||||
|
|
||||||
const onAccept = async (): Promise<void> => {
|
const onAccept = async (): Promise<void> => {
|
||||||
if (kind === Kind.VERIFY_THIS_SESSION) {
|
if (kind === Kind.VERIFY_THIS_SESSION) {
|
||||||
Modal.createDialog(SetupEncryptionDialog, {}, null, /* priority = */ false, /* static = */ true);
|
Modal.createDialog(SetupEncryptionDialog, {}, undefined, /* priority = */ false, /* static = */ true);
|
||||||
} else {
|
} else {
|
||||||
const modal = Modal.createDialog(
|
const modal = Modal.createDialog(
|
||||||
Spinner,
|
Spinner,
|
||||||
null,
|
undefined,
|
||||||
"mx_Dialog_spinner",
|
"mx_Dialog_spinner",
|
||||||
/* priority */ false,
|
/* priority */ false,
|
||||||
/* static */ true,
|
/* static */ true,
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import SdkConfig from "../SdkConfig";
|
import SdkConfig from "../SdkConfig";
|
||||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||||
|
import { Policies } from "../Terms";
|
||||||
|
|
||||||
export function getDefaultIdentityServerUrl(): string {
|
export function getDefaultIdentityServerUrl(): string {
|
||||||
return SdkConfig.get("validated_server_config").isUrl;
|
return SdkConfig.get("validated_server_config").isUrl;
|
||||||
|
@ -33,7 +34,7 @@ export function setToDefaultIdentityServer(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function doesIdentityServerHaveTerms(fullUrl: string): Promise<boolean> {
|
export async function doesIdentityServerHaveTerms(fullUrl: string): Promise<boolean> {
|
||||||
let terms;
|
let terms: { policies?: Policies } | null;
|
||||||
try {
|
try {
|
||||||
terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IS, fullUrl);
|
terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IS, fullUrl);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -45,7 +46,7 @@ export async function doesIdentityServerHaveTerms(fullUrl: string): Promise<bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return terms && terms["policies"] && Object.keys(terms["policies"]).length > 0;
|
return !!terms?.["policies"] && Object.keys(terms["policies"]).length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doesAccountDataHaveIdentityServer(): boolean {
|
export function doesAccountDataHaveIdentityServer(): boolean {
|
||||||
|
|
|
@ -28,12 +28,12 @@ import { IDestroyable } from "./IDestroyable";
|
||||||
|
|
||||||
export class MediaEventHelper implements IDestroyable {
|
export class MediaEventHelper implements IDestroyable {
|
||||||
// Either an HTTP or Object URL (when encrypted) to the media.
|
// Either an HTTP or Object URL (when encrypted) to the media.
|
||||||
public readonly sourceUrl: LazyValue<string>;
|
public readonly sourceUrl: LazyValue<string | null>;
|
||||||
public readonly thumbnailUrl: LazyValue<string>;
|
public readonly thumbnailUrl: LazyValue<string | null>;
|
||||||
|
|
||||||
// Either the raw or decrypted (when encrypted) contents of the file.
|
// Either the raw or decrypted (when encrypted) contents of the file.
|
||||||
public readonly sourceBlob: LazyValue<Blob>;
|
public readonly sourceBlob: LazyValue<Blob>;
|
||||||
public readonly thumbnailBlob: LazyValue<Blob>;
|
public readonly thumbnailBlob: LazyValue<Blob | null>;
|
||||||
|
|
||||||
public readonly media: Media;
|
public readonly media: Media;
|
||||||
|
|
||||||
|
@ -56,12 +56,12 @@ export class MediaEventHelper implements IDestroyable {
|
||||||
|
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
if (this.media.isEncrypted) {
|
if (this.media.isEncrypted) {
|
||||||
if (this.sourceUrl.present) URL.revokeObjectURL(this.sourceUrl.cachedValue);
|
if (this.sourceUrl.cachedValue) URL.revokeObjectURL(this.sourceUrl.cachedValue);
|
||||||
if (this.thumbnailUrl.present) URL.revokeObjectURL(this.thumbnailUrl.cachedValue);
|
if (this.thumbnailUrl.cachedValue) URL.revokeObjectURL(this.thumbnailUrl.cachedValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private prepareSourceUrl = async (): Promise<string> => {
|
private prepareSourceUrl = async (): Promise<string | null> => {
|
||||||
if (this.media.isEncrypted) {
|
if (this.media.isEncrypted) {
|
||||||
const blob = await this.sourceBlob.value;
|
const blob = await this.sourceBlob.value;
|
||||||
return URL.createObjectURL(blob);
|
return URL.createObjectURL(blob);
|
||||||
|
@ -70,7 +70,7 @@ export class MediaEventHelper implements IDestroyable {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private prepareThumbnailUrl = async (): Promise<string> => {
|
private prepareThumbnailUrl = async (): Promise<string | null> => {
|
||||||
if (this.media.isEncrypted) {
|
if (this.media.isEncrypted) {
|
||||||
const blob = await this.thumbnailBlob.value;
|
const blob = await this.thumbnailBlob.value;
|
||||||
if (blob === null) return null;
|
if (blob === null) return null;
|
||||||
|
@ -83,12 +83,12 @@ export class MediaEventHelper implements IDestroyable {
|
||||||
private fetchSource = (): Promise<Blob> => {
|
private fetchSource = (): Promise<Blob> => {
|
||||||
if (this.media.isEncrypted) {
|
if (this.media.isEncrypted) {
|
||||||
const content = this.event.getContent<IMediaEventContent>();
|
const content = this.event.getContent<IMediaEventContent>();
|
||||||
return decryptFile(content.file, content.info);
|
return decryptFile(content.file!, content.info);
|
||||||
}
|
}
|
||||||
return this.media.downloadSource().then((r) => r.blob());
|
return this.media.downloadSource().then((r) => r.blob());
|
||||||
};
|
};
|
||||||
|
|
||||||
private fetchThumbnail = (): Promise<Blob> => {
|
private fetchThumbnail = (): Promise<Blob | null> => {
|
||||||
if (!this.media.hasThumbnail) return Promise.resolve(null);
|
if (!this.media.hasThumbnail) return Promise.resolve(null);
|
||||||
|
|
||||||
if (this.media.isEncrypted) {
|
if (this.media.isEncrypted) {
|
||||||
|
@ -113,7 +113,7 @@ export class MediaEventHelper implements IDestroyable {
|
||||||
|
|
||||||
const content = event.getContent();
|
const content = event.getContent();
|
||||||
const mediaMsgTypes: string[] = [MsgType.Video, MsgType.Audio, MsgType.Image, MsgType.File];
|
const mediaMsgTypes: string[] = [MsgType.Video, MsgType.Audio, MsgType.Image, MsgType.File];
|
||||||
if (mediaMsgTypes.includes(content.msgtype)) return true;
|
if (mediaMsgTypes.includes(content.msgtype!)) return true;
|
||||||
if (typeof content.url === "string") return true;
|
if (typeof content.url === "string") return true;
|
||||||
|
|
||||||
// Finally, it's probably not media
|
// Finally, it's probably not media
|
||||||
|
|
|
@ -96,7 +96,7 @@ export default class WidgetUtils {
|
||||||
* @param {[type]} testUrlString URL to check
|
* @param {[type]} testUrlString URL to check
|
||||||
* @return {Boolean} True if specified URL is a scalar URL
|
* @return {Boolean} True if specified URL is a scalar URL
|
||||||
*/
|
*/
|
||||||
public static isScalarUrl(testUrlString: string): boolean {
|
public static isScalarUrl(testUrlString?: string): boolean {
|
||||||
if (!testUrlString) {
|
if (!testUrlString) {
|
||||||
logger.error("Scalar URL check failed. No URL specified");
|
logger.error("Scalar URL check failed. No URL specified");
|
||||||
return false;
|
return false;
|
||||||
|
@ -554,7 +554,7 @@ export default class WidgetUtils {
|
||||||
// noinspection JSIgnoredPromiseFromCall
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
IntegrationManagers.sharedInstance()
|
IntegrationManagers.sharedInstance()
|
||||||
.getPrimaryManager()
|
.getPrimaryManager()
|
||||||
.open(room, "type_" + app.type, app.id);
|
?.open(room, "type_" + app.type, app.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static isManagedByManager(app: IApp): boolean {
|
public static isManagedByManager(app: IApp): boolean {
|
||||||
|
@ -563,7 +563,7 @@ export default class WidgetUtils {
|
||||||
if (managers.hasManager()) {
|
if (managers.hasManager()) {
|
||||||
// TODO: Pick the right manager for the widget
|
// TODO: Pick the right manager for the widget
|
||||||
const defaultManager = managers.getPrimaryManager();
|
const defaultManager = managers.getPrimaryManager();
|
||||||
return WidgetUtils.isScalarUrl(defaultManager.apiUrl);
|
return WidgetUtils.isScalarUrl(defaultManager?.apiUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -29,7 +29,7 @@ export const useBeacon = (beaconInfoEvent: MatrixEvent): Beacon | undefined => {
|
||||||
const beaconIdentifier = getBeaconInfoIdentifier(beaconInfoEvent);
|
const beaconIdentifier = getBeaconInfoIdentifier(beaconInfoEvent);
|
||||||
|
|
||||||
const room = matrixClient.getRoom(roomId);
|
const room = matrixClient.getRoom(roomId);
|
||||||
const beaconInstance = room.currentState.beacons.get(beaconIdentifier);
|
const beaconInstance = room?.currentState.beacons.get(beaconIdentifier);
|
||||||
|
|
||||||
// TODO could this be less stupid?
|
// TODO could this be less stupid?
|
||||||
|
|
||||||
|
|
|
@ -173,7 +173,7 @@ export class ThreepidMember extends Member {
|
||||||
|
|
||||||
export interface IDMUserTileProps {
|
export interface IDMUserTileProps {
|
||||||
member: Member;
|
member: Member;
|
||||||
onRemove(member: Member): void;
|
onRemove?(member: Member): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -92,7 +92,7 @@ export async function createDmLocalRoom(client: MatrixClient, targets: Member[])
|
||||||
type: EventType.RoomMember,
|
type: EventType.RoomMember,
|
||||||
content: {
|
content: {
|
||||||
displayname: target.name,
|
displayname: target.name,
|
||||||
avatar_url: target.getMxcAvatarUrl(),
|
avatar_url: target.getMxcAvatarUrl() ?? undefined,
|
||||||
membership: "invite",
|
membership: "invite",
|
||||||
isDirect: true,
|
isDirect: true,
|
||||||
},
|
},
|
||||||
|
@ -107,7 +107,7 @@ export async function createDmLocalRoom(client: MatrixClient, targets: Member[])
|
||||||
type: EventType.RoomMember,
|
type: EventType.RoomMember,
|
||||||
content: {
|
content: {
|
||||||
displayname: target.name,
|
displayname: target.name,
|
||||||
avatar_url: target.getMxcAvatarUrl(),
|
avatar_url: target.getMxcAvatarUrl() ?? undefined,
|
||||||
membership: "join",
|
membership: "join",
|
||||||
},
|
},
|
||||||
state_key: target.userId,
|
state_key: target.userId,
|
||||||
|
|
|
@ -37,7 +37,7 @@ import { bulkSpaceBehaviour } from "./space";
|
||||||
import { SdkContextClass } from "../contexts/SDKContext";
|
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> | undefined;
|
||||||
if (spinner) {
|
if (spinner) {
|
||||||
spinnerModal = Modal.createDialog(Spinner, undefined, "mx_Dialog_spinner");
|
spinnerModal = Modal.createDialog(Spinner, undefined, "mx_Dialog_spinner");
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ export async function leaveRoomBehaviour(roomId: string, retry = true, spinner =
|
||||||
room
|
room
|
||||||
.getPendingEvents()
|
.getPendingEvents()
|
||||||
.filter((ev) => {
|
.filter((ev) => {
|
||||||
return [EventStatus.QUEUED, EventStatus.ENCRYPTING, EventStatus.SENDING].includes(ev.status);
|
return [EventStatus.QUEUED, EventStatus.ENCRYPTING, EventStatus.SENDING].includes(ev.status!);
|
||||||
})
|
})
|
||||||
.map(
|
.map(
|
||||||
(ev) =>
|
(ev) =>
|
||||||
|
|
|
@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const parseGeoUri = (uri: string): GeolocationCoordinates => {
|
export const parseGeoUri = (uri: string): GeolocationCoordinates | undefined => {
|
||||||
function parse(s: string): number {
|
function parse(s: string): number | undefined {
|
||||||
const ret = parseFloat(s);
|
const ret = parseFloat(s);
|
||||||
if (Number.isNaN(ret)) {
|
if (Number.isNaN(ret)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -28,7 +28,7 @@ export const parseGeoUri = (uri: string): GeolocationCoordinates => {
|
||||||
if (!m) return;
|
if (!m) return;
|
||||||
const parts = m[1].split(";");
|
const parts = m[1].split(";");
|
||||||
const coords = parts[0].split(",");
|
const coords = parts[0].split(",");
|
||||||
let uncertainty: number;
|
let uncertainty: number | undefined;
|
||||||
for (const param of parts.slice(1)) {
|
for (const param of parts.slice(1)) {
|
||||||
const m = param.match(/u=(.*)/);
|
const m = param.match(/u=(.*)/);
|
||||||
if (m) uncertainty = parse(m[1]);
|
if (m) uncertainty = parse(m[1]);
|
||||||
|
|
|
@ -23,5 +23,5 @@ export function isLoggedIn(): boolean {
|
||||||
// store to hold this state.
|
// store to hold this state.
|
||||||
// See also https://github.com/vector-im/element-web/issues/15034.
|
// See also https://github.com/vector-im/element-web/issues/15034.
|
||||||
const app = window.matrixChat;
|
const app = window.matrixChat;
|
||||||
return app && (app as MatrixChat).state.view === Views.LOGGED_IN;
|
return (app as MatrixChat)?.state.view === Views.LOGGED_IN;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ import { parsePermalink } from "./permalinks/Permalinks";
|
||||||
* The initial caller should pass in an empty array to seed the accumulator.
|
* The initial caller should pass in an empty array to seed the accumulator.
|
||||||
*/
|
*/
|
||||||
export function pillifyLinks(nodes: ArrayLike<Element>, mxEvent: MatrixEvent, pills: Element[]): void {
|
export function pillifyLinks(nodes: ArrayLike<Element>, mxEvent: MatrixEvent, pills: Element[]): void {
|
||||||
const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId());
|
const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId()) ?? undefined;
|
||||||
const shouldShowPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar");
|
const shouldShowPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar");
|
||||||
let node = nodes[0];
|
let node = nodes[0];
|
||||||
while (node) {
|
while (node) {
|
||||||
|
@ -49,7 +49,7 @@ export function pillifyLinks(nodes: ArrayLike<Element>, mxEvent: MatrixEvent, pi
|
||||||
node = node.nextSibling as Element;
|
node = node.nextSibling as Element;
|
||||||
continue;
|
continue;
|
||||||
} else if (node.tagName === "A" && node.getAttribute("href")) {
|
} else if (node.tagName === "A" && node.getAttribute("href")) {
|
||||||
const href = node.getAttribute("href");
|
const href = node.getAttribute("href")!;
|
||||||
const parts = parsePermalink(href);
|
const parts = parsePermalink(href);
|
||||||
// If the link is a (localised) matrix.to link, replace it with a pill
|
// If the link is a (localised) matrix.to link, replace it with a pill
|
||||||
// We don't want to pill event permalinks, so those are ignored.
|
// We don't want to pill event permalinks, so those are ignored.
|
||||||
|
|
|
@ -71,17 +71,17 @@ export class VoiceBroadcastPlaybacksStore
|
||||||
}
|
}
|
||||||
|
|
||||||
public getByInfoEvent(infoEvent: MatrixEvent, client: MatrixClient): VoiceBroadcastPlayback {
|
public getByInfoEvent(infoEvent: MatrixEvent, client: MatrixClient): VoiceBroadcastPlayback {
|
||||||
const infoEventId = infoEvent.getId();
|
const infoEventId = infoEvent.getId()!;
|
||||||
|
|
||||||
if (!this.playbacks.has(infoEventId)) {
|
if (!this.playbacks.has(infoEventId)) {
|
||||||
this.addPlayback(new VoiceBroadcastPlayback(infoEvent, client, this.recordings));
|
this.addPlayback(new VoiceBroadcastPlayback(infoEvent, client, this.recordings));
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.playbacks.get(infoEventId);
|
return this.playbacks.get(infoEventId)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private addPlayback(playback: VoiceBroadcastPlayback): void {
|
private addPlayback(playback: VoiceBroadcastPlayback): void {
|
||||||
const infoEventId = playback.infoEvent.getId();
|
const infoEventId = playback.infoEvent.getId()!;
|
||||||
|
|
||||||
if (this.playbacks.has(infoEventId)) return;
|
if (this.playbacks.has(infoEventId)) return;
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ describe("ContentMessages", () => {
|
||||||
uploadContent: jest.fn().mockResolvedValue({ content_uri: "mxc://server/file" }),
|
uploadContent: jest.fn().mockResolvedValue({ content_uri: "mxc://server/file" }),
|
||||||
} as unknown as MatrixClient;
|
} as unknown as MatrixClient;
|
||||||
contentMessages = new ContentMessages();
|
contentMessages = new ContentMessages();
|
||||||
prom = Promise.resolve(null);
|
prom = Promise.resolve<ISendEventResponse>({ event_id: "$event_id" });
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("sendStickerContentToRoom", () => {
|
describe("sendStickerContentToRoom", () => {
|
||||||
|
@ -98,7 +98,7 @@ describe("ContentMessages", () => {
|
||||||
mocked(doMaybeLocalRoomAction).mockImplementation(
|
mocked(doMaybeLocalRoomAction).mockImplementation(
|
||||||
<T>(roomId: string, fn: (actualRoomId: string) => Promise<T>) => fn(roomId),
|
<T>(roomId: string, fn: (actualRoomId: string) => Promise<T>) => fn(roomId),
|
||||||
);
|
);
|
||||||
mocked(BlurhashEncoder.instance.getBlurhash).mockResolvedValue(undefined);
|
mocked(BlurhashEncoder.instance.getBlurhash).mockResolvedValue("blurhashstring");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should use m.image for image files", async () => {
|
it("should use m.image for image files", async () => {
|
||||||
|
@ -134,7 +134,7 @@ describe("ContentMessages", () => {
|
||||||
const element = createElement(tagName);
|
const element = createElement(tagName);
|
||||||
if (tagName === "video") {
|
if (tagName === "video") {
|
||||||
(<HTMLVideoElement>element).load = jest.fn();
|
(<HTMLVideoElement>element).load = jest.fn();
|
||||||
(<HTMLVideoElement>element).play = () => element.onloadeddata(new Event("loadeddata"));
|
(<HTMLVideoElement>element).play = () => element.onloadeddata!(new Event("loadeddata"));
|
||||||
(<HTMLVideoElement>element).pause = jest.fn();
|
(<HTMLVideoElement>element).pause = jest.fn();
|
||||||
Object.defineProperty(element, "videoHeight", {
|
Object.defineProperty(element, "videoHeight", {
|
||||||
get() {
|
get() {
|
||||||
|
@ -200,8 +200,8 @@ describe("ContentMessages", () => {
|
||||||
|
|
||||||
expect(upload.loaded).toBe(0);
|
expect(upload.loaded).toBe(0);
|
||||||
expect(upload.total).toBe(file.size);
|
expect(upload.total).toBe(file.size);
|
||||||
const { progressHandler } = mocked(client.uploadContent).mock.calls[0][1];
|
const { progressHandler } = mocked(client.uploadContent).mock.calls[0][1]!;
|
||||||
progressHandler({ loaded: 123, total: 1234 });
|
progressHandler!({ loaded: 123, total: 1234 });
|
||||||
expect(upload.loaded).toBe(123);
|
expect(upload.loaded).toBe(123);
|
||||||
expect(upload.total).toBe(1234);
|
expect(upload.total).toBe(1234);
|
||||||
await prom;
|
await prom;
|
||||||
|
@ -256,11 +256,11 @@ describe("ContentMessages", () => {
|
||||||
mocked(client.uploadContent).mockReturnValue(deferred.promise);
|
mocked(client.uploadContent).mockReturnValue(deferred.promise);
|
||||||
const file1 = new File([], "file1");
|
const file1 = new File([], "file1");
|
||||||
const prom = contentMessages.sendContentToRoom(file1, roomId, undefined, client, undefined);
|
const prom = contentMessages.sendContentToRoom(file1, roomId, undefined, client, undefined);
|
||||||
const { abortController } = mocked(client.uploadContent).mock.calls[0][1];
|
const { abortController } = mocked(client.uploadContent).mock.calls[0][1]!;
|
||||||
expect(abortController.signal.aborted).toBeFalsy();
|
expect(abortController!.signal.aborted).toBeFalsy();
|
||||||
const [upload] = contentMessages.getCurrentUploads();
|
const [upload] = contentMessages.getCurrentUploads();
|
||||||
contentMessages.cancelUpload(upload);
|
contentMessages.cancelUpload(upload);
|
||||||
expect(abortController.signal.aborted).toBeTruthy();
|
expect(abortController!.signal.aborted).toBeTruthy();
|
||||||
deferred.resolve({} as UploadResponse);
|
deferred.resolve({} as UploadResponse);
|
||||||
await prom;
|
await prom;
|
||||||
});
|
});
|
||||||
|
@ -325,7 +325,7 @@ describe("uploadFile", () => {
|
||||||
const file = new Blob([]);
|
const file = new Blob([]);
|
||||||
|
|
||||||
const prom = uploadFile(client, "!roomId:server", file);
|
const prom = uploadFile(client, "!roomId:server", file);
|
||||||
mocked(client.uploadContent).mock.calls[0][1].abortController.abort();
|
mocked(client.uploadContent).mock.calls[0][1]!.abortController!.abort();
|
||||||
deferred.resolve({ content_uri: "mxc://foo/bar" });
|
deferred.resolve({ content_uri: "mxc://foo/bar" });
|
||||||
await expect(prom).rejects.toThrowError(UploadCanceledError);
|
await expect(prom).rejects.toThrowError(UploadCanceledError);
|
||||||
});
|
});
|
||||||
|
|
|
@ -134,12 +134,14 @@ But this is not
|
||||||
expect(getNestedReplyText(event, mockPermalinkGenerator)).toMatchSnapshot();
|
expect(getNestedReplyText(event, mockPermalinkGenerator)).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
[
|
(
|
||||||
["m.room.message", MsgType.Location, LocationAssetType.Pin],
|
[
|
||||||
["m.room.message", MsgType.Location, LocationAssetType.Self],
|
["m.room.message", MsgType.Location, LocationAssetType.Pin],
|
||||||
[M_BEACON_INFO.name, undefined, LocationAssetType.Pin],
|
["m.room.message", MsgType.Location, LocationAssetType.Self],
|
||||||
[M_BEACON_INFO.name, undefined, LocationAssetType.Self],
|
[M_BEACON_INFO.name, undefined, LocationAssetType.Pin],
|
||||||
].forEach(([type, msgType, assetType]) => {
|
[M_BEACON_INFO.name, undefined, LocationAssetType.Self],
|
||||||
|
] as const
|
||||||
|
).forEach(([type, msgType, assetType]) => {
|
||||||
it(`should create the expected fallback text for ${assetType} ${type}/${msgType}`, () => {
|
it(`should create the expected fallback text for ${assetType} ${type}/${msgType}`, () => {
|
||||||
const event = makeTestEvent(type, {
|
const event = makeTestEvent(type, {
|
||||||
body: "body",
|
body: "body",
|
||||||
|
|
|
@ -49,7 +49,7 @@ describe("Terms", function () {
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
mockClient.getAccountData.mockReturnValue(null);
|
mockClient.getAccountData.mockReturnValue(undefined);
|
||||||
mockClient.getTerms.mockResolvedValue(null);
|
mockClient.getTerms.mockResolvedValue(null);
|
||||||
mockClient.setAccountData.mockResolvedValue({});
|
mockClient.setAccountData.mockResolvedValue({});
|
||||||
});
|
});
|
||||||
|
@ -59,7 +59,7 @@ describe("Terms", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should prompt for all terms & services if no account data", async function () {
|
it("should prompt for all terms & services if no account data", async function () {
|
||||||
mockClient.getAccountData.mockReturnValue(null);
|
mockClient.getAccountData.mockReturnValue(undefined);
|
||||||
mockClient.getTerms.mockResolvedValue({
|
mockClient.getTerms.mockResolvedValue({
|
||||||
policies: {
|
policies: {
|
||||||
policy_the_first: POLICY_ONE,
|
policy_the_first: POLICY_ONE,
|
||||||
|
|
|
@ -34,7 +34,7 @@ describe("RoomStatusBar", () => {
|
||||||
|
|
||||||
stubClient();
|
stubClient();
|
||||||
client = MatrixClientPeg.get();
|
client = MatrixClientPeg.get();
|
||||||
room = new Room(ROOM_ID, client, client.getUserId(), {
|
room = new Room(ROOM_ID, client, client.getUserId()!, {
|
||||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||||
});
|
});
|
||||||
event = mkEvent({
|
event = mkEvent({
|
||||||
|
@ -72,7 +72,7 @@ describe("RoomStatusBar", () => {
|
||||||
length: 2,
|
length: 2,
|
||||||
});
|
});
|
||||||
rootEvent.status = EventStatus.NOT_SENT;
|
rootEvent.status = EventStatus.NOT_SENT;
|
||||||
room.addPendingEvent(rootEvent, rootEvent.getId());
|
room.addPendingEvent(rootEvent, rootEvent.getId()!);
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
event.status = EventStatus.NOT_SENT;
|
event.status = EventStatus.NOT_SENT;
|
||||||
room.addPendingEvent(event, Date.now() + Math.random() + "");
|
room.addPendingEvent(event, Date.now() + Math.random() + "");
|
||||||
|
|
|
@ -99,10 +99,10 @@ describe("ThreadView", () => {
|
||||||
"is_falling_back": true,
|
"is_falling_back": true,
|
||||||
"m.in_reply_to": {
|
"m.in_reply_to": {
|
||||||
event_id: rootEvent
|
event_id: rootEvent
|
||||||
.getThread()
|
.getThread()!
|
||||||
.lastReply((ev: MatrixEvent) => {
|
.lastReply((ev: MatrixEvent) => {
|
||||||
return ev.isRelation(THREAD_RELATION_TYPE.name);
|
return ev.isRelation(THREAD_RELATION_TYPE.name);
|
||||||
})
|
})!
|
||||||
.getId(),
|
.getId(),
|
||||||
},
|
},
|
||||||
"rel_type": RelationType.Thread,
|
"rel_type": RelationType.Thread,
|
||||||
|
@ -126,8 +126,8 @@ describe("ThreadView", () => {
|
||||||
const res = mkThread({
|
const res = mkThread({
|
||||||
room,
|
room,
|
||||||
client: mockClient,
|
client: mockClient,
|
||||||
authorId: mockClient.getUserId(),
|
authorId: mockClient.getUserId()!,
|
||||||
participantUserIds: [mockClient.getUserId()],
|
participantUserIds: [mockClient.getUserId()!],
|
||||||
});
|
});
|
||||||
|
|
||||||
rootEvent = res.rootEvent;
|
rootEvent = res.rootEvent;
|
||||||
|
@ -154,8 +154,8 @@ describe("ThreadView", () => {
|
||||||
const { rootEvent: rootEvent2 } = mkThread({
|
const { rootEvent: rootEvent2 } = mkThread({
|
||||||
room,
|
room,
|
||||||
client: mockClient,
|
client: mockClient,
|
||||||
authorId: mockClient.getUserId(),
|
authorId: mockClient.getUserId()!,
|
||||||
participantUserIds: [mockClient.getUserId()],
|
participantUserIds: [mockClient.getUserId()!],
|
||||||
});
|
});
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
|
|
|
@ -54,8 +54,8 @@ describe("ThreadListContextMenu", () => {
|
||||||
const res = mkThread({
|
const res = mkThread({
|
||||||
room,
|
room,
|
||||||
client: mockClient,
|
client: mockClient,
|
||||||
authorId: mockClient.getUserId(),
|
authorId: mockClient.getUserId()!,
|
||||||
participantUserIds: [mockClient.getUserId()],
|
participantUserIds: [mockClient.getUserId()!],
|
||||||
});
|
});
|
||||||
|
|
||||||
event = res.rootEvent;
|
event = res.rootEvent;
|
||||||
|
|
|
@ -109,7 +109,7 @@ describe("<ExportDialog />", () => {
|
||||||
plainTextExporterInstance.export.mockClear();
|
plainTextExporterInstance.export.mockClear();
|
||||||
|
|
||||||
// default setting value
|
// default setting value
|
||||||
ChatExportMock.getForceChatExportParameters.mockClear().mockReturnValue({});
|
mocked(ChatExportMock.getForceChatExportParameters!).mockClear().mockReturnValue({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders export dialog", () => {
|
it("renders export dialog", () => {
|
||||||
|
@ -145,7 +145,7 @@ describe("<ExportDialog />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("exports room using values set from ForceRoomExportParameters", async () => {
|
it("exports room using values set from ForceRoomExportParameters", async () => {
|
||||||
ChatExportMock.getForceChatExportParameters.mockReturnValue({
|
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
|
||||||
format: ExportFormat.PlainText,
|
format: ExportFormat.PlainText,
|
||||||
range: ExportType.Beginning,
|
range: ExportType.Beginning,
|
||||||
sizeMb: 7000,
|
sizeMb: 7000,
|
||||||
|
@ -198,7 +198,7 @@ describe("<ExportDialog />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not render export format when set in ForceRoomExportParameters", () => {
|
it("does not render export format when set in ForceRoomExportParameters", () => {
|
||||||
ChatExportMock.getForceChatExportParameters.mockReturnValue({
|
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
|
||||||
format: ExportFormat.PlainText,
|
format: ExportFormat.PlainText,
|
||||||
});
|
});
|
||||||
const component = getComponent();
|
const component = getComponent();
|
||||||
|
@ -219,7 +219,7 @@ describe("<ExportDialog />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not render export type when set in ForceRoomExportParameters", () => {
|
it("does not render export type when set in ForceRoomExportParameters", () => {
|
||||||
ChatExportMock.getForceChatExportParameters.mockReturnValue({
|
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
|
||||||
range: ExportType.Beginning,
|
range: ExportType.Beginning,
|
||||||
});
|
});
|
||||||
const component = getComponent();
|
const component = getComponent();
|
||||||
|
@ -310,7 +310,7 @@ describe("<ExportDialog />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not render size limit input when set in ForceRoomExportParameters", () => {
|
it("does not render size limit input when set in ForceRoomExportParameters", () => {
|
||||||
ChatExportMock.getForceChatExportParameters.mockReturnValue({
|
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
|
||||||
sizeMb: 10000,
|
sizeMb: 10000,
|
||||||
});
|
});
|
||||||
const component = getComponent();
|
const component = getComponent();
|
||||||
|
@ -321,7 +321,7 @@ describe("<ExportDialog />", () => {
|
||||||
* 2000mb size limit does not apply when higher limit is configured in config
|
* 2000mb size limit does not apply when higher limit is configured in config
|
||||||
*/
|
*/
|
||||||
it("exports when size limit set in ForceRoomExportParameters is larger than 2000", async () => {
|
it("exports when size limit set in ForceRoomExportParameters is larger than 2000", async () => {
|
||||||
ChatExportMock.getForceChatExportParameters.mockReturnValue({
|
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
|
||||||
sizeMb: 10000,
|
sizeMb: 10000,
|
||||||
});
|
});
|
||||||
const component = getComponent();
|
const component = getComponent();
|
||||||
|
@ -344,7 +344,7 @@ describe("<ExportDialog />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not render input when set in ForceRoomExportParameters", () => {
|
it("does not render input when set in ForceRoomExportParameters", () => {
|
||||||
ChatExportMock.getForceChatExportParameters.mockReturnValue({
|
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
|
||||||
includeAttachments: false,
|
includeAttachments: false,
|
||||||
});
|
});
|
||||||
const component = getComponent();
|
const component = getComponent();
|
||||||
|
|
|
@ -74,7 +74,7 @@ describe("<DevicesPanel />", () => {
|
||||||
|
|
||||||
const toggleDeviceSelection = (container: HTMLElement, deviceId: string) =>
|
const toggleDeviceSelection = (container: HTMLElement, deviceId: string) =>
|
||||||
act(() => {
|
act(() => {
|
||||||
const checkbox = container.querySelector(`#device-tile-checkbox-${deviceId}`);
|
const checkbox = container.querySelector(`#device-tile-checkbox-${deviceId}`)!;
|
||||||
fireEvent.click(checkbox);
|
fireEvent.click(checkbox);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -204,7 +204,7 @@ describe("<DevicesPanel />", () => {
|
||||||
|
|
||||||
// close the modal without submission
|
// close the modal without submission
|
||||||
act(() => {
|
act(() => {
|
||||||
const modalCloseButton = document.querySelector('[aria-label="Close dialog"]');
|
const modalCloseButton = document.querySelector('[aria-label="Close dialog"]')!;
|
||||||
fireEvent.click(modalCloseButton);
|
fireEvent.click(modalCloseButton);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ describe("PreferencesUserSettingsTab", () => {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
jest.spyOn(client, "isVersionSupported").mockImplementation(async (version: string) => {
|
jest.spyOn(client, "isVersionSupported").mockImplementation(async (version: string) => {
|
||||||
if (version === "v1.4") return val;
|
if (version === "v1.4") return val;
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -61,8 +62,12 @@ describe("PreferencesUserSettingsTab", () => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectSetValueToHaveBeenCalled = (name: string, roomId: string, level: SettingLevel, value: boolean) =>
|
const expectSetValueToHaveBeenCalled = (
|
||||||
expect(SettingsStore.setValue).toHaveBeenCalledWith(name, roomId, level, value);
|
name: string,
|
||||||
|
roomId: string | undefined,
|
||||||
|
level: SettingLevel,
|
||||||
|
value: boolean,
|
||||||
|
) => expect(SettingsStore.setValue).toHaveBeenCalledWith(name, roomId, level, value);
|
||||||
|
|
||||||
describe("with server support", () => {
|
describe("with server support", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
|
@ -923,7 +923,7 @@ describe("ElementCall", () => {
|
||||||
jest.spyOn(Modal, "createDialog").mockReturnValue({
|
jest.spyOn(Modal, "createDialog").mockReturnValue({
|
||||||
finished: new Promise((r) => r([sourceId])),
|
finished: new Promise((r) => r([sourceId])),
|
||||||
} as IHandle<any[]>);
|
} as IHandle<any[]>);
|
||||||
jest.spyOn(PlatformPeg.get(), "supportsDesktopCapturer").mockReturnValue(true);
|
jest.spyOn(PlatformPeg.get()!, "supportsDesktopCapturer").mockReturnValue(true);
|
||||||
|
|
||||||
await call.connect();
|
await call.connect();
|
||||||
|
|
||||||
|
@ -951,7 +951,7 @@ describe("ElementCall", () => {
|
||||||
jest.spyOn(Modal, "createDialog").mockReturnValue({
|
jest.spyOn(Modal, "createDialog").mockReturnValue({
|
||||||
finished: new Promise((r) => r([null])),
|
finished: new Promise((r) => r([null])),
|
||||||
} as IHandle<any[]>);
|
} as IHandle<any[]>);
|
||||||
jest.spyOn(PlatformPeg.get(), "supportsDesktopCapturer").mockReturnValue(true);
|
jest.spyOn(PlatformPeg.get()!, "supportsDesktopCapturer").mockReturnValue(true);
|
||||||
|
|
||||||
await call.connect();
|
await call.connect();
|
||||||
|
|
||||||
|
@ -976,7 +976,7 @@ describe("ElementCall", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("replies with pending: false if we don't support desktop capturer", async () => {
|
it("replies with pending: false if we don't support desktop capturer", async () => {
|
||||||
jest.spyOn(PlatformPeg.get(), "supportsDesktopCapturer").mockReturnValue(false);
|
jest.spyOn(PlatformPeg.get()!, "supportsDesktopCapturer").mockReturnValue(false);
|
||||||
|
|
||||||
await call.connect();
|
await call.connect();
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ describe("ImageSize", () => {
|
||||||
expect(size).toStrictEqual({ w: 800, h: 400 });
|
expect(size).toStrictEqual({ w: 800, h: 400 });
|
||||||
});
|
});
|
||||||
it("returns max values if content size is not specified", () => {
|
it("returns max values if content size is not specified", () => {
|
||||||
const size = suggestedSize(ImageSize.Normal, { w: null, h: null });
|
const size = suggestedSize(ImageSize.Normal, {});
|
||||||
expect(size).toStrictEqual({ w: 324, h: 324 });
|
expect(size).toStrictEqual({ w: 324, h: 324 });
|
||||||
});
|
});
|
||||||
it("returns integer values", () => {
|
it("returns integer values", () => {
|
||||||
|
|
|
@ -102,9 +102,9 @@ describe("OwnBeaconStore", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const expireBeaconAndEmit = (store: OwnBeaconStore, beaconInfoEvent: MatrixEvent): void => {
|
const expireBeaconAndEmit = (store: OwnBeaconStore, beaconInfoEvent: MatrixEvent): void => {
|
||||||
const beacon = store.getBeaconById(getBeaconInfoIdentifier(beaconInfoEvent));
|
const beacon = store.getBeaconById(getBeaconInfoIdentifier(beaconInfoEvent))!;
|
||||||
// time travel until beacon is expired
|
// time travel until beacon is expired
|
||||||
advanceDateAndTime(beacon.beaconInfo.timeout + 100);
|
advanceDateAndTime(beacon.beaconInfo!.timeout + 100);
|
||||||
|
|
||||||
// force an update on the beacon
|
// force an update on the beacon
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -118,13 +118,13 @@ describe("OwnBeaconStore", () => {
|
||||||
beaconInfoEvent: MatrixEvent,
|
beaconInfoEvent: MatrixEvent,
|
||||||
isLive: boolean,
|
isLive: boolean,
|
||||||
): void => {
|
): void => {
|
||||||
const beacon = store.getBeaconById(getBeaconInfoIdentifier(beaconInfoEvent));
|
const beacon = store.getBeaconById(getBeaconInfoIdentifier(beaconInfoEvent))!;
|
||||||
// matches original state of event content
|
// matches original state of event content
|
||||||
// except for live property
|
// except for live property
|
||||||
const updateEvent = makeBeaconInfoEvent(
|
const updateEvent = makeBeaconInfoEvent(
|
||||||
beaconInfoEvent.getSender(),
|
beaconInfoEvent.getSender()!,
|
||||||
beaconInfoEvent.getRoomId(),
|
beaconInfoEvent.getRoomId()!,
|
||||||
{ isLive, timeout: beacon.beaconInfo.timeout },
|
{ isLive, timeout: beacon.beaconInfo!.timeout },
|
||||||
"update-event-id",
|
"update-event-id",
|
||||||
);
|
);
|
||||||
beacon.update(updateEvent);
|
beacon.update(updateEvent);
|
||||||
|
@ -236,12 +236,12 @@ describe("OwnBeaconStore", () => {
|
||||||
expect(mockClient.sendEvent).toHaveBeenCalledWith(
|
expect(mockClient.sendEvent).toHaveBeenCalledWith(
|
||||||
room1Id,
|
room1Id,
|
||||||
M_BEACON.name,
|
M_BEACON.name,
|
||||||
makeBeaconContent(defaultLocationUri, now, alicesRoom1BeaconInfo.getId()),
|
makeBeaconContent(defaultLocationUri, now, alicesRoom1BeaconInfo.getId()!),
|
||||||
);
|
);
|
||||||
expect(mockClient.sendEvent).toHaveBeenCalledWith(
|
expect(mockClient.sendEvent).toHaveBeenCalledWith(
|
||||||
room2Id,
|
room2Id,
|
||||||
M_BEACON.name,
|
M_BEACON.name,
|
||||||
makeBeaconContent(defaultLocationUri, now, alicesRoom2BeaconInfo.getId()),
|
makeBeaconContent(defaultLocationUri, now, alicesRoom2BeaconInfo.getId()!),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -263,7 +263,7 @@ describe("OwnBeaconStore", () => {
|
||||||
it("destroys beacons", async () => {
|
it("destroys beacons", async () => {
|
||||||
const [room1] = makeRoomsWithStateEvents([alicesRoom1BeaconInfo]);
|
const [room1] = makeRoomsWithStateEvents([alicesRoom1BeaconInfo]);
|
||||||
const store = await makeOwnBeaconStore();
|
const store = await makeOwnBeaconStore();
|
||||||
const beacon = room1.currentState.beacons.get(getBeaconInfoIdentifier(alicesRoom1BeaconInfo));
|
const beacon = room1.currentState.beacons.get(getBeaconInfoIdentifier(alicesRoom1BeaconInfo))!;
|
||||||
const destroySpy = jest.spyOn(beacon, "destroy");
|
const destroySpy = jest.spyOn(beacon, "destroy");
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
store.onNotReady();
|
store.onNotReady();
|
||||||
|
@ -559,7 +559,7 @@ describe("OwnBeaconStore", () => {
|
||||||
|
|
||||||
const [room1] = makeRoomsWithStateEvents([alicesRoom1BeaconInfo, alicesRoom2BeaconInfo]);
|
const [room1] = makeRoomsWithStateEvents([alicesRoom1BeaconInfo, alicesRoom2BeaconInfo]);
|
||||||
const store = await makeOwnBeaconStore();
|
const store = await makeOwnBeaconStore();
|
||||||
const room1BeaconInstance = store.beacons.get(getBeaconInfoIdentifier(alicesRoom1BeaconInfo));
|
const room1BeaconInstance = store.beacons.get(getBeaconInfoIdentifier(alicesRoom1BeaconInfo))!;
|
||||||
const beaconDestroySpy = jest.spyOn(room1BeaconInstance, "destroy");
|
const beaconDestroySpy = jest.spyOn(room1BeaconInstance, "destroy");
|
||||||
const emitSpy = jest.spyOn(store, "emit");
|
const emitSpy = jest.spyOn(store, "emit");
|
||||||
|
|
||||||
|
@ -610,7 +610,7 @@ describe("OwnBeaconStore", () => {
|
||||||
expect(store.hasLiveBeacons()).toBe(true);
|
expect(store.hasLiveBeacons()).toBe(true);
|
||||||
const emitSpy = jest.spyOn(store, "emit");
|
const emitSpy = jest.spyOn(store, "emit");
|
||||||
|
|
||||||
const beacon = store.getBeaconById(getBeaconInfoIdentifier(alicesRoom1BeaconInfo));
|
const beacon = store.getBeaconById(getBeaconInfoIdentifier(alicesRoom1BeaconInfo))!;
|
||||||
|
|
||||||
beacon.destroy();
|
beacon.destroy();
|
||||||
mockClient.emit(BeaconEvent.Destroy, beacon.identifier);
|
mockClient.emit(BeaconEvent.Destroy, beacon.identifier);
|
||||||
|
|
|
@ -103,10 +103,11 @@ describe("SpaceStore", () => {
|
||||||
const viewRoom = (roomId: string) => defaultDispatcher.dispatch({ action: Action.ViewRoom, room_id: roomId }, true);
|
const viewRoom = (roomId: string) => defaultDispatcher.dispatch({ action: Action.ViewRoom, room_id: roomId }, true);
|
||||||
|
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
mocked(client).getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId));
|
mocked(client).getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId) || null);
|
||||||
mocked(client).getRoomUpgradeHistory.mockImplementation((roomId) => [
|
mocked(client).getRoomUpgradeHistory.mockImplementation((roomId) => {
|
||||||
rooms.find((room) => room.roomId === roomId),
|
const room = rooms.find((room) => room.roomId === roomId);
|
||||||
]);
|
return room ? [room] : [];
|
||||||
|
});
|
||||||
await testUtils.setupAsyncStoreWithClient(store, client);
|
await testUtils.setupAsyncStoreWithClient(store, client);
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
};
|
};
|
||||||
|
@ -312,10 +313,12 @@ describe("SpaceStore", () => {
|
||||||
mkSpace(space3, [invite2]);
|
mkSpace(space3, [invite2]);
|
||||||
mkSpace(space4, [room4, fav2, space2, space3]);
|
mkSpace(space4, [room4, fav2, space2, space3]);
|
||||||
|
|
||||||
mocked(client).getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId));
|
mocked(client).getRoom.mockImplementation(
|
||||||
|
(roomId) => rooms.find((room) => room.roomId === roomId) || null,
|
||||||
|
);
|
||||||
|
|
||||||
[fav1, fav2, fav3].forEach((roomId) => {
|
[fav1, fav2, fav3].forEach((roomId) => {
|
||||||
client.getRoom(roomId).tags = {
|
client.getRoom(roomId)!.tags = {
|
||||||
"m.favourite": {
|
"m.favourite": {
|
||||||
order: 0.5,
|
order: 0.5,
|
||||||
},
|
},
|
||||||
|
@ -323,20 +326,20 @@ describe("SpaceStore", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
[invite1, invite2].forEach((roomId) => {
|
[invite1, invite2].forEach((roomId) => {
|
||||||
mocked(client.getRoom(roomId)).getMyMembership.mockReturnValue("invite");
|
mocked(client.getRoom(roomId)!).getMyMembership.mockReturnValue("invite");
|
||||||
});
|
});
|
||||||
|
|
||||||
// have dmPartner1 be in space1 with you
|
// have dmPartner1 be in space1 with you
|
||||||
const mySpace1Member = new RoomMember(space1, testUserId);
|
const mySpace1Member = new RoomMember(space1, testUserId);
|
||||||
mySpace1Member.membership = "join";
|
mySpace1Member.membership = "join";
|
||||||
(rooms.find((r) => r.roomId === space1).getMembers as jest.Mock).mockReturnValue([
|
(rooms.find((r) => r.roomId === space1)!.getMembers as jest.Mock).mockReturnValue([
|
||||||
mySpace1Member,
|
mySpace1Member,
|
||||||
dm1Partner,
|
dm1Partner,
|
||||||
]);
|
]);
|
||||||
// have dmPartner2 be in space2 with you
|
// have dmPartner2 be in space2 with you
|
||||||
const mySpace2Member = new RoomMember(space2, testUserId);
|
const mySpace2Member = new RoomMember(space2, testUserId);
|
||||||
mySpace2Member.membership = "join";
|
mySpace2Member.membership = "join";
|
||||||
(rooms.find((r) => r.roomId === space2).getMembers as jest.Mock).mockReturnValue([
|
(rooms.find((r) => r.roomId === space2)!.getMembers as jest.Mock).mockReturnValue([
|
||||||
mySpace2Member,
|
mySpace2Member,
|
||||||
dm2Partner,
|
dm2Partner,
|
||||||
]);
|
]);
|
||||||
|
@ -349,15 +352,15 @@ describe("SpaceStore", () => {
|
||||||
event: true,
|
event: true,
|
||||||
type: EventType.SpaceParent,
|
type: EventType.SpaceParent,
|
||||||
room: room2,
|
room: room2,
|
||||||
user: client.getUserId(),
|
user: client.getUserId()!,
|
||||||
skey: space2,
|
skey: space2,
|
||||||
content: { via: [], canonical: true },
|
content: { via: [], canonical: true },
|
||||||
ts: Date.now(),
|
ts: Date.now(),
|
||||||
}) as MatrixEvent,
|
}) as MatrixEvent,
|
||||||
]);
|
]);
|
||||||
mocked(cliRoom2.currentState).getStateEvents.mockImplementation(room2MockStateEvents);
|
mocked(cliRoom2!.currentState).getStateEvents.mockImplementation(room2MockStateEvents);
|
||||||
const cliSpace2 = client.getRoom(space2);
|
const cliSpace2 = client.getRoom(space2);
|
||||||
mocked(cliSpace2.currentState).maySendStateEvent.mockImplementation(
|
mocked(cliSpace2!.currentState).maySendStateEvent.mockImplementation(
|
||||||
(evType: string, userId: string) => {
|
(evType: string, userId: string) => {
|
||||||
if (evType === EventType.SpaceChild) {
|
if (evType === EventType.SpaceChild) {
|
||||||
return userId === client.getUserId();
|
return userId === client.getUserId();
|
||||||
|
@ -368,13 +371,13 @@ describe("SpaceStore", () => {
|
||||||
|
|
||||||
// room 3 claims to be a child of space3 but is not due to invalid m.space.parent (permissions)
|
// room 3 claims to be a child of space3 but is not due to invalid m.space.parent (permissions)
|
||||||
const cliRoom3 = client.getRoom(room3);
|
const cliRoom3 = client.getRoom(room3);
|
||||||
mocked(cliRoom3.currentState).getStateEvents.mockImplementation(
|
mocked(cliRoom3!.currentState).getStateEvents.mockImplementation(
|
||||||
testUtils.mockStateEventImplementation([
|
testUtils.mockStateEventImplementation([
|
||||||
mkEvent({
|
mkEvent({
|
||||||
event: true,
|
event: true,
|
||||||
type: EventType.SpaceParent,
|
type: EventType.SpaceParent,
|
||||||
room: room3,
|
room: room3,
|
||||||
user: client.getUserId(),
|
user: client.getUserId()!,
|
||||||
skey: space3,
|
skey: space3,
|
||||||
content: { via: [], canonical: true },
|
content: { via: [], canonical: true },
|
||||||
ts: Date.now(),
|
ts: Date.now(),
|
||||||
|
@ -382,7 +385,7 @@ describe("SpaceStore", () => {
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
const cliSpace3 = client.getRoom(space3);
|
const cliSpace3 = client.getRoom(space3);
|
||||||
mocked(cliSpace3.currentState).maySendStateEvent.mockImplementation(
|
mocked(cliSpace3!.currentState).maySendStateEvent.mockImplementation(
|
||||||
(evType: string, userId: string) => {
|
(evType: string, userId: string) => {
|
||||||
if (evType === EventType.SpaceChild) {
|
if (evType === EventType.SpaceChild) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -813,7 +816,7 @@ describe("SpaceStore", () => {
|
||||||
content: { membership: "join" },
|
content: { membership: "join" },
|
||||||
ts: Date.now(),
|
ts: Date.now(),
|
||||||
});
|
});
|
||||||
const spaceRoom = client.getRoom(spaceId);
|
const spaceRoom = client.getRoom(spaceId)!;
|
||||||
mocked(spaceRoom.currentState).getStateEvents.mockImplementation(
|
mocked(spaceRoom.currentState).getStateEvents.mockImplementation(
|
||||||
testUtils.mockStateEventImplementation([memberEvent]),
|
testUtils.mockStateEventImplementation([memberEvent]),
|
||||||
);
|
);
|
||||||
|
@ -929,7 +932,7 @@ describe("SpaceStore", () => {
|
||||||
|
|
||||||
it("switch to unknown space is a nop", async () => {
|
it("switch to unknown space is a nop", async () => {
|
||||||
expect(store.activeSpace).toBe(MetaSpace.Home);
|
expect(store.activeSpace).toBe(MetaSpace.Home);
|
||||||
const space = client.getRoom(room1); // not a space
|
const space = client.getRoom(room1)!; // not a space
|
||||||
store.setActiveSpace(space.roomId);
|
store.setActiveSpace(space.roomId);
|
||||||
expect(fn).not.toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space.roomId);
|
expect(fn).not.toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space.roomId);
|
||||||
expect(store.activeSpace).toBe(MetaSpace.Home);
|
expect(store.activeSpace).toBe(MetaSpace.Home);
|
||||||
|
@ -962,6 +965,7 @@ describe("SpaceStore", () => {
|
||||||
member.membership = "join";
|
member.membership = "join";
|
||||||
return member;
|
return member;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
client.emit(RoomStateEvent.Members, event, space.currentState, dm1Partner);
|
client.emit(RoomStateEvent.Members, event, space.currentState, dm1Partner);
|
||||||
|
@ -1088,7 +1092,7 @@ describe("SpaceStore", () => {
|
||||||
mkSpace(space1, [room1, room2, room3]);
|
mkSpace(space1, [room1, room2, room3]);
|
||||||
mkSpace(space2, [room1, room2]);
|
mkSpace(space2, [room1, room2]);
|
||||||
|
|
||||||
const cliRoom2 = client.getRoom(room2);
|
const cliRoom2 = client.getRoom(room2)!;
|
||||||
mocked(cliRoom2.currentState).getStateEvents.mockImplementation(
|
mocked(cliRoom2.currentState).getStateEvents.mockImplementation(
|
||||||
testUtils.mockStateEventImplementation([
|
testUtils.mockStateEventImplementation([
|
||||||
mkEvent({
|
mkEvent({
|
||||||
|
@ -1267,8 +1271,9 @@ describe("SpaceStore", () => {
|
||||||
case dm1Partner.userId:
|
case dm1Partner.userId:
|
||||||
return rootSpaceFriend;
|
return rootSpaceFriend;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
expect(SpaceStore.instance.getSpaceFilteredUserIds(space1).has(dm1Partner.userId)).toBeFalsy();
|
expect(SpaceStore.instance.getSpaceFilteredUserIds(space1)!.has(dm1Partner.userId)).toBeFalsy();
|
||||||
const memberEvent = mkEvent({
|
const memberEvent = mkEvent({
|
||||||
event: true,
|
event: true,
|
||||||
type: EventType.RoomMember,
|
type: EventType.RoomMember,
|
||||||
|
@ -1281,7 +1286,7 @@ describe("SpaceStore", () => {
|
||||||
});
|
});
|
||||||
client.emit(RoomStateEvent.Members, memberEvent, rootSpace.currentState, dm1Partner);
|
client.emit(RoomStateEvent.Members, memberEvent, rootSpace.currentState, dm1Partner);
|
||||||
jest.runOnlyPendingTimers();
|
jest.runOnlyPendingTimers();
|
||||||
expect(SpaceStore.instance.getSpaceFilteredUserIds(space1).has(dm1Partner.userId)).toBeTruthy();
|
expect(SpaceStore.instance.getSpaceFilteredUserIds(space1)!.has(dm1Partner.userId)).toBeTruthy();
|
||||||
const dm1Room = mkRoom(dm1);
|
const dm1Room = mkRoom(dm1);
|
||||||
dm1Room.getMyMembership.mockReturnValue("join");
|
dm1Room.getMyMembership.mockReturnValue("join");
|
||||||
client.emit(ClientEvent.Room, dm1Room);
|
client.emit(ClientEvent.Room, dm1Room);
|
||||||
|
|
|
@ -198,10 +198,10 @@ describe("SlidingRoomListStore", () => {
|
||||||
return keyToListData[key] || null;
|
return keyToListData[key] || null;
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(store.getTagsForRoom(new Room(roomA, context.client!, context.client!.getUserId()))).toEqual([
|
expect(store.getTagsForRoom(new Room(roomA, context.client!, context.client!.getUserId()!))).toEqual([
|
||||||
DefaultTagID.Untagged,
|
DefaultTagID.Untagged,
|
||||||
]);
|
]);
|
||||||
expect(store.getTagsForRoom(new Room(roomB, context.client!, context.client!.getUserId()))).toEqual([
|
expect(store.getTagsForRoom(new Room(roomB, context.client!, context.client!.getUserId()!))).toEqual([
|
||||||
DefaultTagID.Favourite,
|
DefaultTagID.Favourite,
|
||||||
DefaultTagID.Untagged,
|
DefaultTagID.Untagged,
|
||||||
]);
|
]);
|
||||||
|
@ -221,9 +221,9 @@ describe("SlidingRoomListStore", () => {
|
||||||
0: roomA,
|
0: roomA,
|
||||||
};
|
};
|
||||||
const rooms = [
|
const rooms = [
|
||||||
new Room(roomA, context.client!, context.client!.getUserId()),
|
new Room(roomA, context.client!, context.client!.getUserId()!),
|
||||||
new Room(roomB, context.client!, context.client!.getUserId()),
|
new Room(roomB, context.client!, context.client!.getUserId()!),
|
||||||
new Room(roomC, context.client!, context.client!.getUserId()),
|
new Room(roomC, context.client!, context.client!.getUserId()!),
|
||||||
];
|
];
|
||||||
mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
|
mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
|
||||||
switch (roomId) {
|
switch (roomId) {
|
||||||
|
@ -257,9 +257,9 @@ describe("SlidingRoomListStore", () => {
|
||||||
2: roomIdC,
|
2: roomIdC,
|
||||||
0: roomIdA,
|
0: roomIdA,
|
||||||
};
|
};
|
||||||
const roomA = new Room(roomIdA, context.client!, context.client!.getUserId());
|
const roomA = new Room(roomIdA, context.client!, context.client!.getUserId()!);
|
||||||
const roomB = new Room(roomIdB, context.client!, context.client!.getUserId());
|
const roomB = new Room(roomIdB, context.client!, context.client!.getUserId()!);
|
||||||
const roomC = new Room(roomIdC, context.client!, context.client!.getUserId());
|
const roomC = new Room(roomIdC, context.client!, context.client!.getUserId()!);
|
||||||
mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
|
mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
|
||||||
switch (roomId) {
|
switch (roomId) {
|
||||||
case roomIdA:
|
case roomIdA:
|
||||||
|
@ -321,8 +321,8 @@ describe("SlidingRoomListStore", () => {
|
||||||
const tagId = DefaultTagID.Favourite;
|
const tagId = DefaultTagID.Favourite;
|
||||||
const joinCount = 10;
|
const joinCount = 10;
|
||||||
// seed the store with 2 rooms
|
// seed the store with 2 rooms
|
||||||
const roomA = new Room(roomIdA, context.client!, context.client!.getUserId());
|
const roomA = new Room(roomIdA, context.client!, context.client!.getUserId()!);
|
||||||
const roomC = new Room(roomIdC, context.client!, context.client!.getUserId());
|
const roomC = new Room(roomIdC, context.client!, context.client!.getUserId()!);
|
||||||
mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
|
mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
|
||||||
switch (roomId) {
|
switch (roomId) {
|
||||||
case roomIdA:
|
case roomIdA:
|
||||||
|
|
|
@ -29,11 +29,13 @@ import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||||
import { SpaceFilterCondition } from "../../../src/stores/room-list/filters/SpaceFilterCondition";
|
import { SpaceFilterCondition } from "../../../src/stores/room-list/filters/SpaceFilterCondition";
|
||||||
import DMRoomMap from "../../../src/utils/DMRoomMap";
|
import DMRoomMap from "../../../src/utils/DMRoomMap";
|
||||||
|
|
||||||
let filter: SpaceFilterCondition = null;
|
let filter: SpaceFilterCondition | null = null;
|
||||||
|
|
||||||
const mockRoomListStore = {
|
const mockRoomListStore = {
|
||||||
addFilter: (f: SpaceFilterCondition) => (filter = f),
|
addFilter: (f: SpaceFilterCondition) => (filter = f),
|
||||||
removeFilter: (): void => (filter = null),
|
removeFilter: (): void => {
|
||||||
|
filter = null;
|
||||||
|
},
|
||||||
} as unknown as RoomListStoreClass;
|
} as unknown as RoomListStoreClass;
|
||||||
|
|
||||||
const getUserIdForRoomId = jest.fn();
|
const getUserIdForRoomId = jest.fn();
|
||||||
|
@ -74,7 +76,7 @@ describe("SpaceWatcher", () => {
|
||||||
[MetaSpace.Orphans]: true,
|
[MetaSpace.Orphans]: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
client.getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId));
|
client.getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId) || null);
|
||||||
await setupAsyncStoreWithClient(store, client);
|
await setupAsyncStoreWithClient(store, client);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -99,7 +101,7 @@ describe("SpaceWatcher", () => {
|
||||||
await setShowAllRooms(false);
|
await setShowAllRooms(false);
|
||||||
|
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(MetaSpace.Home);
|
expect(filter!["space"]).toBe(MetaSpace.Home);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets filter correctly for all -> space transition", async () => {
|
it("sets filter correctly for all -> space transition", async () => {
|
||||||
|
@ -109,7 +111,7 @@ describe("SpaceWatcher", () => {
|
||||||
SpaceStore.instance.setActiveSpace(space1);
|
SpaceStore.instance.setActiveSpace(space1);
|
||||||
|
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space1);
|
expect(filter!["space"]).toBe(space1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("removes filter for home -> all transition", async () => {
|
it("removes filter for home -> all transition", async () => {
|
||||||
|
@ -128,7 +130,7 @@ describe("SpaceWatcher", () => {
|
||||||
SpaceStore.instance.setActiveSpace(space1);
|
SpaceStore.instance.setActiveSpace(space1);
|
||||||
|
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space1);
|
expect(filter!["space"]).toBe(space1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("removes filter for space -> all transition", async () => {
|
it("removes filter for space -> all transition", async () => {
|
||||||
|
@ -137,7 +139,7 @@ describe("SpaceWatcher", () => {
|
||||||
|
|
||||||
SpaceStore.instance.setActiveSpace(space1);
|
SpaceStore.instance.setActiveSpace(space1);
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space1);
|
expect(filter!["space"]).toBe(space1);
|
||||||
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
|
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
|
||||||
|
|
||||||
expect(filter).toBeNull();
|
expect(filter).toBeNull();
|
||||||
|
@ -149,7 +151,7 @@ describe("SpaceWatcher", () => {
|
||||||
|
|
||||||
SpaceStore.instance.setActiveSpace(MetaSpace.Favourites);
|
SpaceStore.instance.setActiveSpace(MetaSpace.Favourites);
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(MetaSpace.Favourites);
|
expect(filter!["space"]).toBe(MetaSpace.Favourites);
|
||||||
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
|
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
|
||||||
|
|
||||||
expect(filter).toBeNull();
|
expect(filter).toBeNull();
|
||||||
|
@ -161,7 +163,7 @@ describe("SpaceWatcher", () => {
|
||||||
|
|
||||||
SpaceStore.instance.setActiveSpace(MetaSpace.People);
|
SpaceStore.instance.setActiveSpace(MetaSpace.People);
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(MetaSpace.People);
|
expect(filter!["space"]).toBe(MetaSpace.People);
|
||||||
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
|
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
|
||||||
|
|
||||||
expect(filter).toBeNull();
|
expect(filter).toBeNull();
|
||||||
|
@ -173,7 +175,7 @@ describe("SpaceWatcher", () => {
|
||||||
|
|
||||||
SpaceStore.instance.setActiveSpace(MetaSpace.Orphans);
|
SpaceStore.instance.setActiveSpace(MetaSpace.Orphans);
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(MetaSpace.Orphans);
|
expect(filter!["space"]).toBe(MetaSpace.Orphans);
|
||||||
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
|
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
|
||||||
|
|
||||||
expect(filter).toBeNull();
|
expect(filter).toBeNull();
|
||||||
|
@ -185,11 +187,11 @@ describe("SpaceWatcher", () => {
|
||||||
|
|
||||||
new SpaceWatcher(mockRoomListStore);
|
new SpaceWatcher(mockRoomListStore);
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space1);
|
expect(filter!["space"]).toBe(space1);
|
||||||
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
|
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
|
||||||
|
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(MetaSpace.Home);
|
expect(filter!["space"]).toBe(MetaSpace.Home);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates filter correctly for space -> orphans transition", async () => {
|
it("updates filter correctly for space -> orphans transition", async () => {
|
||||||
|
@ -198,11 +200,11 @@ describe("SpaceWatcher", () => {
|
||||||
|
|
||||||
new SpaceWatcher(mockRoomListStore);
|
new SpaceWatcher(mockRoomListStore);
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space1);
|
expect(filter!["space"]).toBe(space1);
|
||||||
SpaceStore.instance.setActiveSpace(MetaSpace.Orphans);
|
SpaceStore.instance.setActiveSpace(MetaSpace.Orphans);
|
||||||
|
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(MetaSpace.Orphans);
|
expect(filter!["space"]).toBe(MetaSpace.Orphans);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates filter correctly for orphans -> people transition", async () => {
|
it("updates filter correctly for orphans -> people transition", async () => {
|
||||||
|
@ -211,11 +213,11 @@ describe("SpaceWatcher", () => {
|
||||||
|
|
||||||
new SpaceWatcher(mockRoomListStore);
|
new SpaceWatcher(mockRoomListStore);
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(MetaSpace.Orphans);
|
expect(filter!["space"]).toBe(MetaSpace.Orphans);
|
||||||
SpaceStore.instance.setActiveSpace(MetaSpace.People);
|
SpaceStore.instance.setActiveSpace(MetaSpace.People);
|
||||||
|
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(MetaSpace.People);
|
expect(filter!["space"]).toBe(MetaSpace.People);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates filter correctly for space -> space transition", async () => {
|
it("updates filter correctly for space -> space transition", async () => {
|
||||||
|
@ -224,11 +226,11 @@ describe("SpaceWatcher", () => {
|
||||||
|
|
||||||
new SpaceWatcher(mockRoomListStore);
|
new SpaceWatcher(mockRoomListStore);
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space1);
|
expect(filter!["space"]).toBe(space1);
|
||||||
SpaceStore.instance.setActiveSpace(space2);
|
SpaceStore.instance.setActiveSpace(space2);
|
||||||
|
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space2);
|
expect(filter!["space"]).toBe(space2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't change filter when changing showAllRooms mode to true", async () => {
|
it("doesn't change filter when changing showAllRooms mode to true", async () => {
|
||||||
|
@ -237,11 +239,11 @@ describe("SpaceWatcher", () => {
|
||||||
|
|
||||||
new SpaceWatcher(mockRoomListStore);
|
new SpaceWatcher(mockRoomListStore);
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space1);
|
expect(filter!["space"]).toBe(space1);
|
||||||
await setShowAllRooms(true);
|
await setShowAllRooms(true);
|
||||||
|
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space1);
|
expect(filter!["space"]).toBe(space1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't change filter when changing showAllRooms mode to false", async () => {
|
it("doesn't change filter when changing showAllRooms mode to false", async () => {
|
||||||
|
@ -250,10 +252,10 @@ describe("SpaceWatcher", () => {
|
||||||
|
|
||||||
new SpaceWatcher(mockRoomListStore);
|
new SpaceWatcher(mockRoomListStore);
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space1);
|
expect(filter!["space"]).toBe(space1);
|
||||||
await setShowAllRooms(false);
|
await setShowAllRooms(false);
|
||||||
|
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space1);
|
expect(filter!["space"]).toBe(space1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -109,14 +109,14 @@ describe("VisibilityProvider", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return false if visibility customisation returns false", () => {
|
it("should return false if visibility customisation returns false", () => {
|
||||||
mocked(RoomListCustomisations.isRoomVisible).mockReturnValue(false);
|
mocked(RoomListCustomisations.isRoomVisible!).mockReturnValue(false);
|
||||||
const room = createRoom();
|
const room = createRoom();
|
||||||
expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(false);
|
expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(false);
|
||||||
expect(RoomListCustomisations.isRoomVisible).toHaveBeenCalledWith(room);
|
expect(RoomListCustomisations.isRoomVisible).toHaveBeenCalledWith(room);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return true if visibility customisation returns true", () => {
|
it("should return true if visibility customisation returns true", () => {
|
||||||
mocked(RoomListCustomisations.isRoomVisible).mockReturnValue(true);
|
mocked(RoomListCustomisations.isRoomVisible!).mockReturnValue(true);
|
||||||
const room = createRoom();
|
const room = createRoom();
|
||||||
expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(true);
|
expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(true);
|
||||||
expect(RoomListCustomisations.isRoomVisible).toHaveBeenCalledWith(room);
|
expect(RoomListCustomisations.isRoomVisible).toHaveBeenCalledWith(room);
|
||||||
|
|
|
@ -80,8 +80,8 @@ describe("StopGapWidget", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
voiceBroadcastInfoEvent = mkEvent({
|
voiceBroadcastInfoEvent = mkEvent({
|
||||||
event: true,
|
event: true,
|
||||||
room: client.getRoom("x").roomId,
|
room: client.getRoom("x")?.roomId,
|
||||||
user: client.getUserId(),
|
user: client.getUserId()!,
|
||||||
type: VoiceBroadcastInfoEventType,
|
type: VoiceBroadcastInfoEventType,
|
||||||
content: {},
|
content: {},
|
||||||
});
|
});
|
||||||
|
|
|
@ -59,22 +59,22 @@ describe("WidgetPermissionStore", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should persist OIDCState.Allowed for a widget", () => {
|
it("should persist OIDCState.Allowed for a widget", () => {
|
||||||
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Allowed);
|
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, roomId, OIDCState.Allowed);
|
||||||
// check it remembered the value
|
// check it remembered the value
|
||||||
expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, null)).toEqual(OIDCState.Allowed);
|
expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, roomId)).toEqual(OIDCState.Allowed);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should persist OIDCState.Denied for a widget", () => {
|
it("should persist OIDCState.Denied for a widget", () => {
|
||||||
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Denied);
|
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, roomId, OIDCState.Denied);
|
||||||
// check it remembered the value
|
// check it remembered the value
|
||||||
expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, null)).toEqual(OIDCState.Denied);
|
expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, roomId)).toEqual(OIDCState.Denied);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should update OIDCState for a widget", () => {
|
it("should update OIDCState for a widget", () => {
|
||||||
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Allowed);
|
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, roomId, OIDCState.Allowed);
|
||||||
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Denied);
|
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, roomId, OIDCState.Denied);
|
||||||
// check it remembered the latest value
|
// check it remembered the latest value
|
||||||
expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, null)).toEqual(OIDCState.Denied);
|
expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, roomId)).toEqual(OIDCState.Denied);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should scope the location for a widget when setting OIDC state", () => {
|
it("should scope the location for a widget when setting OIDC state", () => {
|
||||||
|
|
|
@ -197,7 +197,7 @@ export const makeRoomWithBeacons = (
|
||||||
locationEvents?: MatrixEvent[],
|
locationEvents?: MatrixEvent[],
|
||||||
): Beacon[] => {
|
): Beacon[] => {
|
||||||
const room = makeRoomWithStateEvents(beaconInfoEvents, { roomId, mockClient });
|
const room = makeRoomWithStateEvents(beaconInfoEvents, { roomId, mockClient });
|
||||||
const beacons = beaconInfoEvents.map((event) => room.currentState.beacons.get(getBeaconInfoIdentifier(event)));
|
const beacons = beaconInfoEvents.map((event) => room.currentState.beacons.get(getBeaconInfoIdentifier(event))!);
|
||||||
if (locationEvents) {
|
if (locationEvents) {
|
||||||
beacons.forEach((beacon) => {
|
beacons.forEach((beacon) => {
|
||||||
// this filtering happens in roomState, which is bypassed here
|
// this filtering happens in roomState, which is bypassed here
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const addTextToComposer = (container: HTMLElement, text: string) =>
|
||||||
getData: (type: string) => (type === "text/plain" ? text : undefined),
|
getData: (type: string) => (type === "text/plain" ? text : undefined),
|
||||||
} as unknown as DataTransfer,
|
} as unknown as DataTransfer,
|
||||||
};
|
};
|
||||||
fireEvent.paste(container.querySelector('[role="textbox"]'), pasteEvent);
|
fireEvent.paste(container.querySelector('[role="textbox"]')!, pasteEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const addTextToComposerEnzyme = (wrapper: ReactWrapper, text: string) =>
|
export const addTextToComposerEnzyme = (wrapper: ReactWrapper, text: string) =>
|
||||||
|
|
|
@ -37,7 +37,7 @@ export function untilDispatch(
|
||||||
dispatcher = defaultDispatcher,
|
dispatcher = defaultDispatcher,
|
||||||
timeout = 1000,
|
timeout = 1000,
|
||||||
): Promise<ActionPayload> {
|
): Promise<ActionPayload> {
|
||||||
const callerLine = new Error().stack.toString().split("\n")[2];
|
const callerLine = new Error().stack!.toString().split("\n")[2];
|
||||||
if (typeof waitForAction === "string") {
|
if (typeof waitForAction === "string") {
|
||||||
const action = waitForAction;
|
const action = waitForAction;
|
||||||
waitForAction = (payload) => {
|
waitForAction = (payload) => {
|
||||||
|
@ -89,10 +89,10 @@ export function untilDispatch(
|
||||||
export function untilEmission(
|
export function untilEmission(
|
||||||
emitter: EventEmitter,
|
emitter: EventEmitter,
|
||||||
eventName: string,
|
eventName: string,
|
||||||
check: (...args: any[]) => boolean = undefined,
|
check?: (...args: any[]) => boolean,
|
||||||
timeout = 1000,
|
timeout = 1000,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const callerLine = new Error().stack.toString().split("\n")[2];
|
const callerLine = new Error().stack!.toString().split("\n")[2];
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let fulfilled = false;
|
let fulfilled = false;
|
||||||
let timeoutId: number;
|
let timeoutId: number;
|
||||||
|
|
|
@ -58,7 +58,7 @@ describe("theme", () => {
|
||||||
// When
|
// When
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
setTheme("light").then(resolve);
|
setTheme("light").then(resolve);
|
||||||
lightTheme.onload(void 0);
|
lightTheme.onload!({} as Event);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
|
@ -72,7 +72,7 @@ describe("theme", () => {
|
||||||
return expect(
|
return expect(
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
setTheme("light").catch((e) => resolve(e));
|
setTheme("light").catch((e) => resolve(e));
|
||||||
lightTheme.onerror("call onerror");
|
lightTheme.onerror!("call onerror");
|
||||||
}),
|
}),
|
||||||
).resolves.toBe("call onerror");
|
).resolves.toBe("call onerror");
|
||||||
});
|
});
|
||||||
|
|
|
@ -42,7 +42,7 @@ describe("useTopic", () => {
|
||||||
|
|
||||||
function RoomTopic() {
|
function RoomTopic() {
|
||||||
const topic = useTopic(room);
|
const topic = useTopic(room);
|
||||||
return <p>{topic.text}</p>;
|
return <p>{topic!.text}</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(<RoomTopic />);
|
render(<RoomTopic />);
|
||||||
|
|
|
@ -85,10 +85,6 @@ describe("MegolmExportEncryption", function () {
|
||||||
MegolmExportEncryption = require("../../src/utils/MegolmExportEncryption");
|
MegolmExportEncryption = require("../../src/utils/MegolmExportEncryption");
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
window.crypto = undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("decrypt", function () {
|
describe("decrypt", function () {
|
||||||
it("should handle missing header", function () {
|
it("should handle missing header", function () {
|
||||||
const input = stringToArray(`-----`);
|
const input = stringToArray(`-----`);
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue