Merge remote-tracking branch 'upstream/develop' into fix/17130/draggable-pip

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner 2021-07-09 08:26:19 +02:00
commit 91d208d514
No known key found for this signature in database
GPG key ID: 9760693FDD98A790
802 changed files with 12931 additions and 11005 deletions

View file

@ -15,9 +15,9 @@ limitations under the License.
*/
import React, { ReactNode } from 'react';
import {AutoDiscovery} from "matrix-js-sdk/src/autodiscovery";
import {_t, _td, newTranslatableError} from "../languageHandler";
import {makeType} from "./TypeUtils";
import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery";
import { _t, _td, newTranslatableError } from "../languageHandler";
import { makeType } from "./TypeUtils";
import SdkConfig from '../SdkConfig';
const LIVELINESS_DISCOVERY_ERRORS: string[] = [

View file

@ -86,7 +86,7 @@ export default class DMRoomMap {
this.userToRooms = null;
this.roomToUser = null;
}
}
};
/**
* some client bug somewhere is causing some DMs to be marked
@ -103,7 +103,7 @@ export default class DMRoomMap {
if (room) {
const userId = room.guessDMUserId();
if (userId && userId !== myUserId) {
return {userId, roomId};
return { userId, roomId };
}
}
}).filter((ids) => !!ids); //filter out
@ -116,7 +116,7 @@ export default class DMRoomMap {
return !guessedUserIdsThatChanged
.some((ids) => ids.roomId === roomId);
});
guessedUserIdsThatChanged.forEach(({userId, roomId}) => {
guessedUserIdsThatChanged.forEach(({ userId, roomId }) => {
const roomIds = userToRooms[userId];
if (!roomIds) {
userToRooms[userId] = [roomId];
@ -181,7 +181,7 @@ export default class DMRoomMap {
public getUniqueRoomsWithIndividuals(): {[userId: string]: Room} {
if (!this.roomToUser) return {}; // No rooms means no map.
return Object.keys(this.roomToUser)
.map(r => ({userId: this.getUserIdForRoomId(r), room: this.matrixClient.getRoom(r)}))
.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, {});
}

View file

@ -16,7 +16,7 @@ limitations under the License.
// Pull in the encryption lib so that we can decrypt attachments.
import encrypt from 'browser-encrypt-attachment';
import {mediaFromContent} from "../customisations/Media";
import { mediaFromContent } from "../customisations/Media";
import { IEncryptedFile } from "../customisations/models/IMediaEventContent";
import { getBlobSafeMimeType } from "./blobs";
@ -29,7 +29,7 @@ import { getBlobSafeMimeType } from "./blobs";
* @returns {Promise<Blob>} Resolves to a Blob of the file.
*/
export function decryptFile(file: IEncryptedFile): Promise<Blob> {
const media = mediaFromContent({file});
const media = mediaFromContent({ file });
// Download the encrypted file as an array buffer.
return media.downloadSource().then((response) => {
return response.arrayBuffer();
@ -47,6 +47,6 @@ export function decryptFile(file: IEncryptedFile): Promise<Blob> {
let mimetype = file.mimetype ? file.mimetype.split(";")[0].trim() : '';
mimetype = getBlobSafeMimeType(mimetype);
return new Blob([dataArray], {type: mimetype});
return new Blob([dataArray], { type: mimetype });
});
}

View file

@ -1,49 +0,0 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* Used while editing, to pass the event, and to preserve editor state
* from one editor instance to another when remounting the editor
* upon receiving the remote echo for an unsent event.
*/
export default class EditorStateTransfer {
constructor(event) {
this._event = event;
this._serializedParts = null;
this.caret = null;
}
setEditorState(caret, serializedParts) {
this._caret = caret;
this._serializedParts = serializedParts;
}
hasEditorState() {
return !!this._serializedParts;
}
getSerializedParts() {
return this._serializedParts;
}
getCaret() {
return this._caret;
}
getEvent() {
return this._event;
}
}

View file

@ -0,0 +1,53 @@
/*
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { SerializedPart } from "../editor/parts";
import DocumentOffset from "../editor/offset";
/**
* Used while editing, to pass the event, and to preserve editor state
* from one editor instance to another when remounting the editor
* upon receiving the remote echo for an unsent event.
*/
export default class EditorStateTransfer {
private serializedParts: SerializedPart[] = null;
private caret: DocumentOffset = null;
constructor(private readonly event: MatrixEvent) {}
public setEditorState(caret: DocumentOffset, serializedParts: SerializedPart[]) {
this.caret = caret;
this.serializedParts = serializedParts;
}
public hasEditorState(): boolean {
return !!this.serializedParts;
}
public getSerializedParts(): SerializedPart[] {
return this.serializedParts;
}
public getCaret(): DocumentOffset {
return this.caret;
}
public getEvent(): MatrixEvent {
return this.event;
}
}

View file

@ -1,5 +1,5 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2018 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,7 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { _t, _td } from '../languageHandler';
import React, { ReactNode } from "react";
import { MatrixError } from "matrix-js-sdk/src/http-api";
import { _t, _td, Tags, TranslatedString } from '../languageHandler';
/**
* Produce a translated error message for a
@ -30,7 +33,12 @@ import { _t, _td } from '../languageHandler';
* for any tags in the strings apart from 'a'
* @returns {*} Translated string or react component
*/
export function messageForResourceLimitError(limitType, adminContact, strings, extraTranslations) {
export function messageForResourceLimitError(
limitType: string,
adminContact: string,
strings: Record<string, string>,
extraTranslations?: Tags,
): TranslatedString {
let errString = strings[limitType];
if (errString === undefined) errString = strings[''];
@ -49,7 +57,7 @@ export function messageForResourceLimitError(limitType, adminContact, strings, e
}
}
export function messageForSyncError(err) {
export function messageForSyncError(err: MatrixError | Error): ReactNode {
if (err.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') {
const limitError = messageForResourceLimitError(
err.data.limit_type,

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,9 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { EventStatus } from 'matrix-js-sdk/src/models/event';
import {MatrixClientPeg} from '../MatrixClientPeg';
import { Room } from 'matrix-js-sdk/src/models/room';
import { MatrixEvent, EventStatus } from 'matrix-js-sdk/src/models/event';
import { MatrixClientPeg } from '../MatrixClientPeg';
import shouldHideEvent from "../shouldHideEvent";
/**
* Returns whether an event should allow actions like reply, reactions, edit, etc.
* which effectively checks whether it's a regular message that has been sent and that we
@ -25,7 +28,7 @@ import shouldHideEvent from "../shouldHideEvent";
* @param {MatrixEvent} mxEvent The event to check
* @returns {boolean} true if actionable
*/
export function isContentActionable(mxEvent) {
export function isContentActionable(mxEvent: MatrixEvent): boolean {
const { status: eventStatus } = mxEvent;
// status is SENT before remote-echo, null after
@ -45,18 +48,18 @@ export function isContentActionable(mxEvent) {
return false;
}
export function canEditContent(mxEvent) {
export function canEditContent(mxEvent: MatrixEvent): boolean {
if (mxEvent.status === EventStatus.CANCELLED || mxEvent.getType() !== "m.room.message" || mxEvent.isRedacted()) {
return false;
}
const content = mxEvent.getOriginalContent();
const {msgtype} = content;
const { msgtype } = content;
return (msgtype === "m.text" || msgtype === "m.emote") &&
content.body && typeof content.body === 'string' &&
mxEvent.getSender() === MatrixClientPeg.get().getUserId();
}
export function canEditOwnEvent(mxEvent) {
export function canEditOwnEvent(mxEvent: MatrixEvent): boolean {
// for now we only allow editing
// your own events. So this just call through
// In the future though, moderators will be able to
@ -67,7 +70,7 @@ export function canEditOwnEvent(mxEvent) {
}
const MAX_JUMP_DISTANCE = 100;
export function findEditableEvent(room, isForward, fromEventId = undefined) {
export function findEditableEvent(room: Room, isForward: boolean, fromEventId: string = undefined): MatrixEvent {
const liveTimeline = room.getLiveTimeline();
const events = liveTimeline.getEvents().concat(room.getPendingEvents());
const maxIdx = events.length - 1;

View file

@ -47,7 +47,7 @@ function safariVersionCheck(ua) {
async function isColrFontSupported() {
console.log("Checking for COLR support");
const {userAgent} = navigator;
const { userAgent } = navigator;
// Firefox has supported COLR fonts since version 26
// but doesn't support the check below without
// "Extract canvas data" permissions

View file

@ -18,7 +18,7 @@ import url from 'url';
import qs from 'qs';
import SdkConfig from '../SdkConfig';
import {MatrixClientPeg} from '../MatrixClientPeg';
import { MatrixClientPeg } from '../MatrixClientPeg';
export function getHostingLink(campaign) {
const hostingLink = SdkConfig.get().hosting_signup_link;

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,14 +15,15 @@ limitations under the License.
*/
import { SERVICE_TYPES } from 'matrix-js-sdk/src/service-types';
import SdkConfig from '../SdkConfig';
import {MatrixClientPeg} from '../MatrixClientPeg';
export function getDefaultIdentityServerUrl() {
import SdkConfig from '../SdkConfig';
import { MatrixClientPeg } from '../MatrixClientPeg';
export function getDefaultIdentityServerUrl(): string {
return SdkConfig.get()['validated_server_config']['isUrl'];
}
export function useDefaultIdentityServer() {
export function useDefaultIdentityServer(): void {
const url = getDefaultIdentityServerUrl();
// Account data change will update localstorage, client, etc through dispatcher
MatrixClientPeg.get().setAccountData("m.identity_server", {
@ -30,7 +31,7 @@ export function useDefaultIdentityServer() {
});
}
export async function doesIdentityServerHaveTerms(fullUrl) {
export async function doesIdentityServerHaveTerms(fullUrl: string): Promise<boolean> {
let terms;
try {
terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IS, fullUrl);
@ -46,7 +47,7 @@ export async function doesIdentityServerHaveTerms(fullUrl) {
return terms && terms["policies"] && (Object.keys(terms["policies"]).length > 0);
}
export function doesAccountDataHaveIdentityServer() {
export function doesAccountDataHaveIdentityServer(): boolean {
const event = MatrixClientPeg.get().getAccountData("m.identity_server");
return event && event.getContent() && event.getContent()['base_url'];
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {MatrixClientPeg} from '../MatrixClientPeg';
import { MatrixClientPeg } from '../MatrixClientPeg';
import { _t } from '../languageHandler';
export function getNameForEventRoom(userId, roomId) {
@ -27,7 +27,7 @@ export function getNameForEventRoom(userId, roomId) {
export function userLabelForEventRoom(userId, roomId) {
const name = getNameForEventRoom(userId, roomId);
if (name !== userId) {
return _t("%(name)s (%(userId)s)", {name, userId});
return _t("%(name)s (%(userId)s)", { name, userId });
} else {
return userId;
}

View file

@ -26,9 +26,11 @@ export class MarkedExecution {
/**
* Creates a MarkedExecution for the provided function.
* @param fn The function to be called upon trigger if marked.
* @param {Function} fn The function to be called upon trigger if marked.
* @param {Function} onMarkCallback A function that is called when a new mark is made. Not
* called if a mark is already flagged.
*/
constructor(private fn: () => void) {
constructor(private fn: () => void, private onMarkCallback?: () => void) {
}
/**
@ -42,6 +44,7 @@ export class MarkedExecution {
* Marks the function to be called upon trigger().
*/
public mark() {
if (!this.marked) this.onMarkCallback?.();
this.marked = true;
}

View file

@ -81,7 +81,7 @@ export async function decryptMegolmKeyFile(data, password) {
let isValid;
try {
isValid = await subtleCrypto.verify(
{name: 'HMAC'},
{ name: 'HMAC' },
hmacKey,
hmac,
toVerify,
@ -112,7 +112,6 @@ export async function decryptMegolmKeyFile(data, password) {
return new TextDecoder().decode(new Uint8Array(plaintext));
}
/**
* Encrypt a megolm key file
*
@ -174,7 +173,7 @@ export async function encryptMegolmKeyFile(data, password, options) {
let hmac;
try {
hmac = await subtleCrypto.sign(
{name: 'HMAC'},
{ name: 'HMAC' },
hmacKey,
toSign,
);
@ -182,7 +181,6 @@ export async function encryptMegolmKeyFile(data, password, options) {
throw friendlyError('subtleCrypto.sign failed: ' + e, cryptoFailMsg());
}
const hmacArray = new Uint8Array(hmac);
resultBuffer.set(hmacArray, idx);
return packMegolmKeyFile(resultBuffer);
@ -204,7 +202,7 @@ async function deriveKeys(salt, iterations, password) {
key = await subtleCrypto.importKey(
'raw',
new TextEncoder().encode(password),
{name: 'PBKDF2'},
{ name: 'PBKDF2' },
false,
['deriveBits'],
);
@ -237,7 +235,7 @@ async function deriveKeys(salt, iterations, password) {
const aesProm = subtleCrypto.importKey(
'raw',
aesKey,
{name: 'AES-CTR'},
{ name: 'AES-CTR' },
false,
['encrypt', 'decrypt'],
).catch((e) => {
@ -249,7 +247,7 @@ async function deriveKeys(salt, iterations, password) {
hmacKey,
{
name: 'HMAC',
hash: {name: 'SHA-256'},
hash: { name: 'SHA-256' },
},
false,
['sign', 'verify'],

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,31 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React, { ReactNode } from 'react';
import classNames from 'classnames';
import DiffMatchPatch from 'diff-match-patch';
import {DiffDOM} from "diff-dom";
import { checkBlockNode, bodyToHtml } from "../HtmlUtils";
import { diff_match_patch as DiffMatchPatch } from 'diff-match-patch';
import { DiffDOM, IDiff } from "diff-dom";
import { IContent } from "matrix-js-sdk/src/models/event";
import { bodyToHtml, checkBlockNode, IOptsReturnString } from "../HtmlUtils";
const decodeEntities = (function() {
let textarea = null;
return function(string) {
return function(str: string): string {
if (!textarea) {
textarea = document.createElement("textarea");
}
textarea.innerHTML = string;
textarea.innerHTML = str;
return textarea.value;
};
})();
function textToHtml(text) {
function textToHtml(text: string): string {
const container = document.createElement("div");
container.textContent = text;
return container.innerHTML;
}
function getSanitizedHtmlBody(content) {
const opts = {
function getSanitizedHtmlBody(content: IContent): string {
const opts: IOptsReturnString = {
stripReplyFallback: true,
returnString: true,
};
@ -57,21 +59,21 @@ function getSanitizedHtmlBody(content) {
}
}
function wrapInsertion(child) {
function wrapInsertion(child: Node): HTMLElement {
const wrapper = document.createElement(checkBlockNode(child) ? "div" : "span");
wrapper.className = "mx_EditHistoryMessage_insertion";
wrapper.appendChild(child);
return wrapper;
}
function wrapDeletion(child) {
function wrapDeletion(child: Node): HTMLElement {
const wrapper = document.createElement(checkBlockNode(child) ? "div" : "span");
wrapper.className = "mx_EditHistoryMessage_deletion";
wrapper.appendChild(child);
return wrapper;
}
function findRefNodes(root, route, isAddition) {
function findRefNodes(root: Node, route: number[], isAddition = false) {
let refNode = root;
let refParentNode;
const end = isAddition ? route.length - 1 : route.length;
@ -79,7 +81,7 @@ function findRefNodes(root, route, isAddition) {
refParentNode = refNode;
refNode = refNode.childNodes[route[i]];
}
return {refNode, refParentNode};
return { refNode, refParentNode };
}
function diffTreeToDOM(desc) {
@ -101,7 +103,7 @@ function diffTreeToDOM(desc) {
}
}
function insertBefore(parent, nextSibling, child) {
function insertBefore(parent: Node, nextSibling: Node | null, child: Node): void {
if (nextSibling) {
parent.insertBefore(child, nextSibling);
} else {
@ -109,7 +111,7 @@ function insertBefore(parent, nextSibling, child) {
}
}
function isRouteOfNextSibling(route1, route2) {
function isRouteOfNextSibling(route1: number[], route2: number[]): boolean {
// routes are arrays with indices,
// to be interpreted as a path in the dom tree
@ -127,7 +129,7 @@ function isRouteOfNextSibling(route1, route2) {
return route2[lastD1Idx] >= route1[lastD1Idx];
}
function adjustRoutes(diff, remainingDiffs) {
function adjustRoutes(diff: IDiff, remainingDiffs: IDiff[]): void {
if (diff.action === "removeTextElement" || diff.action === "removeElement") {
// as removed text is not removed from the html, but marked as deleted,
// we need to readjust indices that assume the current node has been removed.
@ -140,12 +142,12 @@ function adjustRoutes(diff, remainingDiffs) {
}
}
function stringAsTextNode(string) {
function stringAsTextNode(string: string): Text {
return document.createTextNode(decodeEntities(string));
}
function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) {
const {refNode, refParentNode} = findRefNodes(originalRootNode, diff.route);
function renderDifferenceInDOM(originalRootNode: Node, diff: IDiff, diffMathPatch: DiffMatchPatch): void {
const { refNode, refParentNode } = findRefNodes(originalRootNode, diff.route);
switch (diff.action) {
case "replaceElement": {
const container = document.createElement("span");
@ -171,7 +173,7 @@ function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) {
diffMathPatch.diff_cleanupSemantic(textDiffs);
const container = document.createElement("span");
for (const [modifier, text] of textDiffs) {
let textDiffNode = stringAsTextNode(text);
let textDiffNode: Node = stringAsTextNode(text);
if (modifier < 0) {
textDiffNode = wrapDeletion(textDiffNode);
} else if (modifier > 0) {
@ -201,7 +203,7 @@ function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) {
case "addAttribute":
case "modifyAttribute": {
const delNode = wrapDeletion(refNode.cloneNode(true));
const updatedNode = refNode.cloneNode(true);
const updatedNode = refNode.cloneNode(true) as HTMLElement;
if (diff.action === "addAttribute" || diff.action === "modifyAttribute") {
updatedNode.setAttribute(diff.name, diff.newValue);
} else {
@ -220,12 +222,12 @@ function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) {
}
}
function routeIsEqual(r1, r2) {
function routeIsEqual(r1: number[], r2: number[]): boolean {
return r1.length === r2.length && !r1.some((e, i) => e !== r2[i]);
}
// workaround for https://github.com/fiduswriter/diffDOM/issues/90
function filterCancelingOutDiffs(originalDiffActions) {
function filterCancelingOutDiffs(originalDiffActions: IDiff[]): IDiff[] {
const diffActions = originalDiffActions.slice();
for (let i = 0; i < diffActions.length; ++i) {
@ -252,7 +254,7 @@ function filterCancelingOutDiffs(originalDiffActions) {
* @param {object} editContent the content for the edit message
* @return {object} a react element similar to what `bodyToHtml` returns
*/
export function editBodyDiffToHtml(originalContent, editContent) {
export function editBodyDiffToHtml(originalContent: IContent, editContent: IContent): ReactNode {
// wrap the body in a div, DiffDOM needs a root element
const originalBody = `<div>${getSanitizedHtmlBody(originalContent)}</div>`;
const editBody = `<div>${getSanitizedHtmlBody(editContent)}</div>`;

View file

@ -1,6 +1,5 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017, 2018 New Vector Ltd
Copyright 2016 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,23 +14,51 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {MatrixClientPeg} from '../MatrixClientPeg';
import {getAddressType} from '../UserAddress';
import { MatrixError } from "matrix-js-sdk/src/http-api";
import { defer, IDeferred } from "matrix-js-sdk/src/utils";
import { MatrixClientPeg } from '../MatrixClientPeg';
import { AddressType, getAddressType } from '../UserAddress';
import GroupStore from '../stores/GroupStore';
import {_t} from "../languageHandler";
import * as sdk from "../index";
import { _t } from "../languageHandler";
import Modal from "../Modal";
import SettingsStore from "../settings/SettingsStore";
import {defer} from "./promise";
import AskInviteAnywayDialog from "../components/views/dialogs/AskInviteAnywayDialog";
export enum InviteState {
Invited = "invited",
Error = "error",
}
interface IError {
errorText: string;
errcode: string;
}
const UNKNOWN_PROFILE_ERRORS = ['M_NOT_FOUND', 'M_USER_NOT_FOUND', 'M_PROFILE_UNDISCLOSED', 'M_PROFILE_NOT_FOUND'];
export type CompletionStates = Record<string, InviteState>;
/**
* Invites multiple addresses to a room or group, handling rate limiting from the server
*/
export default class MultiInviter {
private readonly roomId?: string;
private readonly groupId?: string;
private canceled = false;
private addresses: string[] = [];
private busy = false;
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;
/**
* @param {string} targetId The ID of the room or group to invite to
*/
constructor(targetId) {
constructor(targetId: string) {
if (targetId[0] === '+') {
this.roomId = null;
this.groupId = targetId;
@ -39,41 +66,38 @@ export default class MultiInviter {
this.roomId = targetId;
this.groupId = null;
}
}
this.canceled = false;
this.addrs = [];
this.busy = false;
this.completionStates = {}; // State of each address (invited or error)
this.errors = {}; // { address: {errorText, errcode} }
this.deferred = null;
public get fatal() {
return this._fatal;
}
/**
* Invite users to this room. This may only be called once per
* instance of the class.
*
* @param {array} addrs Array of addresses to invite
* @param {array} addresses Array of addresses to invite
* @param {string} reason Reason for inviting (optional)
* @returns {Promise} Resolved when all invitations in the queue are complete
*/
invite(addrs, reason) {
if (this.addrs.length > 0) {
public invite(addresses, reason?: string): Promise<CompletionStates> {
if (this.addresses.length > 0) {
throw new Error("Already inviting/invited");
}
this.addrs.push(...addrs);
this.addresses.push(...addresses);
this.reason = reason;
for (const addr of this.addrs) {
for (const addr of this.addresses) {
if (getAddressType(addr) === null) {
this.completionStates[addr] = 'error';
this.completionStates[addr] = InviteState.Error;
this.errors[addr] = {
errcode: 'M_INVALID',
errorText: _t('Unrecognised address'),
};
}
}
this.deferred = defer();
this._inviteMore(0);
this.deferred = defer<CompletionStates>();
this.inviteMore(0);
return this.deferred.promise;
}
@ -81,33 +105,36 @@ export default class MultiInviter {
/**
* Stops inviting. Causes promises returned by invite() to be rejected.
*/
cancel() {
public cancel(): void {
if (!this.busy) return;
this._canceled = true;
this.canceled = true;
this.deferred.reject(new Error('canceled'));
}
getCompletionState(addr) {
public getCompletionState(addr: string): InviteState {
return this.completionStates[addr];
}
getErrorText(addr) {
public getErrorText(addr: string): string {
return this.errors[addr] ? this.errors[addr].errorText : null;
}
async _inviteToRoom(roomId, addr, ignoreProfile) {
private async inviteToRoom(roomId: string, addr: string, ignoreProfile = false): Promise<{}> {
const addrType = getAddressType(addr);
if (addrType === 'email') {
if (addrType === AddressType.Email) {
return MatrixClientPeg.get().inviteByEmail(roomId, addr);
} else if (addrType === 'mx-user-id') {
} else if (addrType === AddressType.MatrixUserId) {
const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) throw new Error("Room not found");
const member = room.getMember(addr);
if (member && ['join', 'invite'].includes(member.membership)) {
throw {errcode: "RIOT.ALREADY_IN_ROOM", error: "Member already invited"};
throw new new MatrixError({
errcode: "RIOT.ALREADY_IN_ROOM",
error: "Member already invited",
});
}
if (!ignoreProfile && SettingsStore.getValue("promptBeforeInviteUnknownUsers", this.roomId)) {
@ -124,28 +151,28 @@ export default class MultiInviter {
}
}
_doInvite(address, ignoreProfile) {
return new Promise((resolve, reject) => {
private doInvite(address: string, ignoreProfile = false): Promise<void> {
return new Promise<void>((resolve, reject) => {
console.log(`Inviting ${address}`);
let doInvite;
if (this.groupId !== null) {
doInvite = GroupStore.inviteUserToGroup(this.groupId, address);
} else {
doInvite = this._inviteToRoom(this.roomId, address, ignoreProfile);
doInvite = this.inviteToRoom(this.roomId, address, ignoreProfile);
}
doInvite.then(() => {
if (this._canceled) {
if (this.canceled) {
return;
}
this.completionStates[address] = 'invited';
this.completionStates[address] = InviteState.Invited;
delete this.errors[address];
resolve();
}).catch((err) => {
if (this._canceled) {
if (this.canceled) {
return;
}
@ -157,21 +184,21 @@ export default class MultiInviter {
fatal = true;
errorText = _t('You do not have permission to invite people to this room.');
} else if (err.errcode === "RIOT.ALREADY_IN_ROOM") {
errorText = _t("User %(userId)s is already in the room", {userId: address});
errorText = _t("User %(userId)s is already in the room", { userId: address });
} else if (err.errcode === 'M_LIMIT_EXCEEDED') {
// we're being throttled so wait a bit & try again
setTimeout(() => {
this._doInvite(address, ignoreProfile).then(resolve, reject);
this.doInvite(address, ignoreProfile).then(resolve, reject);
}, 5000);
return;
} else if (['M_NOT_FOUND', 'M_USER_NOT_FOUND'].includes(err.errcode)) {
errorText = _t("User %(user_id)s does not exist", {user_id: address});
errorText = _t("User %(user_id)s does not exist", { user_id: address });
} else if (err.errcode === 'M_PROFILE_UNDISCLOSED') {
errorText = _t("User %(user_id)s may or may not exist", {user_id: address});
errorText = _t("User %(user_id)s may or may not exist", { user_id: address });
} else if (err.errcode === 'M_PROFILE_NOT_FOUND' && !ignoreProfile) {
// Invite without the profile check
console.warn(`User ${address} does not have a profile - inviting anyways automatically`);
this._doInvite(address, true).then(resolve, reject);
this.doInvite(address, true).then(resolve, reject);
} else if (err.errcode === "M_BAD_STATE") {
errorText = _t("The user must be unbanned before they can be invited.");
} else if (err.errcode === "M_UNSUPPORTED_ROOM_VERSION") {
@ -180,14 +207,14 @@ export default class MultiInviter {
errorText = _t('Unknown server error');
}
this.completionStates[address] = 'error';
this.errors[address] = {errorText, errcode: err.errcode};
this.completionStates[address] = InviteState.Error;
this.errors[address] = { errorText, errcode: err.errcode };
this.busy = !fatal;
this.fatal = fatal;
this._fatal = fatal;
if (fatal) {
reject();
reject(err);
} else {
resolve();
}
@ -195,22 +222,22 @@ export default class MultiInviter {
});
}
_inviteMore(nextIndex, ignoreProfile) {
if (this._canceled) {
private inviteMore(nextIndex: number, ignoreProfile = false): void {
if (this.canceled) {
return;
}
if (nextIndex === this.addrs.length) {
if (nextIndex === this.addresses.length) {
this.busy = false;
if (Object.keys(this.errors).length > 0 && !this.groupId) {
// There were problems inviting some people - see if we can invite them
// without caring if they exist or not.
const unknownProfileErrors = ['M_NOT_FOUND', 'M_USER_NOT_FOUND', 'M_PROFILE_UNDISCLOSED', 'M_PROFILE_NOT_FOUND'];
const unknownProfileUsers = Object.keys(this.errors).filter(a => unknownProfileErrors.includes(this.errors[a].errcode));
const unknownProfileUsers = Object.keys(this.errors)
.filter(a => UNKNOWN_PROFILE_ERRORS.includes(this.errors[a].errcode));
if (unknownProfileUsers.length > 0) {
const inviteUnknowns = () => {
const promises = unknownProfileUsers.map(u => this._doInvite(u, true));
const promises = unknownProfileUsers.map(u => this.doInvite(u, true));
Promise.all(promises).then(() => this.deferred.resolve(this.completionStates));
};
@ -219,15 +246,17 @@ export default class MultiInviter {
return;
}
const AskInviteAnywayDialog = sdk.getComponent("dialogs.AskInviteAnywayDialog");
console.log("Showing failed to invite dialog...");
Modal.createTrackedDialog('Failed to invite', '', AskInviteAnywayDialog, {
unknownProfileUsers: unknownProfileUsers.map(u => {return {userId: u, errorText: this.errors[u].errorText};}),
unknownProfileUsers: unknownProfileUsers.map(u => ({
userId: u,
errorText: this.errors[u].errorText,
})),
onInviteAnyways: () => inviteUnknowns(),
onGiveUp: () => {
// Fake all the completion states because we already warned the user
for (const addr of unknownProfileUsers) {
this.completionStates[addr] = 'invited';
this.completionStates[addr] = InviteState.Invited;
}
this.deferred.resolve(this.completionStates);
},
@ -239,25 +268,25 @@ export default class MultiInviter {
return;
}
const addr = this.addrs[nextIndex];
const addr = this.addresses[nextIndex];
// don't try to invite it if it's an invalid address
// (it will already be marked as an error though,
// so no need to do so again)
if (getAddressType(addr) === null) {
this._inviteMore(nextIndex + 1);
this.inviteMore(nextIndex + 1);
return;
}
// don't re-invite (there's no way in the UI to do this, but
// for sanity's sake)
if (this.completionStates[addr] === 'invited') {
this._inviteMore(nextIndex + 1);
if (this.completionStates[addr] === InviteState.Invited) {
this.inviteMore(nextIndex + 1);
return;
}
this._doInvite(addr, ignoreProfile).then(() => {
this._inviteMore(nextIndex + 1, ignoreProfile);
this.doInvite(addr, ignoreProfile).then(() => {
this.inviteMore(nextIndex + 1, ignoreProfile);
}).catch(() => this.deferred.resolve(this.completionStates));
}
}

View file

@ -16,7 +16,7 @@ limitations under the License.
import zxcvbn from 'zxcvbn';
import {MatrixClientPeg} from '../MatrixClientPeg';
import { MatrixClientPeg } from '../MatrixClientPeg';
import { _t, _td } from '../languageHandler';
const ZXCVBN_USER_INPUTS = [

View file

@ -14,13 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
export default class PinningUtils {
/**
* Determines if the given event may be pinned.
* @param {MatrixEvent} event The event to check.
* @return {boolean} True if the event may be pinned, false otherwise.
*/
static isPinnable(event) {
static isPinnable(event: MatrixEvent): boolean {
if (!event) return false;
if (event.getType() !== "m.room.message") return false;
if (event.isRedacted()) return false;

View file

@ -1,5 +1,5 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2016 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
/**
* Given MatrixEvent containing receipts, return the first
* read receipt from the given user ID, or null if no such
@ -23,7 +25,7 @@ limitations under the License.
* @param {string} userId A user ID
* @returns {Object} Read receipt
*/
export function findReadReceiptFromUserId(receiptEvent, userId) {
export function findReadReceiptFromUserId(receiptEvent: MatrixEvent, userId: string): object | null {
const receiptKeys = Object.keys(receiptEvent.getContent());
for (let i = 0; i < receiptKeys.length; ++i) {
const rcpt = receiptEvent.getContent()[receiptKeys[i]];

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -22,59 +22,58 @@ limitations under the License.
* Fires when the middle panel has been resized by a pixel.
* @event module:utils~ResizeNotifier#"middlePanelResizedNoisy"
*/
import { EventEmitter } from "events";
import { throttle } from "lodash";
export default class ResizeNotifier extends EventEmitter {
constructor() {
super();
// with default options, will call fn once at first call, and then every x ms
// if there was another call in that timespan
this._throttledMiddlePanel = throttle(() => this.emit("middlePanelResized"), 200);
this._isResizing = false;
}
private _isResizing = false;
get isResizing() {
// with default options, will call fn once at first call, and then every x ms
// if there was another call in that timespan
private throttledMiddlePanel = throttle(() => this.emit("middlePanelResized"), 200);
public get isResizing() {
return this._isResizing;
}
startResizing() {
public startResizing() {
this._isResizing = true;
this.emit("isResizing", true);
}
stopResizing() {
public stopResizing() {
this._isResizing = false;
this.emit("isResizing", false);
}
_noisyMiddlePanel() {
private noisyMiddlePanel() {
this.emit("middlePanelResizedNoisy");
}
_updateMiddlePanel() {
this._throttledMiddlePanel();
this._noisyMiddlePanel();
private updateMiddlePanel() {
this.throttledMiddlePanel();
this.noisyMiddlePanel();
}
// can be called in quick succession
notifyLeftHandleResized() {
public notifyLeftHandleResized() {
// don't emit event for own region
this._updateMiddlePanel();
this.updateMiddlePanel();
}
// can be called in quick succession
notifyRightHandleResized() {
this._updateMiddlePanel();
public notifyRightHandleResized() {
this.updateMiddlePanel();
}
notifyTimelineHeightChanged() {
this._updateMiddlePanel();
public notifyTimelineHeightChanged() {
this.updateMiddlePanel();
}
// can be called in quick succession
notifyWindowResized() {
this._updateMiddlePanel();
public notifyWindowResized() {
this.updateMiddlePanel();
}
}

View file

@ -1,31 +1,32 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room";
import DMRoomMap from './DMRoomMap';
/* For now, a cut-down type spec for the client */
interface Client {
getUserId: () => string;
checkUserTrust: (userId: string) => {
isCrossSigningVerified: () => boolean
wasCrossSigningVerified: () => boolean
};
getStoredDevicesForUser: (userId: string) => [{ deviceId: string }];
checkDeviceTrust: (userId: string, deviceId: string) => {
isVerified: () => boolean
};
}
interface Room {
getEncryptionTargetMembers: () => Promise<[{userId: string}]>;
roomId: string;
}
export enum E2EStatus {
Warning = "warning",
Verified = "verified",
Normal = "normal"
}
export async function shieldStatusForRoom(client: Client, room: Room): Promise<E2EStatus> {
const members = (await room.getEncryptionTargetMembers()).map(({userId}) => userId);
export async function shieldStatusForRoom(client: MatrixClient, room: Room): Promise<E2EStatus> {
const members = (await room.getEncryptionTargetMembers()).map(({ userId }) => userId);
const inDMMap = !!DMRoomMap.shared().getUserIdForRoomId(room.roomId);
const verified: string[] = [];
@ -52,7 +53,7 @@ export async function shieldStatusForRoom(client: Client, room: Room): Promise<E
const targets = includeUser ? [...verified, client.getUserId()] : verified;
for (const userId of targets) {
const devices = client.getStoredDevicesForUser(userId);
const anyDeviceNotVerified = devices.some(({deviceId}) => {
const anyDeviceNotVerified = devices.some(({ deviceId }) => {
return !client.checkDeviceTrust(userId, deviceId).isVerified();
});
if (anyDeviceNotVerified) {

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {EnhancedMap} from "./maps";
import { EnhancedMap } from "./maps";
// Inspired by https://pkg.go.dev/golang.org/x/sync/singleflight

View file

@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {LocalStorageCryptoStore} from 'matrix-js-sdk/src/crypto/store/localStorage-crypto-store';
import { LocalStorageCryptoStore } from 'matrix-js-sdk/src/crypto/store/localStorage-crypto-store';
import Analytics from '../Analytics';
import {IndexedDBStore} from "matrix-js-sdk/src/store/indexeddb";
import {IndexedDBCryptoStore} from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store";
import { IndexedDBStore } from "matrix-js-sdk/src/store/indexeddb";
import { IndexedDBCryptoStore } from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store";
const localStorage = window.localStorage;

View file

@ -57,7 +57,7 @@ export default class Timer {
const delta = this.timeout - elapsed;
this.timerHandle = setTimeout(this.onTimeout, delta);
}
}
};
changeTimeout(timeout: number) {
if (timeout === this.timeout) {

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {MatrixClientPeg} from '../MatrixClientPeg';
import { MatrixClientPeg } from '../MatrixClientPeg';
const CALL_BEHAVIOUR_WK_KEY = "io.element.call_behaviour";
const E2EE_WK_KEY = "io.element.e2ee";
@ -43,7 +43,7 @@ export function getE2EEWellKnown(): IE2EEWellKnown {
return clientWellKnown[E2EE_WK_KEY];
}
if (clientWellKnown && clientWellKnown[E2EE_WK_KEY_DEPRECATED]) {
return clientWellKnown[E2EE_WK_KEY_DEPRECATED]
return clientWellKnown[E2EE_WK_KEY_DEPRECATED];
}
return null;
}

View file

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { IDestroyable } from "./IDestroyable";
import { arrayFastClone } from "./arrays";
@ -36,7 +35,7 @@ export abstract class Whenable<T> implements IDestroyable {
* @returns This.
*/
public when(condition: T, fn: WhenFn<T>): Whenable<T> {
this.listeners.push({condition, fn});
this.listeners.push({ condition, fn });
return this;
}
@ -59,7 +58,7 @@ export abstract class Whenable<T> implements IDestroyable {
* @returns This.
*/
public whenAnything(fn: WhenFn<T>): Whenable<T> {
this.listeners.push({condition: null, fn});
this.listeners.push({ condition: null, fn });
return this;
}

View file

@ -16,19 +16,20 @@ limitations under the License.
*/
import * as url from "url";
import { Capability, IWidget, IWidgetData, MatrixCapabilities } from "matrix-widget-api";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import {MatrixClientPeg} from '../MatrixClientPeg';
import { MatrixClientPeg } from '../MatrixClientPeg';
import SdkConfig from "../SdkConfig";
import dis from '../dispatcher/dispatcher';
import WidgetEchoStore from '../stores/WidgetEchoStore';
import SettingsStore from "../settings/SettingsStore";
import {IntegrationManagers} from "../integrations/IntegrationManagers";
import {Room} from "matrix-js-sdk/src/models/room";
import {WidgetType} from "../widgets/WidgetType";
import {objectClone} from "./objects";
import {_t} from "../languageHandler";
import {Capability, IWidget, IWidgetData, MatrixCapabilities} from "matrix-widget-api";
import {IApp} from "../stores/WidgetStore";
import { IntegrationManagers } from "../integrations/IntegrationManagers";
import { WidgetType } from "../widgets/WidgetType";
import { objectClone } from "./objects";
import { _t } from "../languageHandler";
import { IApp } from "../stores/WidgetStore";
// How long we wait for the state event echo to come back from the server
// before waitFor[Room/User]Widget rejects its promise
@ -377,9 +378,9 @@ export default class WidgetUtils {
return widgets.filter(w => w.content && w.content.type === "m.integration_manager");
}
static getRoomWidgetsOfType(room: Room, type: WidgetType): IWidgetEvent[] {
const widgets = WidgetUtils.getRoomWidgets(room);
return (widgets || []).filter(w => {
static getRoomWidgetsOfType(room: Room, type: WidgetType): MatrixEvent[] {
const widgets = WidgetUtils.getRoomWidgets(room) || [];
return widgets.filter(w => {
const content = w.getContent();
return content.url && type.matches(content.type);
});
@ -407,7 +408,7 @@ export default class WidgetUtils {
WidgetType.INTEGRATION_MANAGER,
uiUrl,
"Integration Manager: " + name,
{"api_url": apiUrl},
{ "api_url": apiUrl },
);
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {percentageOf, percentageWithin} from "./numbers";
import { percentageOf, percentageWithin } from "./numbers";
/**
* Quickly resample an array to have less/more data points. If an input which is larger
@ -223,6 +223,21 @@ export function arrayMerge<T>(...a: T[][]): T[] {
}, new Set<T>()));
}
/**
* Moves a single element from fromIndex to toIndex.
* @param {array} list the list from which to construct the new list.
* @param {number} fromIndex the index of the element to move.
* @param {number} toIndex the index of where to put the element.
* @returns {array} A new array with the requested value moved.
*/
export function moveElement<T>(list: T[], fromIndex: number, toIndex: number): T[] {
const result = Array.from(list);
const [removed] = result.splice(fromIndex, 1);
result.splice(toIndex, 0, removed);
return result;
}
/**
* Helper functions to perform LINQ-like queries on arrays.
*/

View file

@ -1,5 +1,5 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {createClient} from "matrix-js-sdk/src/matrix";
import {IndexedDBCryptoStore} from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store";
import {WebStorageSessionStore} from "matrix-js-sdk/src/store/session/webstorage";
import {IndexedDBStore} from "matrix-js-sdk/src/store/indexeddb";
import { createClient, ICreateClientOpts } from "matrix-js-sdk/src/matrix";
import { IndexedDBCryptoStore } from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store";
import { WebStorageSessionStore } from "matrix-js-sdk/src/store/session/webstorage";
import { IndexedDBStore } from "matrix-js-sdk/src/store/indexeddb";
const localStorage = window.localStorage;
@ -41,8 +41,8 @@ try {
*
* @returns {MatrixClient} the newly-created MatrixClient
*/
export default function createMatrixClient(opts) {
const storeOpts = {
export default function createMatrixClient(opts: ICreateClientOpts) {
const storeOpts: Partial<ICreateClientOpts> = {
useAuthorizationHeader: true,
};
@ -65,9 +65,10 @@ export default function createMatrixClient(opts) {
);
}
opts = Object.assign(storeOpts, opts);
return createClient(opts);
return createClient({
...storeOpts,
...opts,
});
}
createMatrixClient.indexedDbWorkerScript = null;

View file

@ -39,19 +39,19 @@ export function humanizeTime(timeMillis: number): string {
if (msAgo >= 0) { // Past
if (msAgo <= MILLISECONDS_RECENT) return _t("a few seconds ago");
if (msAgo <= MILLISECONDS_1_MIN) return _t("about a minute ago");
if (minutes <= MINUTES_UNDER_1_HOUR) return _t("%(num)s minutes ago", {num: minutes});
if (minutes <= MINUTES_UNDER_1_HOUR) return _t("%(num)s minutes ago", { num: minutes });
if (minutes <= MINUTES_1_HOUR) return _t("about an hour ago");
if (hours <= HOURS_UNDER_1_DAY) return _t("%(num)s hours ago", {num: hours});
if (hours <= HOURS_UNDER_1_DAY) return _t("%(num)s hours ago", { num: hours });
if (hours <= HOURS_1_DAY) return _t("about a day ago");
return _t("%(num)s days ago", {num: days});
return _t("%(num)s days ago", { num: days });
} else { // Future
msAgo = Math.abs(msAgo);
if (msAgo <= MILLISECONDS_RECENT) return _t("a few seconds from now");
if (msAgo <= MILLISECONDS_1_MIN) return _t("about a minute from now");
if (minutes <= MINUTES_UNDER_1_HOUR) return _t("%(num)s minutes from now", {num: minutes});
if (minutes <= MINUTES_UNDER_1_HOUR) return _t("%(num)s minutes from now", { num: minutes });
if (minutes <= MINUTES_1_HOUR) return _t("about an hour from now");
if (hours <= HOURS_UNDER_1_DAY) return _t("%(num)s hours from now", {num: hours});
if (hours <= HOURS_UNDER_1_DAY) return _t("%(num)s hours from now", { num: hours });
if (hours <= HOURS_1_DAY) return _t("about a day from now");
return _t("%(num)s days from now", {num: days});
return _t("%(num)s days from now", { num: days });
}
}

View file

@ -30,7 +30,7 @@ export function mapDiff<K, V>(a: Map<K, V>, b: Map<K, V>): { changed: K[], added
const possibleChanges = arrayUnion(aKeys, bKeys);
const changes = possibleChanges.filter(k => a.get(k) !== b.get(k));
return {changed: changes, added: keyDiff.added, removed: keyDiff.removed};
return { changed: changes, added: keyDiff.added, removed: keyDiff.removed };
}
/**

View file

@ -102,7 +102,7 @@ export async function leaveRoomBehaviour(roomId: string) {
} catch (e) {
if (e && e.data && e.data.errcode) {
const message = e.data.error || _t("Unexpected server error trying to leave the room");
results[roomId] = Object.assign(new Error(message), {errcode: e.data.errcode});
results[roomId] = Object.assign(new Error(message), { errcode: e.data.errcode });
} else {
results[roomId] = e || new Error("Failed to leave room for unknown causes");
}
@ -140,6 +140,6 @@ export async function leaveRoomBehaviour(roomId: string) {
}
if (RoomViewStore.getRoomId() === roomId) {
dis.dispatch({action: 'view_home_page'});
dis.dispatch({ action: 'view_home_page' });
}
}

View file

@ -114,7 +114,7 @@ export function objectDiff<O extends {}>(a: O, b: O): Diff<keyof O> {
const possibleChanges = arrayUnion(aKeys, bKeys);
const changes = possibleChanges.filter(k => a[k] !== b[k]);
return {changed: changes, added: keyDiff.added, removed: keyDiff.removed};
return { changed: changes, added: keyDiff.added, removed: keyDiff.removed };
}
/**
@ -152,7 +152,7 @@ export function objectClone<O extends {}>(obj: O): O {
export function objectFromEntries<K, V>(entries: Iterable<[K, V]>): {[k: K]: V} {
const obj: {
// @ts-ignore - same as return type
[k: K]: V} = {};
[k: K]: V;} = {};
for (const e of entries) {
// @ts-ignore - same as return type
obj[e[0]] = e[1];

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import PermalinkConstructor, {PermalinkParts} from "./PermalinkConstructor";
import PermalinkConstructor, { PermalinkParts } from "./PermalinkConstructor";
/**
* Generates permalinks that self-reference the running webapp

View file

@ -16,14 +16,14 @@ limitations under the License.
import isIp from "is-ip";
import * as utils from "matrix-js-sdk/src/utils";
import {Room} from "matrix-js-sdk/src/models/room";
import {EventType} from "matrix-js-sdk/src/@types/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import {MatrixClientPeg} from "../../MatrixClientPeg";
import SpecPermalinkConstructor, {baseUrl as matrixtoBaseUrl} from "./SpecPermalinkConstructor";
import PermalinkConstructor, {PermalinkParts} from "./PermalinkConstructor";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import SpecPermalinkConstructor, { baseUrl as matrixtoBaseUrl } from "./SpecPermalinkConstructor";
import PermalinkConstructor, { PermalinkParts } from "./PermalinkConstructor";
import ElementPermalinkConstructor from "./ElementPermalinkConstructor";
import matrixLinkify from "../../linkify-matrix";
import SdkConfig from "../../SdkConfig";
@ -32,7 +32,6 @@ import SdkConfig from "../../SdkConfig";
// to add to permalinks. The servers are appended as ?via=example.org
const MAX_SERVER_CANDIDATES = 3;
// Permalinks can have servers appended to them so that the user
// receiving them can have a fighting chance at joining the room.
// These servers are called "candidates" at this point because
@ -172,7 +171,7 @@ export class RoomPermalinkCreator {
this.updateServerCandidates();
return;
}
}
};
private onMembership = (evt: MatrixEvent, member: RoomMember, oldMembership: string) => {
const userId = member.userId;
@ -189,7 +188,7 @@ export class RoomPermalinkCreator {
this.updateHighestPlUser();
this.updateServerCandidates();
}
};
private updateHighestPlUser() {
const plEvent = this.room.currentState.getStateEvents("m.room.power_levels", "");

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import PermalinkConstructor, {PermalinkParts} from "./PermalinkConstructor";
import PermalinkConstructor, { PermalinkParts } from "./PermalinkConstructor";
export const host = "matrix.to";
export const baseUrl = `https://${host}`;

View file

@ -16,9 +16,11 @@ limitations under the License.
import React from "react";
import ReactDOM from 'react-dom';
import {MatrixClientPeg} from '../MatrixClientPeg';
import { PushProcessor } from 'matrix-js-sdk/src/pushprocessor';
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { MatrixClientPeg } from '../MatrixClientPeg';
import SettingsStore from "../settings/SettingsStore";
import {PushProcessor} from 'matrix-js-sdk/src/pushprocessor';
import Pill from "../components/views/elements/Pill";
import { parseAppLocalLink } from "./permalinks/Permalinks";
@ -27,15 +29,15 @@ import { parseAppLocalLink } from "./permalinks/Permalinks";
* into pills based on the context of a given room. Returns a list of
* the resulting React nodes so they can be unmounted rather than leaking.
*
* @param {Node[]} nodes - a list of sibling DOM nodes to traverse to try
* @param {Element[]} nodes - a list of sibling DOM nodes to traverse to try
* to turn into pills.
* @param {MatrixEvent} mxEvent - the matrix event which the DOM nodes are
* part of representing.
* @param {Node[]} pills: an accumulator of the DOM nodes which contain
* @param {Element[]} pills: an accumulator of the DOM nodes which contain
* React components which have been mounted as part of this.
* The initial caller should pass in an empty array to seed the accumulator.
*/
export function pillifyLinks(nodes, mxEvent, pills) {
export function pillifyLinks(nodes: ArrayLike<Element>, mxEvent: MatrixEvent, pills: Element[]) {
const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId());
const shouldShowPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar");
let node = nodes[0];
@ -73,7 +75,7 @@ export function pillifyLinks(nodes, mxEvent, pills) {
// to clear the pills from the last run of pillifyLinks
!node.parentElement.classList.contains("mx_AtRoomPill")
) {
let currentTextNode = node;
let currentTextNode = node as Node as Text;
const roomNotifTextNodes = [];
// Take a textNode and break it up to make all the instances of @room their
@ -125,10 +127,10 @@ export function pillifyLinks(nodes, mxEvent, pills) {
}
if (node.childNodes && node.childNodes.length && !pillified) {
pillifyLinks(node.childNodes, mxEvent, pills);
pillifyLinks(node.childNodes as NodeListOf<Element>, mxEvent, pills);
}
node = node.nextSibling;
node = node.nextSibling as Element;
}
}
@ -140,10 +142,10 @@ export function pillifyLinks(nodes, mxEvent, pills) {
* emitter on BaseAvatar as per
* https://github.com/vector-im/element-web/issues/12417
*
* @param {Node[]} pills - array of pill containers whose React
* @param {Element[]} pills - array of pill containers whose React
* components should be unmounted.
*/
export function unmountPills(pills) {
export function unmountPills(pills: Element[]) {
for (const pillContainer of pills) {
ReactDOM.unmountComponentAtNode(pillContainer);
}

View file

@ -14,11 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// Returns a promise which resolves with a given value after the given number of ms
export function sleep<T>(ms: number, value?: T): Promise<T> {
return new Promise((resolve => { setTimeout(resolve, ms, value); }));
}
// Returns a promise which resolves when the input promise resolves with its value
// or when the timeout of ms is reached with the value of given timeoutValue
export async function timeout<T>(promise: Promise<T>, timeoutValue: T, ms: number): Promise<T> {
@ -32,25 +27,6 @@ export async function timeout<T>(promise: Promise<T>, timeoutValue: T, ms: numbe
return Promise.race([promise, timeoutPromise]);
}
export interface IDeferred<T> {
resolve: (value: T) => void;
reject: (any) => void;
promise: Promise<T>;
}
// Returns a Deferred
export function defer<T>(): IDeferred<T> {
let resolve;
let reject;
const promise = new Promise<T>((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
return {resolve, reject, promise};
}
// Helper method to retry a Promise a given number of times or until a predicate fails
export async function retry<T, E extends Error>(fn: () => Promise<T>, num: number, predicate?: (e: E) => boolean) {
let lastErr: E;

View file

@ -15,17 +15,17 @@ limitations under the License.
*/
import React from "react";
import {Room} from "matrix-js-sdk/src/models/room";
import {MatrixClient} from "matrix-js-sdk/src/client";
import {EventType} from "matrix-js-sdk/src/@types/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { EventType } from "matrix-js-sdk/src/@types/event";
import {calculateRoomVia} from "../utils/permalinks/Permalinks";
import { calculateRoomVia } from "../utils/permalinks/Permalinks";
import Modal from "../Modal";
import SpaceSettingsDialog from "../components/views/dialogs/SpaceSettingsDialog";
import AddExistingToSpaceDialog from "../components/views/dialogs/AddExistingToSpaceDialog";
import CreateRoomDialog from "../components/views/dialogs/CreateRoomDialog";
import createRoom, {IOpts} from "../createRoom";
import {_t} from "../languageHandler";
import createRoom, { IOpts } from "../createRoom";
import { _t } from "../languageHandler";
import SpacePublicShare from "../components/views/spaces/SpacePublicShare";
import InfoDialog from "../components/views/dialogs/InfoDialog";
import { showRoomInviteDialog } from "../RoomInvite";

View file

@ -0,0 +1,148 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { alphabetPad, baseToString, stringToBase, DEFAULT_ALPHABET } from "matrix-js-sdk/src/utils";
import { moveElement } from "./arrays";
export function midPointsBetweenStrings(
a: string,
b: string,
count: number,
maxLen: number,
alphabet = DEFAULT_ALPHABET,
): string[] {
const padN = Math.min(Math.max(a.length, b.length), maxLen);
const padA = alphabetPad(a, padN, alphabet);
const padB = alphabetPad(b, padN, alphabet);
const baseA = stringToBase(padA, alphabet);
const baseB = stringToBase(padB, alphabet);
if (baseB - baseA - BigInt(1) < count) {
if (padN < maxLen) {
// this recurses once at most due to the new limit of n+1
return midPointsBetweenStrings(
alphabetPad(padA, padN + 1, alphabet),
alphabetPad(padB, padN + 1, alphabet),
count,
padN + 1,
alphabet,
);
}
return [];
}
const step = (baseB - baseA) / BigInt(count + 1);
const start = BigInt(baseA + step);
return Array(count).fill(undefined).map((_, i) => baseToString(start + (BigInt(i) * step), alphabet));
}
interface IEntry {
index: number;
order: string;
}
export const reorderLexicographically = (
orders: Array<string | undefined>,
fromIndex: number,
toIndex: number,
maxLen = 50,
): IEntry[] => {
// sanity check inputs
if (
fromIndex < 0 || toIndex < 0 ||
fromIndex > orders.length || toIndex > orders.length ||
fromIndex === toIndex
) {
return [];
}
// zip orders with their indices to simplify later index wrangling
const ordersWithIndices: IEntry[] = orders.map((order, index) => ({ index, order }));
// apply the fundamental order update to the zipped array
const newOrder = moveElement(ordersWithIndices, fromIndex, toIndex);
// check if we have to fill undefined orders to complete placement
const orderToLeftUndefined = newOrder[toIndex - 1]?.order === undefined;
let leftBoundIdx = toIndex;
let rightBoundIdx = toIndex;
let canMoveLeft = true;
const nextBase = newOrder[toIndex + 1]?.order !== undefined
? stringToBase(newOrder[toIndex + 1].order)
: BigInt(Number.MAX_VALUE);
// check how far left we would have to mutate to fit in that direction
for (let i = toIndex - 1, j = 1; i >= 0; i--, j++) {
if (newOrder[i]?.order !== undefined && nextBase - stringToBase(newOrder[i].order) > j) break;
leftBoundIdx = i;
}
// verify the left move would be sufficient
const firstOrderBase = newOrder[0].order === undefined ? undefined : stringToBase(newOrder[0].order);
const bigToIndex = BigInt(toIndex);
if (leftBoundIdx === 0 &&
firstOrderBase !== undefined &&
nextBase - firstOrderBase <= bigToIndex &&
firstOrderBase <= bigToIndex
) {
canMoveLeft = false;
}
const canDisplaceRight = !orderToLeftUndefined;
let canMoveRight = canDisplaceRight;
if (canDisplaceRight) {
const prevBase = newOrder[toIndex - 1]?.order !== undefined
? stringToBase(newOrder[toIndex - 1]?.order)
: BigInt(Number.MIN_VALUE);
// check how far right we would have to mutate to fit in that direction
for (let i = toIndex + 1, j = 1; i < newOrder.length; i++, j++) {
if (newOrder[i]?.order === undefined || stringToBase(newOrder[i].order) - prevBase > j) break;
rightBoundIdx = i;
}
// verify the right move would be sufficient
if (rightBoundIdx === newOrder.length - 1 &&
(newOrder[rightBoundIdx]
? stringToBase(newOrder[rightBoundIdx].order)
: BigInt(Number.MAX_VALUE)) - prevBase <= (rightBoundIdx - toIndex)
) {
canMoveRight = false;
}
}
// pick the cheaper direction
const leftDiff = canMoveLeft ? toIndex - leftBoundIdx : Number.MAX_SAFE_INTEGER;
const rightDiff = canMoveRight ? rightBoundIdx - toIndex : Number.MAX_SAFE_INTEGER;
if (orderToLeftUndefined || leftDiff < rightDiff) {
rightBoundIdx = toIndex;
} else {
leftBoundIdx = toIndex;
}
const prevOrder = newOrder[leftBoundIdx - 1]?.order ?? "";
const nextOrder = newOrder[rightBoundIdx + 1]?.order
?? DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 1).repeat(prevOrder.length || 1);
const changes = midPointsBetweenStrings(prevOrder, nextOrder, 1 + rightBoundIdx - leftBoundIdx, maxLen);
return changes.map((order, i) => ({
index: newOrder[leftBoundIdx + i].index,
order,
}));
};

View file

@ -74,7 +74,6 @@ export function copyNode(ref: Element): boolean {
return document.execCommand('copy');
}
const collator = new Intl.Collator();
/**
* Performant language-sensitive string comparison