Merge branch 'develop' into johannes/find-myself
This commit is contained in:
commit
d0e9331f07
612 changed files with 3608 additions and 2769 deletions
|
@ -15,8 +15,9 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React, { ReactNode } from "react";
|
||||
import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery";
|
||||
import { AutoDiscovery, ClientConfig } from "matrix-js-sdk/src/autodiscovery";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { IClientWellKnown } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { _t, _td, newTranslatableError } from "../languageHandler";
|
||||
import { makeType } from "./TypeUtils";
|
||||
|
@ -149,7 +150,7 @@ export default class AutoDiscoveryUtils {
|
|||
throw newTranslatableError(_td("No homeserver URL provided"));
|
||||
}
|
||||
|
||||
const wellknownConfig = {
|
||||
const wellknownConfig: IClientWellKnown = {
|
||||
"m.homeserver": {
|
||||
base_url: homeserverUrl,
|
||||
},
|
||||
|
@ -190,7 +191,7 @@ export default class AutoDiscoveryUtils {
|
|||
*/
|
||||
public static buildValidatedConfigFromDiscovery(
|
||||
serverName: string,
|
||||
discoveryResult,
|
||||
discoveryResult: ClientConfig,
|
||||
syntaxOnly = false,
|
||||
isSynthetic = false,
|
||||
): ValidatedServerConfig {
|
||||
|
@ -219,8 +220,8 @@ export default class AutoDiscoveryUtils {
|
|||
} else if (isResult && isResult.state !== AutoDiscovery.PROMPT) {
|
||||
logger.error("Error determining preferred identity server URL:", isResult);
|
||||
if (isResult.state === AutoDiscovery.FAIL_ERROR) {
|
||||
if (AutoDiscovery.ALL_ERRORS.indexOf(isResult.error) !== -1) {
|
||||
throw newTranslatableError(isResult.error);
|
||||
if (AutoDiscovery.ALL_ERRORS.indexOf(isResult.error as string) !== -1) {
|
||||
throw newTranslatableError(isResult.error as string);
|
||||
}
|
||||
throw newTranslatableError(_td("Unexpected error resolving identity server configuration"));
|
||||
} // else the error is not related to syntax - continue anyways.
|
||||
|
@ -235,8 +236,8 @@ export default class AutoDiscoveryUtils {
|
|||
if (hsResult.state !== AutoDiscovery.SUCCESS) {
|
||||
logger.error("Error processing homeserver config:", hsResult);
|
||||
if (!syntaxOnly || !AutoDiscoveryUtils.isLivelinessError(hsResult.error)) {
|
||||
if (AutoDiscovery.ALL_ERRORS.indexOf(hsResult.error) !== -1) {
|
||||
throw newTranslatableError(hsResult.error);
|
||||
if (AutoDiscovery.ALL_ERRORS.indexOf(hsResult.error as string) !== -1) {
|
||||
throw newTranslatableError(hsResult.error as string);
|
||||
}
|
||||
throw newTranslatableError(_td("Unexpected error resolving homeserver configuration"));
|
||||
} // else the error is not related to syntax - continue anyways.
|
||||
|
|
|
@ -35,8 +35,8 @@ export default class DMRoomMap {
|
|||
private static sharedInstance: DMRoomMap;
|
||||
|
||||
// TODO: convert these to maps
|
||||
private roomToUser: { [key: string]: string } = null;
|
||||
private userToRooms: { [key: string]: string[] } = null;
|
||||
private roomToUser: { [key: string]: string } | null = null;
|
||||
private userToRooms: { [key: string]: string[] } | null = null;
|
||||
private hasSentOutPatchDirectAccountDataPatch: boolean;
|
||||
private mDirectEvent: { [key: string]: string[] };
|
||||
|
||||
|
@ -98,7 +98,7 @@ export default class DMRoomMap {
|
|||
* modifying userToRooms
|
||||
*/
|
||||
private patchUpSelfDMs(userToRooms: Record<string, string[]>): boolean {
|
||||
const myUserId = this.matrixClient.getUserId();
|
||||
const myUserId = this.matrixClient.getUserId()!;
|
||||
const selfRoomIds = userToRooms[myUserId];
|
||||
if (selfRoomIds) {
|
||||
// any self-chats that should not be self-chats?
|
||||
|
@ -112,7 +112,7 @@ export default class DMRoomMap {
|
|||
}
|
||||
}
|
||||
})
|
||||
.filter((ids) => !!ids); //filter out
|
||||
.filter((ids) => !!ids) as { userId: string; roomId: string }[]; //filter out
|
||||
// these are actually all legit self-chats
|
||||
// bail out
|
||||
if (!guessedUserIdsThatChanged.length) {
|
||||
|
@ -132,6 +132,7 @@ export default class DMRoomMap {
|
|||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public getDMRoomsForUserId(userId: string): string[] {
|
||||
|
@ -145,7 +146,7 @@ export default class DMRoomMap {
|
|||
* @param {string[]} ids The identifiers (user IDs and email addresses) to look for.
|
||||
* @returns {Room} The DM room which all IDs given share, or falsy if no common room.
|
||||
*/
|
||||
public getDMRoomForIdentifiers(ids: string[]): Room {
|
||||
public getDMRoomForIdentifiers(ids: string[]): Room | null {
|
||||
// TODO: [Canonical DMs] Handle lookups for email addresses.
|
||||
// For now we'll pretend we only get user IDs and end up returning nothing for email addresses
|
||||
|
||||
|
@ -174,14 +175,14 @@ export default class DMRoomMap {
|
|||
}
|
||||
// Here, we return undefined if the room is not in the map:
|
||||
// the room ID you gave is not a DM room for any user.
|
||||
if (this.roomToUser[roomId] === undefined) {
|
||||
if (this.roomToUser![roomId] === undefined) {
|
||||
// no entry? if the room is an invite, look for the is_direct hint.
|
||||
const room = this.matrixClient.getRoom(roomId);
|
||||
if (room) {
|
||||
return room.getDMInviter();
|
||||
}
|
||||
}
|
||||
return this.roomToUser[roomId];
|
||||
return this.roomToUser![roomId];
|
||||
}
|
||||
|
||||
public getUniqueRoomsWithIndividuals(): { [userId: string]: Room } {
|
||||
|
@ -189,13 +190,23 @@ export default class DMRoomMap {
|
|||
return Object.keys(this.roomToUser)
|
||||
.map((r) => ({ userId: this.getUserIdForRoomId(r), room: this.matrixClient.getRoom(r) }))
|
||||
.filter((r) => r.userId && r.room && r.room.getInvitedAndJoinedMemberCount() === 2)
|
||||
.reduce((obj, r) => (obj[r.userId] = r.room) && obj, {});
|
||||
.reduce((obj, r) => (obj[r.userId] = r.room) && obj, {} as Record<string, Room>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns all room Ids from m.direct
|
||||
*/
|
||||
public getRoomIds(): Set<string> {
|
||||
return Object.values(this.mDirectEvent).reduce((prevRoomIds: Set<string>, roomIds: string[]): Set<string> => {
|
||||
roomIds.forEach((roomId) => prevRoomIds.add(roomId));
|
||||
return prevRoomIds;
|
||||
}, new Set<string>());
|
||||
}
|
||||
|
||||
private getUserToRooms(): { [key: string]: string[] } {
|
||||
if (!this.userToRooms) {
|
||||
const userToRooms = this.mDirectEvent;
|
||||
const myUserId = this.matrixClient.getUserId();
|
||||
const myUserId = this.matrixClient.getUserId()!;
|
||||
const selfDMs = userToRooms[myUserId];
|
||||
if (selfDMs?.length) {
|
||||
const neededPatching = this.patchUpSelfDMs(userToRooms);
|
||||
|
@ -218,7 +229,7 @@ export default class DMRoomMap {
|
|||
private populateRoomToUser(): void {
|
||||
this.roomToUser = {};
|
||||
for (const user of Object.keys(this.getUserToRooms())) {
|
||||
for (const roomId of this.userToRooms[user]) {
|
||||
for (const roomId of this.userToRooms![user]) {
|
||||
this.roomToUser[roomId] = user;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import { IEncryptedFile, IMediaEventInfo } from "../customisations/models/IMedia
|
|||
import { getBlobSafeMimeType } from "./blobs";
|
||||
|
||||
export class DownloadError extends Error {
|
||||
public constructor(e) {
|
||||
public constructor(e: Error) {
|
||||
super(e.message);
|
||||
this.name = "DownloadError";
|
||||
this.stack = e.stack;
|
||||
|
@ -31,7 +31,7 @@ export class DownloadError extends Error {
|
|||
}
|
||||
|
||||
export class DecryptError extends Error {
|
||||
public constructor(e) {
|
||||
public constructor(e: Error) {
|
||||
super(e.message);
|
||||
this.name = "DecryptError";
|
||||
this.stack = e.stack;
|
||||
|
|
|
@ -25,8 +25,8 @@ import DocumentOffset from "../editor/offset";
|
|||
* upon receiving the remote echo for an unsent event.
|
||||
*/
|
||||
export default class EditorStateTransfer {
|
||||
private serializedParts: SerializedPart[] = null;
|
||||
private caret: DocumentOffset = null;
|
||||
private serializedParts: SerializedPart[] | null = null;
|
||||
private caret: DocumentOffset | null = null;
|
||||
|
||||
public constructor(private readonly event: MatrixEvent) {}
|
||||
|
||||
|
@ -39,11 +39,11 @@ export default class EditorStateTransfer {
|
|||
return !!this.serializedParts;
|
||||
}
|
||||
|
||||
public getSerializedParts(): SerializedPart[] {
|
||||
public getSerializedParts(): SerializedPart[] | null {
|
||||
return this.serializedParts;
|
||||
}
|
||||
|
||||
public getCaret(): DocumentOffset {
|
||||
public getCaret(): DocumentOffset | null {
|
||||
return this.caret;
|
||||
}
|
||||
|
||||
|
|
|
@ -161,7 +161,7 @@ const getMsc3531Enabled = (): boolean => {
|
|||
if (msc3531Enabled === null) {
|
||||
msc3531Enabled = SettingsStore.getValue("feature_msc3531_hide_messages_pending_moderation");
|
||||
}
|
||||
return msc3531Enabled;
|
||||
return msc3531Enabled!;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -193,14 +193,14 @@ export function getMessageModerationState(mxEvent: MatrixEvent, client?: MatrixC
|
|||
const room = client.getRoom(mxEvent.getRoomId());
|
||||
if (
|
||||
EVENT_VISIBILITY_CHANGE_TYPE.name &&
|
||||
room.currentState.maySendStateEvent(EVENT_VISIBILITY_CHANGE_TYPE.name, client.getUserId())
|
||||
room?.currentState.maySendStateEvent(EVENT_VISIBILITY_CHANGE_TYPE.name, client.getUserId()!)
|
||||
) {
|
||||
// We're a moderator (as indicated by prefixed event name), show the message.
|
||||
return MessageModerationState.SEE_THROUGH_FOR_CURRENT_USER;
|
||||
}
|
||||
if (
|
||||
EVENT_VISIBILITY_CHANGE_TYPE.altName &&
|
||||
room.currentState.maySendStateEvent(EVENT_VISIBILITY_CHANGE_TYPE.altName, client.getUserId())
|
||||
room?.currentState.maySendStateEvent(EVENT_VISIBILITY_CHANGE_TYPE.altName, client.getUserId()!)
|
||||
) {
|
||||
// We're a moderator (as indicated by unprefixed event name), show the message.
|
||||
return MessageModerationState.SEE_THROUGH_FOR_CURRENT_USER;
|
||||
|
@ -220,7 +220,7 @@ export async function fetchInitialEvent(
|
|||
roomId: string,
|
||||
eventId: string,
|
||||
): Promise<MatrixEvent | null> {
|
||||
let initialEvent: MatrixEvent;
|
||||
let initialEvent: MatrixEvent | null;
|
||||
|
||||
try {
|
||||
const eventData = await client.fetchRoomEvent(roomId, eventId);
|
||||
|
@ -231,12 +231,12 @@ export async function fetchInitialEvent(
|
|||
}
|
||||
|
||||
if (client.supportsThreads() && initialEvent?.isRelation(THREAD_RELATION_TYPE.name) && !initialEvent.getThread()) {
|
||||
const threadId = initialEvent.threadRootId;
|
||||
const threadId = initialEvent.threadRootId!;
|
||||
const room = client.getRoom(roomId);
|
||||
const mapper = client.getEventMapper();
|
||||
const rootEvent = room.findEventById(threadId) ?? mapper(await client.fetchRoomEvent(roomId, threadId));
|
||||
const rootEvent = room?.findEventById(threadId) ?? mapper(await client.fetchRoomEvent(roomId, threadId));
|
||||
try {
|
||||
room.createThread(threadId, rootEvent, [initialEvent], true);
|
||||
room?.createThread(threadId, rootEvent, [initialEvent], true);
|
||||
} catch (e) {
|
||||
logger.warn("Could not find root event: " + threadId);
|
||||
}
|
||||
|
@ -271,12 +271,12 @@ export const isLocationEvent = (event: MatrixEvent): boolean => {
|
|||
const eventType = event.getType();
|
||||
return (
|
||||
M_LOCATION.matches(eventType) ||
|
||||
(eventType === EventType.RoomMessage && M_LOCATION.matches(event.getContent().msgtype))
|
||||
(eventType === EventType.RoomMessage && M_LOCATION.matches(event.getContent().msgtype!))
|
||||
);
|
||||
};
|
||||
|
||||
export function hasThreadSummary(event: MatrixEvent): boolean {
|
||||
return event.isThreadRoot && event.getThread()?.length && !!event.getThread().replyToEvent;
|
||||
return event.isThreadRoot && !!event.getThread()?.length && !!event.getThread()!.replyToEvent;
|
||||
}
|
||||
|
||||
export function canPinEvent(event: MatrixEvent): boolean {
|
||||
|
|
|
@ -19,5 +19,5 @@ import SettingsStore from "../settings/SettingsStore";
|
|||
import { UIFeature } from "../settings/UIFeature";
|
||||
|
||||
export function shouldShowFeedback(): boolean {
|
||||
return SdkConfig.get().bug_report_endpoint_url && SettingsStore.getValue(UIFeature.Feedback);
|
||||
return !!SdkConfig.get().bug_report_endpoint_url && SettingsStore.getValue(UIFeature.Feedback);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ export type getIframeFn = () => HTMLIFrameElement; // eslint-disable-line @types
|
|||
|
||||
export const DEFAULT_STYLES = {
|
||||
imgSrc: "",
|
||||
imgStyle: null, // css props
|
||||
imgStyle: null as string | null, // css props
|
||||
style: "",
|
||||
textContent: "",
|
||||
};
|
||||
|
@ -68,14 +68,14 @@ function getManagedIframe(): { iframe: HTMLIFrameElement; onLoadPromise: Promise
|
|||
* additional control over the styling/position of the iframe itself.
|
||||
*/
|
||||
export class FileDownloader {
|
||||
private onLoadPromise: Promise<void>;
|
||||
private onLoadPromise?: Promise<void>;
|
||||
|
||||
/**
|
||||
* Creates a new file downloader
|
||||
* @param iframeFn Function to get a pre-configured iframe. Set to null to have the downloader
|
||||
* use a generic, hidden, iframe.
|
||||
*/
|
||||
public constructor(private iframeFn: getIframeFn = null) {}
|
||||
public constructor(private iframeFn?: getIframeFn) {}
|
||||
|
||||
private get iframe(): HTMLIFrameElement {
|
||||
const iframe = this.iframeFn?.();
|
||||
|
@ -84,14 +84,14 @@ export class FileDownloader {
|
|||
this.onLoadPromise = managed.onLoadPromise;
|
||||
return managed.iframe;
|
||||
}
|
||||
this.onLoadPromise = null;
|
||||
this.onLoadPromise = undefined;
|
||||
return iframe;
|
||||
}
|
||||
|
||||
public async download({ blob, name, autoDownload = true, opts = DEFAULT_STYLES }: DownloadOptions): Promise<void> {
|
||||
const iframe = this.iframe; // get the iframe first just in case we need to await onload
|
||||
if (this.onLoadPromise) await this.onLoadPromise;
|
||||
iframe.contentWindow.postMessage(
|
||||
iframe.contentWindow?.postMessage(
|
||||
{
|
||||
...opts,
|
||||
blob: blob,
|
||||
|
|
|
@ -15,6 +15,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ReactElement, ReactNode } from "react";
|
||||
|
||||
import { _t } from "../languageHandler";
|
||||
import { jsxJoin } from "./ReactUtils";
|
||||
|
||||
|
@ -64,7 +66,7 @@ export function formatBytes(bytes: number, decimals = 2): string {
|
|||
* @return {string}
|
||||
*/
|
||||
export function formatCryptoKey(key: string): string {
|
||||
return key.match(/.{1,4}/g).join(" ");
|
||||
return key.match(/.{1,4}/g)!.join(" ");
|
||||
}
|
||||
/**
|
||||
* calculates a numeric hash for a given string
|
||||
|
@ -105,9 +107,9 @@ export function getUserNameColorClass(userId: string): string {
|
|||
* between each item, but with the last item appended as " and [lastItem]".
|
||||
*/
|
||||
export function formatCommaSeparatedList(items: string[], itemLimit?: number): string;
|
||||
export function formatCommaSeparatedList(items: JSX.Element[], itemLimit?: number): JSX.Element;
|
||||
export function formatCommaSeparatedList(items: Array<JSX.Element | string>, itemLimit?: number): JSX.Element | string;
|
||||
export function formatCommaSeparatedList(items: Array<JSX.Element | string>, itemLimit?: number): JSX.Element | string {
|
||||
export function formatCommaSeparatedList(items: ReactElement[], itemLimit?: number): ReactElement;
|
||||
export function formatCommaSeparatedList(items: ReactNode[], itemLimit?: number): ReactNode;
|
||||
export function formatCommaSeparatedList(items: ReactNode[], itemLimit?: number): ReactNode {
|
||||
const remaining = itemLimit === undefined ? 0 : Math.max(items.length - itemLimit, 0);
|
||||
if (items.length === 0) {
|
||||
return "";
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
import SdkConfig from "../SdkConfig";
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
|
||||
export function getHostingLink(campaign: string): string {
|
||||
export function getHostingLink(campaign: string): string | null {
|
||||
const hostingLink = SdkConfig.get().hosting_signup_link;
|
||||
if (!hostingLink) return null;
|
||||
if (!campaign) return hostingLink;
|
||||
|
|
|
@ -18,7 +18,7 @@ import { arrayHasDiff } from "./arrays";
|
|||
|
||||
export function mayBeAnimated(mimeType?: string): boolean {
|
||||
// AVIF animation support at the time of writing is only available in Chrome hence not having `blobIsAnimated` check
|
||||
return ["image/gif", "image/webp", "image/png", "image/apng", "image/avif"].includes(mimeType);
|
||||
return ["image/gif", "image/webp", "image/png", "image/apng", "image/avif"].includes(mimeType!);
|
||||
}
|
||||
|
||||
function arrayBufferRead(arr: ArrayBuffer, start: number, len: number): Uint8Array {
|
||||
|
|
|
@ -20,29 +20,21 @@ limitations under the License.
|
|||
* @param {WheelEvent} event to normalize
|
||||
* @returns {WheelEvent} normalized event event
|
||||
*/
|
||||
export function normalizeWheelEvent(event: WheelEvent): WheelEvent {
|
||||
export function normalizeWheelEvent({ deltaMode, deltaX, deltaY, deltaZ, ...event }: WheelEvent): WheelEvent {
|
||||
const LINE_HEIGHT = 18;
|
||||
|
||||
let deltaX;
|
||||
let deltaY;
|
||||
let deltaZ;
|
||||
|
||||
if (event.deltaMode === 1) {
|
||||
if (deltaMode === 1) {
|
||||
// Units are lines
|
||||
deltaX = event.deltaX * LINE_HEIGHT;
|
||||
deltaY = event.deltaY * LINE_HEIGHT;
|
||||
deltaZ = event.deltaZ * LINE_HEIGHT;
|
||||
} else {
|
||||
deltaX = event.deltaX;
|
||||
deltaY = event.deltaY;
|
||||
deltaZ = event.deltaZ;
|
||||
deltaX *= LINE_HEIGHT;
|
||||
deltaY *= LINE_HEIGHT;
|
||||
deltaZ *= LINE_HEIGHT;
|
||||
}
|
||||
|
||||
return new WheelEvent("syntheticWheel", {
|
||||
deltaMode: 0,
|
||||
deltaY: deltaY,
|
||||
deltaX: deltaX,
|
||||
deltaZ: deltaZ,
|
||||
deltaY,
|
||||
deltaX,
|
||||
deltaZ,
|
||||
...event,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -57,8 +57,8 @@ export default class MultiInviter {
|
|||
private _fatal = false;
|
||||
private completionStates: CompletionStates = {}; // State of each address (invited or error)
|
||||
private errors: Record<string, IError> = {}; // { address: {errorText, errcode} }
|
||||
private deferred: IDeferred<CompletionStates> = null;
|
||||
private reason: string = null;
|
||||
private deferred: IDeferred<CompletionStates> | null = null;
|
||||
private reason: string | undefined;
|
||||
|
||||
/**
|
||||
* @param {string} roomId The ID of the room to invite to
|
||||
|
@ -81,7 +81,7 @@ export default class MultiInviter {
|
|||
* @param {boolean} sendSharedHistoryKeys whether to share e2ee keys with the invitees if applicable.
|
||||
* @returns {Promise} Resolved when all invitations in the queue are complete
|
||||
*/
|
||||
public invite(addresses, reason?: string, sendSharedHistoryKeys = false): Promise<CompletionStates> {
|
||||
public invite(addresses: string[], reason?: string, sendSharedHistoryKeys = false): Promise<CompletionStates> {
|
||||
if (this.addresses.length > 0) {
|
||||
throw new Error("Already inviting/invited");
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ export default class MultiInviter {
|
|||
if (!this.busy) return;
|
||||
|
||||
this.canceled = true;
|
||||
this.deferred.reject(new Error("canceled"));
|
||||
this.deferred?.reject(new Error("canceled"));
|
||||
}
|
||||
|
||||
public getCompletionState(addr: string): InviteState {
|
||||
|
@ -216,7 +216,7 @@ export default class MultiInviter {
|
|||
|
||||
const isSpace = this.roomId && this.matrixClient.getRoom(this.roomId)?.isSpaceRoom();
|
||||
|
||||
let errorText: string;
|
||||
let errorText: string | undefined;
|
||||
let fatal = false;
|
||||
switch (err.errcode) {
|
||||
case "M_FORBIDDEN":
|
||||
|
@ -310,7 +310,7 @@ export default class MultiInviter {
|
|||
if (unknownProfileUsers.length > 0) {
|
||||
const inviteUnknowns = (): void => {
|
||||
const promises = unknownProfileUsers.map((u) => this.doInvite(u, true));
|
||||
Promise.all(promises).then(() => this.deferred.resolve(this.completionStates));
|
||||
Promise.all(promises).then(() => this.deferred?.resolve(this.completionStates));
|
||||
};
|
||||
|
||||
if (!SettingsStore.getValue("promptBeforeInviteUnknownUsers", this.roomId)) {
|
||||
|
@ -330,13 +330,13 @@ export default class MultiInviter {
|
|||
for (const addr of unknownProfileUsers) {
|
||||
this.completionStates[addr] = InviteState.Invited;
|
||||
}
|
||||
this.deferred.resolve(this.completionStates);
|
||||
this.deferred?.resolve(this.completionStates);
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.deferred.resolve(this.completionStates);
|
||||
this.deferred?.resolve(this.completionStates);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -361,6 +361,6 @@ export default class MultiInviter {
|
|||
.then(() => {
|
||||
this.inviteMore(nextIndex + 1, ignoreProfile);
|
||||
})
|
||||
.catch(() => this.deferred.resolve(this.completionStates));
|
||||
.catch(() => this.deferred?.resolve(this.completionStates));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,12 +60,12 @@ _td("Short keyboard patterns are easy to guess");
|
|||
* @param {string} password Password to score
|
||||
* @returns {object} Score result with `score` and `feedback` properties
|
||||
*/
|
||||
export function scorePassword(password: string): zxcvbn.ZXCVBNResult {
|
||||
export function scorePassword(password: string): zxcvbn.ZXCVBNResult | null {
|
||||
if (password.length === 0) return null;
|
||||
|
||||
const userInputs = ZXCVBN_USER_INPUTS.slice();
|
||||
if (MatrixClientPeg.get()) {
|
||||
userInputs.push(MatrixClientPeg.get().getUserIdLocalpart());
|
||||
userInputs.push(MatrixClientPeg.get().getUserIdLocalpart()!);
|
||||
}
|
||||
|
||||
let zxcvbnResult = zxcvbn(password, userInputs);
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
|
||||
/**
|
||||
* Joins an array into one value with a joiner. E.g. join(["hello", "world"], " ") -> <span>hello world</span>
|
||||
|
@ -22,8 +22,8 @@ import React from "react";
|
|||
* @param joiner the string/JSX.Element to join with
|
||||
* @returns the joined array
|
||||
*/
|
||||
export function jsxJoin(array: Array<string | JSX.Element>, joiner?: string | JSX.Element): JSX.Element {
|
||||
const newArray = [];
|
||||
export function jsxJoin(array: ReactNode[], joiner?: string | JSX.Element): JSX.Element {
|
||||
const newArray: ReactNode[] = [];
|
||||
array.forEach((element, index) => {
|
||||
newArray.push(element, index === array.length - 1 ? null : joiner);
|
||||
});
|
||||
|
|
|
@ -69,7 +69,15 @@ export function getNestedReplyText(
|
|||
): { body: string; html: string } | null {
|
||||
if (!ev) return null;
|
||||
|
||||
let { body, formatted_body: html, msgtype } = ev.getContent();
|
||||
let {
|
||||
body,
|
||||
formatted_body: html,
|
||||
msgtype,
|
||||
} = ev.getContent<{
|
||||
body: string;
|
||||
msgtype?: string;
|
||||
formatted_body?: string;
|
||||
}>();
|
||||
if (getParentEventId(ev)) {
|
||||
if (body) body = stripPlainReply(body);
|
||||
}
|
||||
|
@ -88,8 +96,8 @@ export function getNestedReplyText(
|
|||
|
||||
// dev note: do not rely on `body` being safe for HTML usage below.
|
||||
|
||||
const evLink = permalinkCreator.forEvent(ev.getId());
|
||||
const userLink = makeUserPermalink(ev.getSender());
|
||||
const evLink = permalinkCreator.forEvent(ev.getId()!);
|
||||
const userLink = makeUserPermalink(ev.getSender()!);
|
||||
const mxid = ev.getSender();
|
||||
|
||||
if (M_BEACON_INFO.matches(ev.getType())) {
|
||||
|
@ -183,8 +191,8 @@ export function makeReplyMixIn(ev?: MatrixEvent): IEventRelation {
|
|||
// with those that do. They should set the m.in_reply_to part as usual, and then add on
|
||||
// "rel_type": "m.thread" and "event_id": "$thread_root", copying $thread_root from the replied-to event.
|
||||
const relation = ev.getRelation();
|
||||
mixin.rel_type = relation.rel_type;
|
||||
mixin.event_id = relation.event_id;
|
||||
mixin.rel_type = relation?.rel_type;
|
||||
mixin.event_id = relation?.event_id;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ export class Singleflight {
|
|||
* @param {string} key A string key relevant to that instance to namespace under.
|
||||
* @returns {SingleflightContext} Returns the context to execute the function.
|
||||
*/
|
||||
public static for(instance: Object, key: string): SingleflightContext {
|
||||
public static for(instance?: Object | null, key?: string | null): SingleflightContext {
|
||||
if (!instance || !key) throw new Error("An instance and key must be supplied");
|
||||
return new SingleflightContext(instance, key);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ export class SnakedObject<T = Record<string, any>> {
|
|||
const val = this.obj[key];
|
||||
if (val !== undefined) return val;
|
||||
|
||||
return this.obj[altCaseName ?? snakeToCamel(key)];
|
||||
return this.obj[<K>(altCaseName ?? snakeToCamel(key))];
|
||||
}
|
||||
|
||||
// Make JSON.stringify() pretend that everything is fine
|
||||
|
|
|
@ -30,7 +30,7 @@ export default class Timer {
|
|||
private startTs?: number;
|
||||
private promise: Promise<void>;
|
||||
private resolve: () => void;
|
||||
private reject: (Error) => void;
|
||||
private reject: (err: Error) => void;
|
||||
|
||||
public constructor(private timeout: number) {
|
||||
this.setNotStarted();
|
||||
|
|
|
@ -40,7 +40,7 @@ export function wrapRequestWithDialog<R, A = any>(
|
|||
Modal.createDialog(InteractiveAuthDialog, {
|
||||
...opts,
|
||||
authData: error.data,
|
||||
makeRequest: (authData) => boundFunction(authData, ...args),
|
||||
makeRequest: (authData?: IAuthData) => boundFunction(authData, ...args),
|
||||
onFinished: (success, result) => {
|
||||
if (success) {
|
||||
resolve(result);
|
||||
|
|
|
@ -25,5 +25,5 @@ export class ValidatedServerConfig {
|
|||
// when the server config is based on static URLs the hsName is not resolvable and things may wish to use hsUrl
|
||||
public isNameResolvable: boolean;
|
||||
|
||||
public warning: string;
|
||||
public warning: string | Error;
|
||||
}
|
||||
|
|
|
@ -50,12 +50,12 @@ export function getCallBehaviourWellKnown(): ICallBehaviourWellKnown {
|
|||
return clientWellKnown?.[CALL_BEHAVIOUR_WK_KEY];
|
||||
}
|
||||
|
||||
export function getE2EEWellKnown(): IE2EEWellKnown {
|
||||
export function getE2EEWellKnown(): IE2EEWellKnown | null {
|
||||
const clientWellKnown = MatrixClientPeg.get().getClientWellKnown();
|
||||
if (clientWellKnown && clientWellKnown[E2EE_WK_KEY]) {
|
||||
if (clientWellKnown?.[E2EE_WK_KEY]) {
|
||||
return clientWellKnown[E2EE_WK_KEY];
|
||||
}
|
||||
if (clientWellKnown && clientWellKnown[E2EE_WK_KEY_DEPRECATED]) {
|
||||
if (clientWellKnown?.[E2EE_WK_KEY_DEPRECATED]) {
|
||||
return clientWellKnown[E2EE_WK_KEY_DEPRECATED];
|
||||
}
|
||||
return null;
|
||||
|
@ -78,8 +78,7 @@ export function embeddedPagesFromWellKnown(clientWellKnown?: IClientWellKnown):
|
|||
}
|
||||
|
||||
export function isSecureBackupRequired(): boolean {
|
||||
const wellKnown = getE2EEWellKnown();
|
||||
return wellKnown && wellKnown["secure_backup_required"] === true;
|
||||
return getE2EEWellKnown()?.["secure_backup_required"] === true;
|
||||
}
|
||||
|
||||
export enum SecureBackupSetupMethod {
|
||||
|
|
|
@ -119,7 +119,7 @@ export default class WidgetUtils {
|
|||
if (
|
||||
testUrl.protocol === scalarUrl.protocol &&
|
||||
testUrl.host === scalarUrl.host &&
|
||||
testUrl.pathname.startsWith(scalarUrl.pathname)
|
||||
testUrl.pathname?.startsWith(scalarUrl.pathname)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
@ -143,8 +143,8 @@ export default class WidgetUtils {
|
|||
return new Promise((resolve, reject) => {
|
||||
// Tests an account data event, returning true if it's in the state
|
||||
// we're waiting for it to be in
|
||||
function eventInIntendedState(ev): boolean {
|
||||
if (!ev || !ev.getContent()) return false;
|
||||
function eventInIntendedState(ev?: MatrixEvent): boolean {
|
||||
if (!ev) return false;
|
||||
if (add) {
|
||||
return ev.getContent()[widgetId] !== undefined;
|
||||
} else {
|
||||
|
@ -158,7 +158,7 @@ export default class WidgetUtils {
|
|||
return;
|
||||
}
|
||||
|
||||
function onAccountData(ev): void {
|
||||
function onAccountData(ev: MatrixEvent): void {
|
||||
const currentAccountDataEvent = MatrixClientPeg.get().getAccountData("m.widgets");
|
||||
if (eventInIntendedState(currentAccountDataEvent)) {
|
||||
MatrixClientPeg.get().removeListener(ClientEvent.AccountData, onAccountData);
|
||||
|
@ -190,12 +190,12 @@ export default class WidgetUtils {
|
|||
return new Promise((resolve, reject) => {
|
||||
// Tests a list of state events, returning true if it's in the state
|
||||
// we're waiting for it to be in
|
||||
function eventsInIntendedState(evList: MatrixEvent[]): boolean {
|
||||
const widgetPresent = evList.some((ev) => {
|
||||
function eventsInIntendedState(evList?: MatrixEvent[]): boolean {
|
||||
const widgetPresent = evList?.some((ev) => {
|
||||
return ev.getContent() && ev.getContent()["id"] === widgetId;
|
||||
});
|
||||
if (add) {
|
||||
return widgetPresent;
|
||||
return !!widgetPresent;
|
||||
} else {
|
||||
return !widgetPresent;
|
||||
}
|
||||
|
@ -203,7 +203,7 @@ export default class WidgetUtils {
|
|||
|
||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
const startingWidgetEvents = room.currentState.getStateEvents("im.vector.modular.widgets");
|
||||
const startingWidgetEvents = room?.currentState.getStateEvents("im.vector.modular.widgets");
|
||||
if (eventsInIntendedState(startingWidgetEvents)) {
|
||||
resolve();
|
||||
return;
|
||||
|
@ -213,7 +213,7 @@ export default class WidgetUtils {
|
|||
if (ev.getRoomId() !== roomId || ev.getType() !== "im.vector.modular.widgets") return;
|
||||
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
const currentWidgetEvents = room.currentState.getStateEvents("im.vector.modular.widgets");
|
||||
const currentWidgetEvents = room?.currentState.getStateEvents("im.vector.modular.widgets");
|
||||
|
||||
if (eventsInIntendedState(currentWidgetEvents)) {
|
||||
MatrixClientPeg.get().removeListener(RoomStateEvent.Events, onRoomStateEvents);
|
||||
|
@ -261,7 +261,7 @@ export default class WidgetUtils {
|
|||
if (addingWidget) {
|
||||
userWidgets[widgetId] = {
|
||||
content: content,
|
||||
sender: client.getUserId(),
|
||||
sender: client.getUserId()!,
|
||||
state_key: widgetId,
|
||||
type: "m.widget",
|
||||
id: widgetId,
|
||||
|
@ -299,7 +299,7 @@ export default class WidgetUtils {
|
|||
content = {
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
// For now we'll send the legacy event type for compatibility with older apps/elements
|
||||
type: widgetType.legacy,
|
||||
type: widgetType?.legacy,
|
||||
url: widgetUrl,
|
||||
name: widgetName,
|
||||
data: widgetData,
|
||||
|
@ -513,7 +513,7 @@ export default class WidgetUtils {
|
|||
"roomId=$matrix_room_id",
|
||||
"theme=$theme",
|
||||
"roomName=$roomName",
|
||||
`supportsScreensharing=${PlatformPeg.get().supportsJitsiScreensharing()}`,
|
||||
`supportsScreensharing=${PlatformPeg.get()?.supportsJitsiScreensharing()}`,
|
||||
"language=$org.matrix.msc2873.client_language",
|
||||
];
|
||||
if (opts.auth) {
|
||||
|
|
|
@ -37,7 +37,7 @@ const GeolocationOptions = {
|
|||
};
|
||||
|
||||
const isGeolocationPositionError = (error: unknown): error is GeolocationPositionError =>
|
||||
typeof error === "object" && !!error["PERMISSION_DENIED"];
|
||||
typeof error === "object" && !!(error as GeolocationPositionError)["PERMISSION_DENIED"];
|
||||
/**
|
||||
* Maps GeolocationPositionError to our GeolocationError enum
|
||||
*/
|
||||
|
@ -132,7 +132,7 @@ export const watchPosition = (
|
|||
onWatchPositionError: (error: GeolocationError) => void,
|
||||
): ClearWatchCallback => {
|
||||
try {
|
||||
const onError = (error): void => onWatchPositionError(mapGeolocationError(error));
|
||||
const onError = (error: GeolocationPositionError): void => onWatchPositionError(mapGeolocationError(error));
|
||||
const watchId = getGeolocation().watchPosition(onWatchPosition, onError, GeolocationOptions);
|
||||
const clearWatch = (): void => {
|
||||
getGeolocation().clearWatch(watchId);
|
||||
|
|
|
@ -31,7 +31,7 @@ const localStorage = window.localStorage;
|
|||
|
||||
// just *accessing* indexedDB throws an exception in firefox with
|
||||
// indexeddb disabled.
|
||||
let indexedDB;
|
||||
let indexedDB: IDBFactory;
|
||||
try {
|
||||
indexedDB = window.indexedDB;
|
||||
} catch (e) {}
|
||||
|
|
|
@ -31,10 +31,10 @@ import { determineCreateRoomEncryptionOption, Member } from "../../../src/utils/
|
|||
* @returns {Promise<LocalRoom>} Resolves to the new local room
|
||||
*/
|
||||
export async function createDmLocalRoom(client: MatrixClient, targets: Member[]): Promise<LocalRoom> {
|
||||
const userId = client.getUserId();
|
||||
const userId = client.getUserId()!;
|
||||
|
||||
const localRoom = new LocalRoom(LOCAL_ROOM_ID_PREFIX + client.makeTxnId(), client, userId);
|
||||
const events = [];
|
||||
const events: MatrixEvent[] = [];
|
||||
|
||||
events.push(
|
||||
new MatrixEvent({
|
||||
|
@ -121,7 +121,7 @@ export async function createDmLocalRoom(client: MatrixClient, targets: Member[])
|
|||
localRoom.updateMyMembership("join");
|
||||
localRoom.addLiveEvents(events);
|
||||
localRoom.currentState.setStateEvents(events);
|
||||
localRoom.name = localRoom.getDefaultRoomName(client.getUserId());
|
||||
localRoom.name = localRoom.getDefaultRoomName(client.getUserId()!);
|
||||
client.store.storeRoom(localRoom);
|
||||
|
||||
return localRoom;
|
||||
|
|
|
@ -21,17 +21,8 @@ import { isLocalRoom } from "../localRoom/isLocalRoom";
|
|||
import { isJoinedOrNearlyJoined } from "../membership";
|
||||
import { getFunctionalMembers } from "../room/getFunctionalMembers";
|
||||
|
||||
/**
|
||||
* Tries to find a DM room with a specific user.
|
||||
*
|
||||
* @param {MatrixClient} client
|
||||
* @param {string} userId ID of the user to find the DM for
|
||||
* @returns {Room} Room if found
|
||||
*/
|
||||
export function findDMForUser(client: MatrixClient, userId: string): Room {
|
||||
const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId);
|
||||
const rooms = roomIds.map((id) => client.getRoom(id));
|
||||
const suitableDMRooms = rooms
|
||||
function extractSuitableRoom(rooms: Room[], userId: string): Room | undefined {
|
||||
const suitableRooms = rooms
|
||||
.filter((r) => {
|
||||
// Validate that we are joined and the other person is also joined. We'll also make sure
|
||||
// that the room also looks like a DM (until we have canonical DMs to tell us). For now,
|
||||
|
@ -44,7 +35,7 @@ export function findDMForUser(client: MatrixClient, userId: string): Room {
|
|||
const functionalUsers = getFunctionalMembers(r);
|
||||
const members = r.currentState.getMembers();
|
||||
const joinedMembers = members.filter(
|
||||
(m) => !functionalUsers.includes(m.userId) && isJoinedOrNearlyJoined(m.membership),
|
||||
(m) => !functionalUsers.includes(m.userId) && m.membership && isJoinedOrNearlyJoined(m.membership),
|
||||
);
|
||||
const otherMember = joinedMembers.find((m) => m.userId === userId);
|
||||
return otherMember && joinedMembers.length === 2;
|
||||
|
@ -54,7 +45,34 @@ export function findDMForUser(client: MatrixClient, userId: string): Room {
|
|||
.sort((r1, r2) => {
|
||||
return r2.getLastActiveTimestamp() - r1.getLastActiveTimestamp();
|
||||
});
|
||||
if (suitableDMRooms.length) {
|
||||
return suitableDMRooms[0];
|
||||
|
||||
if (suitableRooms.length) {
|
||||
return suitableRooms[0];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find a DM room with a specific user.
|
||||
*
|
||||
* @param {MatrixClient} client
|
||||
* @param {string} userId ID of the user to find the DM for
|
||||
* @returns {Room | undefined} Room if found
|
||||
*/
|
||||
export function findDMForUser(client: MatrixClient, userId: string): Room | undefined {
|
||||
const roomIdsForUserId = DMRoomMap.shared().getDMRoomsForUserId(userId);
|
||||
const roomsForUserId = roomIdsForUserId.map((id) => client.getRoom(id)).filter((r): r is Room => r !== null);
|
||||
const suitableRoomForUserId = extractSuitableRoom(roomsForUserId, userId);
|
||||
|
||||
if (suitableRoomForUserId) {
|
||||
return suitableRoomForUserId;
|
||||
}
|
||||
|
||||
// Try to find in all rooms as a fallback
|
||||
const allRoomIds = DMRoomMap.shared().getRoomIds();
|
||||
const allRooms = Array.from(allRoomIds)
|
||||
.map((id) => client.getRoom(id))
|
||||
.filter((r): r is Room => r !== null);
|
||||
return extractSuitableRoom(allRooms, userId);
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import { findDMForUser } from "./findDMForUser";
|
|||
*/
|
||||
export function findDMRoom(client: MatrixClient, targets: Member[]): Room | null {
|
||||
const targetIds = targets.map((t) => t.userId);
|
||||
let existingRoom: Room;
|
||||
let existingRoom: Room | undefined;
|
||||
if (targetIds.length === 1) {
|
||||
existingRoom = findDMForUser(client, targetIds[0]);
|
||||
} else {
|
||||
|
|
|
@ -35,7 +35,7 @@ export async function startDm(client: MatrixClient, targets: Member[], showSpinn
|
|||
const targetIds = targets.map((t) => t.userId);
|
||||
|
||||
// Check if there is already a DM with these people and reuse it if possible.
|
||||
let existingRoom: Room;
|
||||
let existingRoom: Room | undefined;
|
||||
if (targetIds.length === 1) {
|
||||
existingRoom = findDMForUser(client, targetIds[0]);
|
||||
} else {
|
||||
|
@ -66,12 +66,15 @@ export async function startDm(client: MatrixClient, targets: Member[], showSpinn
|
|||
}
|
||||
|
||||
if (targetIds.length > 1) {
|
||||
createRoomOptions.createOpts = targetIds.reduce(
|
||||
createRoomOptions.createOpts = targetIds.reduce<{
|
||||
invite_3pid: IInvite3PID[];
|
||||
invite: string[];
|
||||
}>(
|
||||
(roomOptions, address) => {
|
||||
const type = getAddressType(address);
|
||||
if (type === "email") {
|
||||
const invite: IInvite3PID = {
|
||||
id_server: client.getIdentityServerUrl(true),
|
||||
id_server: client.getIdentityServerUrl(true)!,
|
||||
medium: "email",
|
||||
address,
|
||||
};
|
||||
|
|
|
@ -22,6 +22,8 @@ export enum ExportFormat {
|
|||
Json = "Json",
|
||||
}
|
||||
|
||||
export type ExportFormatKey = "Html" | "PlainText" | "Json";
|
||||
|
||||
export enum ExportType {
|
||||
Timeline = "Timeline",
|
||||
Beginning = "Beginning",
|
||||
|
@ -29,6 +31,8 @@ export enum ExportType {
|
|||
// START_DATE = "START_DATE",
|
||||
}
|
||||
|
||||
export type ExportTypeKey = "Timeline" | "Beginning" | "LastNMessages";
|
||||
|
||||
export const textForFormat = (format: ExportFormat): string => {
|
||||
switch (format) {
|
||||
case ExportFormat.Html:
|
||||
|
|
|
@ -21,7 +21,7 @@ import { _t } from "../languageHandler";
|
|||
import DMRoomMap from "./DMRoomMap";
|
||||
|
||||
export interface RoomContextDetails {
|
||||
details: string;
|
||||
details: string | null;
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,13 +15,13 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import { BlurhashEncoder } from "../BlurhashEncoder";
|
||||
import { IEncryptedFile } from "../customisations/models/IMediaEventContent";
|
||||
|
||||
type ThumbnailableElement = HTMLImageElement | HTMLVideoElement;
|
||||
|
||||
interface IThumbnail {
|
||||
info: {
|
||||
// eslint-disable-next-line camelcase
|
||||
thumbnail_info: {
|
||||
thumbnail_info?: {
|
||||
w: number;
|
||||
h: number;
|
||||
mimetype: string;
|
||||
|
@ -30,6 +30,8 @@ interface IThumbnail {
|
|||
w: number;
|
||||
h: number;
|
||||
[BLURHASH_FIELD]: string;
|
||||
thumbnail_url?: string;
|
||||
thumbnail_file?: IEncryptedFile;
|
||||
};
|
||||
thumbnail: Blob;
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ export const makeMapSiteLink = (coords: GeolocationCoordinates): string => {
|
|||
};
|
||||
|
||||
export const createMapSiteLinkFromEvent = (event: MatrixEvent): string => {
|
||||
const content: Object = event.getContent();
|
||||
const content = event.getContent();
|
||||
const mLocation = content[M_LOCATION.name];
|
||||
if (mLocation !== undefined) {
|
||||
const uri = mLocation["uri"];
|
||||
|
|
|
@ -37,7 +37,7 @@ export const useMap = ({ interactive, bodyId, onError }: UseMapProps): MapLibreM
|
|||
useEffect(
|
||||
() => {
|
||||
try {
|
||||
setMap(createMap(interactive, bodyId, onError));
|
||||
setMap(createMap(!!interactive, bodyId, onError));
|
||||
} catch (error) {
|
||||
onError?.(error);
|
||||
}
|
||||
|
|
|
@ -43,13 +43,13 @@ export class EnhancedMap<K, V> extends Map<K, V> {
|
|||
|
||||
public getOrCreate(key: K, def: V): V {
|
||||
if (this.has(key)) {
|
||||
return this.get(key);
|
||||
return this.get(key)!;
|
||||
}
|
||||
this.set(key, def);
|
||||
return def;
|
||||
}
|
||||
|
||||
public remove(key: K): V {
|
||||
public remove(key: K): V | undefined {
|
||||
const v = this.get(key);
|
||||
this.delete(key);
|
||||
return v;
|
||||
|
|
|
@ -44,10 +44,9 @@ export enum EffectiveMembership {
|
|||
Leave = "LEAVE",
|
||||
}
|
||||
|
||||
export interface MembershipSplit {
|
||||
// @ts-ignore - TS wants this to be a string key, but we know better.
|
||||
[state: EffectiveMembership]: Room[];
|
||||
}
|
||||
export type MembershipSplit = Partial<{
|
||||
[state in EffectiveMembership]: Room[];
|
||||
}>;
|
||||
|
||||
export function splitRoomsByMembership(rooms: Room[]): MembershipSplit {
|
||||
const split: MembershipSplit = {
|
||||
|
|
|
@ -36,7 +36,7 @@ export async function createLocalNotificationSettingsIfNeeded(cli: MatrixClient)
|
|||
if (cli.isGuest()) {
|
||||
return;
|
||||
}
|
||||
const eventType = getLocalNotificationAccountDataEventType(cli.deviceId);
|
||||
const eventType = getLocalNotificationAccountDataEventType(cli.deviceId!);
|
||||
const event = cli.getAccountData(eventType);
|
||||
// New sessions will create an account data event to signify they support
|
||||
// remote toggling of push notifications on this device. Default `is_silenced=true`
|
||||
|
@ -54,7 +54,7 @@ export async function createLocalNotificationSettingsIfNeeded(cli: MatrixClient)
|
|||
}
|
||||
|
||||
export function localNotificationsAreSilenced(cli: MatrixClient): boolean {
|
||||
const eventType = getLocalNotificationAccountDataEventType(cli.deviceId);
|
||||
const eventType = getLocalNotificationAccountDataEventType(cli.deviceId!);
|
||||
const event = cli.getAccountData(eventType);
|
||||
return event?.getContent<LocalNotificationSettings>()?.is_silenced ?? false;
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@ export function objectHasDiff<O extends {}>(a: O, b: O): boolean {
|
|||
const aKeys = Object.keys(a);
|
||||
const bKeys = Object.keys(b);
|
||||
if (aKeys.length !== bKeys.length) return true;
|
||||
const possibleChanges = arrayIntersection(aKeys, bKeys);
|
||||
const possibleChanges = arrayIntersection(aKeys, bKeys) as Array<keyof O>;
|
||||
// if the amalgamation of both sets of keys has the a different length to the inputs then there must be a change
|
||||
if (possibleChanges.length !== aKeys.length) return true;
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import { IConfigOptions } from "../IConfigOptions";
|
|||
import { getEmbeddedPagesWellKnown } from "../utils/WellKnownUtils";
|
||||
import { SnakedObject } from "./SnakedObject";
|
||||
|
||||
export function getHomePageUrl(appConfig: IConfigOptions): string | null {
|
||||
export function getHomePageUrl(appConfig: IConfigOptions): string | undefined {
|
||||
const config = new SnakedObject(appConfig);
|
||||
|
||||
const pagesConfig = config.get("embedded_pages");
|
||||
|
|
|
@ -208,7 +208,7 @@ export class RoomPermalinkCreator {
|
|||
}
|
||||
|
||||
private updateAllowedServers(): void {
|
||||
const bannedHostsRegexps = [];
|
||||
const bannedHostsRegexps: RegExp[] = [];
|
||||
let allowedHostsRegexps = [ANY_REGEX]; // default allow everyone
|
||||
if (this.room.currentState) {
|
||||
const aclEvent = this.room.currentState.getStateEvents(EventType.RoomServerAcl, "");
|
||||
|
@ -216,10 +216,10 @@ export class RoomPermalinkCreator {
|
|||
const getRegex = (hostname: string): RegExp =>
|
||||
new RegExp("^" + utils.globToRegexp(hostname, false) + "$");
|
||||
|
||||
const denied = aclEvent.getContent().deny || [];
|
||||
const denied = aclEvent.getContent<{ deny: string[] }>().deny || [];
|
||||
denied.forEach((h) => bannedHostsRegexps.push(getRegex(h)));
|
||||
|
||||
const allowed = aclEvent.getContent().allow || [];
|
||||
const allowed = aclEvent.getContent<{ allow: string[] }>().allow || [];
|
||||
allowedHostsRegexps = []; // we don't want to use the default rule here
|
||||
allowed.forEach((h) => allowedHostsRegexps.push(getRegex(h)));
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ export function pillifyLinks(nodes: ArrayLike<Element>, mxEvent: MatrixEvent, pi
|
|||
// we're adding now, since we've just inserted nodes into the structure
|
||||
// we're iterating over.
|
||||
// Note we've checked roomNotifTextNodes.length > 0 so we'll do this at least once
|
||||
node = roomNotifTextNode.nextSibling;
|
||||
node = roomNotifTextNode.nextSibling as Element;
|
||||
|
||||
const pillContainer = document.createElement("span");
|
||||
const pill = (
|
||||
|
|
|
@ -33,7 +33,7 @@ export async function retry<T, E extends Error>(
|
|||
num: number,
|
||||
predicate?: (e: E) => boolean,
|
||||
): Promise<T> {
|
||||
let lastErr: E;
|
||||
let lastErr!: E;
|
||||
for (let i = 0; i < num; i++) {
|
||||
try {
|
||||
const v = await fn();
|
||||
|
|
|
@ -25,7 +25,7 @@ import { isSupportedReceiptType } from "matrix-js-sdk/src/utils";
|
|||
* @returns True if the read receipt update includes the client, false otherwise.
|
||||
*/
|
||||
export function readReceiptChangeIsFor(event: MatrixEvent, client: MatrixClient): boolean {
|
||||
const myUserId = client.getUserId();
|
||||
const myUserId = client.getUserId()!;
|
||||
for (const eventId of Object.keys(event.getContent())) {
|
||||
for (const [receiptType, receipt] of Object.entries(event.getContent()[eventId])) {
|
||||
if (!isSupportedReceiptType(receiptType)) continue;
|
||||
|
@ -33,4 +33,5 @@ export function readReceiptChangeIsFor(event: MatrixEvent, client: MatrixClient)
|
|||
if (Object.keys(receipt || {}).includes(myUserId)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue