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:
commit
91d208d514
802 changed files with 12931 additions and 11005 deletions
|
@ -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[] = [
|
||||
|
|
|
@ -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, {});
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
53
src/utils/EditorStateTransfer.ts
Normal file
53
src/utils/EditorStateTransfer.ts
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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,
|
|
@ -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;
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'];
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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>`;
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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 = [
|
||||
|
|
|
@ -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;
|
|
@ -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]];
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 },
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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;
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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' });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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", "");
|
||||
|
|
|
@ -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}`;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
148
src/utils/stringOrderField.ts
Normal file
148
src/utils/stringOrderField.ts
Normal 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,
|
||||
}));
|
||||
};
|
|
@ -74,7 +74,6 @@ export function copyNode(ref: Element): boolean {
|
|||
return document.execCommand('copy');
|
||||
}
|
||||
|
||||
|
||||
const collator = new Intl.Collator();
|
||||
/**
|
||||
* Performant language-sensitive string comparison
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue