Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/fix/17244

This commit is contained in:
Michael Telatynski 2021-06-22 22:42:59 +01:00
commit e4542e13d9
24 changed files with 314 additions and 217 deletions

View file

@ -123,6 +123,7 @@
"@sinonjs/fake-timers": "^7.0.2", "@sinonjs/fake-timers": "^7.0.2",
"@types/classnames": "^2.2.11", "@types/classnames": "^2.2.11",
"@types/counterpart": "^0.18.1", "@types/counterpart": "^0.18.1",
"@types/diff-match-patch": "^1.0.5",
"@types/flux": "^3.1.9", "@types/flux": "^3.1.9",
"@types/jest": "^26.0.20", "@types/jest": "^26.0.20",
"@types/linkifyjs": "^2.1.3", "@types/linkifyjs": "^2.1.3",

50
src/@types/diff-dom.ts Normal file
View file

@ -0,0 +1,50 @@
/*
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.
*/
declare module "diff-dom" {
enum Action {
AddElement = "addElement",
AddTextElement = "addTextElement",
RemoveTextElement = "removeTextElement",
RemoveElement = "removeElement",
ReplaceElement = "replaceElement",
ModifyTextElement = "modifyTextElement",
AddAttribute = "addAttribute",
RemoveAttribute = "removeAttribute",
ModifyAttribute = "modifyAttribute",
}
export interface IDiff {
action: Action;
name: string;
text?: string;
route: number[];
value: string;
element: unknown;
oldValue: string;
newValue: string;
}
interface IOpts {
}
export class DiffDOM {
public constructor(opts?: IOpts);
public apply(tree: unknown, diffs: IDiff[]): unknown;
public undo(tree: unknown, diffs: IDiff[]): unknown;
public diff(a: HTMLElement | string, b: HTMLElement | string): IDiff[];
}
}

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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,34 +14,40 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { MatrixError } from "matrix-js-sdk/src/http-api";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
export class DecryptionFailure { export class DecryptionFailure {
constructor(failedEventId, errorCode) { public readonly ts: number;
this.failedEventId = failedEventId;
this.errorCode = errorCode; constructor(public readonly failedEventId: string, public readonly errorCode: string) {
this.ts = Date.now(); this.ts = Date.now();
} }
} }
type TrackingFn = (count: number, trackedErrCode: string) => void;
type ErrCodeMapFn = (errcode: string) => string;
export class DecryptionFailureTracker { export class DecryptionFailureTracker {
// Array of items of type DecryptionFailure. Every `CHECK_INTERVAL_MS`, this list // Array of items of type DecryptionFailure. Every `CHECK_INTERVAL_MS`, this list
// is checked for failures that happened > `GRACE_PERIOD_MS` ago. Those that did // is checked for failures that happened > `GRACE_PERIOD_MS` ago. Those that did
// are accumulated in `failureCounts`. // are accumulated in `failureCounts`.
failures = []; public failures: DecryptionFailure[] = [];
// A histogram of the number of failures that will be tracked at the next tracking // A histogram of the number of failures that will be tracked at the next tracking
// interval, split by failure error code. // interval, split by failure error code.
failureCounts = { public failureCounts: Record<string, number> = {
// [errorCode]: 42 // [errorCode]: 42
}; };
// Event IDs of failures that were tracked previously // Event IDs of failures that were tracked previously
trackedEventHashMap = { public trackedEventHashMap: Record<string, boolean> = {
// [eventId]: true // [eventId]: true
}; };
// Set to an interval ID when `start` is called // Set to an interval ID when `start` is called
checkInterval = null; public checkInterval: NodeJS.Timeout = null;
trackInterval = null; public trackInterval: NodeJS.Timeout = null;
// Spread the load on `Analytics` by tracking at a low frequency, `TRACK_INTERVAL_MS`. // Spread the load on `Analytics` by tracking at a low frequency, `TRACK_INTERVAL_MS`.
static TRACK_INTERVAL_MS = 60000; static TRACK_INTERVAL_MS = 60000;
@ -67,7 +73,7 @@ export class DecryptionFailureTracker {
* @param {function?} errorCodeMapFn The function used to map error codes to the * @param {function?} errorCodeMapFn The function used to map error codes to the
* trackedErrorCode. If not provided, the `.code` of errors will be used. * trackedErrorCode. If not provided, the `.code` of errors will be used.
*/ */
constructor(fn, errorCodeMapFn) { constructor(private readonly fn: TrackingFn, private readonly errorCodeMapFn?: ErrCodeMapFn) {
if (!fn || typeof fn !== 'function') { if (!fn || typeof fn !== 'function') {
throw new Error('DecryptionFailureTracker requires tracking function'); throw new Error('DecryptionFailureTracker requires tracking function');
} }
@ -75,9 +81,6 @@ export class DecryptionFailureTracker {
if (errorCodeMapFn && typeof errorCodeMapFn !== 'function') { if (errorCodeMapFn && typeof errorCodeMapFn !== 'function') {
throw new Error('DecryptionFailureTracker second constructor argument should be a function'); throw new Error('DecryptionFailureTracker second constructor argument should be a function');
} }
this._trackDecryptionFailure = fn;
this._mapErrorCode = errorCodeMapFn;
} }
// loadTrackedEventHashMap() { // loadTrackedEventHashMap() {
@ -88,7 +91,7 @@ export class DecryptionFailureTracker {
// localStorage.setItem('mx-decryption-failure-event-id-hashes', JSON.stringify(this.trackedEventHashMap)); // localStorage.setItem('mx-decryption-failure-event-id-hashes', JSON.stringify(this.trackedEventHashMap));
// } // }
eventDecrypted(e, err) { public eventDecrypted(e: MatrixEvent, err: MatrixError | Error): void {
if (err) { if (err) {
this.addDecryptionFailure(new DecryptionFailure(e.getId(), err.code)); this.addDecryptionFailure(new DecryptionFailure(e.getId(), err.code));
} else { } else {
@ -97,18 +100,18 @@ export class DecryptionFailureTracker {
} }
} }
addDecryptionFailure(failure) { public addDecryptionFailure(failure: DecryptionFailure): void {
this.failures.push(failure); this.failures.push(failure);
} }
removeDecryptionFailuresForEvent(e) { public removeDecryptionFailuresForEvent(e: MatrixEvent): void {
this.failures = this.failures.filter((f) => f.failedEventId !== e.getId()); this.failures = this.failures.filter((f) => f.failedEventId !== e.getId());
} }
/** /**
* Start checking for and tracking failures. * Start checking for and tracking failures.
*/ */
start() { public start(): void {
this.checkInterval = setInterval( this.checkInterval = setInterval(
() => this.checkFailures(Date.now()), () => this.checkFailures(Date.now()),
DecryptionFailureTracker.CHECK_INTERVAL_MS, DecryptionFailureTracker.CHECK_INTERVAL_MS,
@ -123,7 +126,7 @@ export class DecryptionFailureTracker {
/** /**
* Clear state and stop checking for and tracking failures. * Clear state and stop checking for and tracking failures.
*/ */
stop() { public stop(): void {
clearInterval(this.checkInterval); clearInterval(this.checkInterval);
clearInterval(this.trackInterval); clearInterval(this.trackInterval);
@ -132,11 +135,11 @@ export class DecryptionFailureTracker {
} }
/** /**
* Mark failures that occured before nowTs - GRACE_PERIOD_MS as failures that should be * Mark failures that occurred before nowTs - GRACE_PERIOD_MS as failures that should be
* tracked. Only mark one failure per event ID. * tracked. Only mark one failure per event ID.
* @param {number} nowTs the timestamp that represents the time now. * @param {number} nowTs the timestamp that represents the time now.
*/ */
checkFailures(nowTs) { public checkFailures(nowTs: number): void {
const failuresGivenGrace = []; const failuresGivenGrace = [];
const failuresNotReady = []; const failuresNotReady = [];
while (this.failures.length > 0) { while (this.failures.length > 0) {
@ -175,10 +178,10 @@ export class DecryptionFailureTracker {
const dedupedFailures = dedupedFailuresMap.values(); const dedupedFailures = dedupedFailuresMap.values();
this._aggregateFailures(dedupedFailures); this.aggregateFailures(dedupedFailures);
} }
_aggregateFailures(failures) { private aggregateFailures(failures: DecryptionFailure[]): void {
for (const failure of failures) { for (const failure of failures) {
const errorCode = failure.errorCode; const errorCode = failure.errorCode;
this.failureCounts[errorCode] = (this.failureCounts[errorCode] || 0) + 1; this.failureCounts[errorCode] = (this.failureCounts[errorCode] || 0) + 1;
@ -189,12 +192,12 @@ export class DecryptionFailureTracker {
* If there are failures that should be tracked, call the given trackDecryptionFailure * If there are failures that should be tracked, call the given trackDecryptionFailure
* function with the number of failures that should be tracked. * function with the number of failures that should be tracked.
*/ */
trackFailures() { public trackFailures(): void {
for (const errorCode of Object.keys(this.failureCounts)) { for (const errorCode of Object.keys(this.failureCounts)) {
if (this.failureCounts[errorCode] > 0) { if (this.failureCounts[errorCode] > 0) {
const trackedErrorCode = this._mapErrorCode ? this._mapErrorCode(errorCode) : errorCode; const trackedErrorCode = this.errorCodeMapFn ? this.errorCodeMapFn(errorCode) : errorCode;
this._trackDecryptionFailure(this.failureCounts[errorCode], trackedErrorCode); this.fn(this.failureCounts[errorCode], trackedErrorCode);
this.failureCounts[errorCode] = 0; this.failureCounts[errorCode] = 0;
} }
} }

View file

@ -17,11 +17,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React, { ReactNode } from 'react';
import sanitizeHtml from 'sanitize-html'; import sanitizeHtml from 'sanitize-html';
import { IExtendedSanitizeOptions } from './@types/sanitize-html'; import cheerio from 'cheerio';
import * as linkify from 'linkifyjs'; import * as linkify from 'linkifyjs';
import linkifyMatrix from './linkify-matrix';
import _linkifyElement from 'linkifyjs/element'; import _linkifyElement from 'linkifyjs/element';
import _linkifyString from 'linkifyjs/string'; import _linkifyString from 'linkifyjs/string';
import classNames from 'classnames'; import classNames from 'classnames';
@ -29,13 +28,15 @@ import EMOJIBASE_REGEX from 'emojibase-regex';
import url from 'url'; import url from 'url';
import katex from 'katex'; import katex from 'katex';
import { AllHtmlEntities } from 'html-entities'; import { AllHtmlEntities } from 'html-entities';
import SettingsStore from './settings/SettingsStore'; import { IContent } from 'matrix-js-sdk/src/models/event';
import cheerio from 'cheerio';
import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks"; import { IExtendedSanitizeOptions } from './@types/sanitize-html';
import {SHORTCODE_TO_EMOJI, getEmojiFromUnicode} from "./emoji"; import linkifyMatrix from './linkify-matrix';
import SettingsStore from './settings/SettingsStore';
import { tryTransformPermalinkToLocalHref } from "./utils/permalinks/Permalinks";
import { SHORTCODE_TO_EMOJI, getEmojiFromUnicode } from "./emoji";
import ReplyThread from "./components/views/elements/ReplyThread"; import ReplyThread from "./components/views/elements/ReplyThread";
import {mediaFromMxc} from "./customisations/Media"; import { mediaFromMxc } from "./customisations/Media";
linkifyMatrix(linkify); linkifyMatrix(linkify);
@ -66,7 +67,7 @@ export const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet'
* need emojification. * need emojification.
* unicodeToImage uses this function. * unicodeToImage uses this function.
*/ */
function mightContainEmoji(str: string) { function mightContainEmoji(str: string): boolean {
return SURROGATE_PAIR_PATTERN.test(str) || SYMBOL_PATTERN.test(str); return SURROGATE_PAIR_PATTERN.test(str) || SYMBOL_PATTERN.test(str);
} }
@ -76,7 +77,7 @@ function mightContainEmoji(str: string) {
* @param {String} char The emoji character * @param {String} char The emoji character
* @return {String} The shortcode (such as :thumbup:) * @return {String} The shortcode (such as :thumbup:)
*/ */
export function unicodeToShortcode(char: string) { export function unicodeToShortcode(char: string): string {
const data = getEmojiFromUnicode(char); const data = getEmojiFromUnicode(char);
return (data && data.shortcodes ? `:${data.shortcodes[0]}:` : ''); return (data && data.shortcodes ? `:${data.shortcodes[0]}:` : '');
} }
@ -87,7 +88,7 @@ export function unicodeToShortcode(char: string) {
* @param {String} shortcode The shortcode (such as :thumbup:) * @param {String} shortcode The shortcode (such as :thumbup:)
* @return {String} The emoji character; null if none exists * @return {String} The emoji character; null if none exists
*/ */
export function shortcodeToUnicode(shortcode: string) { export function shortcodeToUnicode(shortcode: string): string {
shortcode = shortcode.slice(1, shortcode.length - 1); shortcode = shortcode.slice(1, shortcode.length - 1);
const data = SHORTCODE_TO_EMOJI.get(shortcode); const data = SHORTCODE_TO_EMOJI.get(shortcode);
return data ? data.unicode : null; return data ? data.unicode : null;
@ -124,13 +125,13 @@ export function processHtmlForSending(html: string): string {
* Given an untrusted HTML string, return a React node with an sanitized version * Given an untrusted HTML string, return a React node with an sanitized version
* of that HTML. * of that HTML.
*/ */
export function sanitizedHtmlNode(insaneHtml: string) { export function sanitizedHtmlNode(insaneHtml: string): ReactNode {
const saneHtml = sanitizeHtml(insaneHtml, sanitizeHtmlParams); const saneHtml = sanitizeHtml(insaneHtml, sanitizeHtmlParams);
return <div dangerouslySetInnerHTML={{ __html: saneHtml }} dir="auto" />; return <div dangerouslySetInnerHTML={{ __html: saneHtml }} dir="auto" />;
} }
export function getHtmlText(insaneHtml: string) { export function getHtmlText(insaneHtml: string): string {
return sanitizeHtml(insaneHtml, { return sanitizeHtml(insaneHtml, {
allowedTags: [], allowedTags: [],
allowedAttributes: {}, allowedAttributes: {},
@ -148,7 +149,7 @@ export function getHtmlText(insaneHtml: string) {
* other places we need to sanitise URLs. * other places we need to sanitise URLs.
* @return true if permitted, otherwise false * @return true if permitted, otherwise false
*/ */
export function isUrlPermitted(inputUrl: string) { export function isUrlPermitted(inputUrl: string): boolean {
try { try {
const parsed = url.parse(inputUrl); const parsed = url.parse(inputUrl);
if (!parsed.protocol) return false; if (!parsed.protocol) return false;
@ -351,13 +352,6 @@ class HtmlHighlighter extends BaseHighlighter<string> {
} }
} }
interface IContent {
format?: string;
// eslint-disable-next-line camelcase
formatted_body?: string;
body: string;
}
interface IOpts { interface IOpts {
highlightLink?: string; highlightLink?: string;
disableBigEmoji?: boolean; disableBigEmoji?: boolean;
@ -367,6 +361,14 @@ interface IOpts {
ref?: React.Ref<any>; ref?: React.Ref<any>;
} }
export interface IOptsReturnNode extends IOpts {
returnString: false;
}
export interface IOptsReturnString extends IOpts {
returnString: true;
}
/* turn a matrix event body into html /* turn a matrix event body into html
* *
* content: 'content' of the MatrixEvent * content: 'content' of the MatrixEvent
@ -380,6 +382,8 @@ interface IOpts {
* opts.forComposerQuote: optional param to lessen the url rewriting done by sanitization, for quoting into composer * opts.forComposerQuote: optional param to lessen the url rewriting done by sanitization, for quoting into composer
* opts.ref: React ref to attach to any React components returned (not compatible with opts.returnString) * opts.ref: React ref to attach to any React components returned (not compatible with opts.returnString)
*/ */
export function bodyToHtml(content: IContent, highlights: string[], opts: IOptsReturnString): string;
export function bodyToHtml(content: IContent, highlights: string[], opts: IOptsReturnNode): ReactNode;
export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts = {}) { export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts = {}) {
const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body; const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body;
let bodyHasEmoji = false; let bodyHasEmoji = false;
@ -501,7 +505,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
* @param {object} [options] Options for linkifyString. Default: linkifyMatrix.options * @param {object} [options] Options for linkifyString. Default: linkifyMatrix.options
* @returns {string} Linkified string * @returns {string} Linkified string
*/ */
export function linkifyString(str: string, options = linkifyMatrix.options) { export function linkifyString(str: string, options = linkifyMatrix.options): string {
return _linkifyString(str, options); return _linkifyString(str, options);
} }
@ -512,7 +516,7 @@ export function linkifyString(str: string, options = linkifyMatrix.options) {
* @param {object} [options] Options for linkifyElement. Default: linkifyMatrix.options * @param {object} [options] Options for linkifyElement. Default: linkifyMatrix.options
* @returns {object} * @returns {object}
*/ */
export function linkifyElement(element: HTMLElement, options = linkifyMatrix.options) { export function linkifyElement(element: HTMLElement, options = linkifyMatrix.options): HTMLElement {
return _linkifyElement(element, options); return _linkifyElement(element, options);
} }
@ -523,7 +527,7 @@ export function linkifyElement(element: HTMLElement, options = linkifyMatrix.opt
* @param {object} [options] Options for linkifyString. Default: linkifyMatrix.options * @param {object} [options] Options for linkifyString. Default: linkifyMatrix.options
* @returns {string} * @returns {string}
*/ */
export function linkifyAndSanitizeHtml(dirtyHtml: string, options = linkifyMatrix.options) { export function linkifyAndSanitizeHtml(dirtyHtml: string, options = linkifyMatrix.options): string {
return sanitizeHtml(linkifyString(dirtyHtml, options), sanitizeHtmlParams); return sanitizeHtml(linkifyString(dirtyHtml, options), sanitizeHtmlParams);
} }
@ -534,7 +538,7 @@ export function linkifyAndSanitizeHtml(dirtyHtml: string, options = linkifyMatri
* @param {Node} node * @param {Node} node
* @returns {bool} * @returns {bool}
*/ */
export function checkBlockNode(node: Node) { export function checkBlockNode(node: Node): boolean {
switch (node.nodeName) { switch (node.nodeName) {
case "H1": case "H1":
case "H2": case "H2":

View file

@ -1,5 +1,5 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,7 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {MatrixClientPeg} from './MatrixClientPeg'; import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClientPeg } from './MatrixClientPeg';
/** /**
* Given a room object, return the alias we should use for it, * Given a room object, return the alias we should use for it,
@ -25,11 +27,11 @@ import {MatrixClientPeg} from './MatrixClientPeg';
* @param {Object} room The room object * @param {Object} room The room object
* @returns {string} A display alias for the given room * @returns {string} A display alias for the given room
*/ */
export function getDisplayAliasForRoom(room) { export function getDisplayAliasForRoom(room: Room): string {
return room.getCanonicalAlias() || room.getAltAliases()[0]; return room.getCanonicalAlias() || room.getAltAliases()[0];
} }
export function looksLikeDirectMessageRoom(room, myUserId) { export function looksLikeDirectMessageRoom(room: Room, myUserId: string): boolean {
const myMembership = room.getMyMembership(); const myMembership = room.getMyMembership();
const me = room.getMember(myUserId); const me = room.getMember(myUserId);
@ -48,7 +50,7 @@ export function looksLikeDirectMessageRoom(room, myUserId) {
return false; return false;
} }
export function guessAndSetDMRoom(room, isDirect) { export function guessAndSetDMRoom(room: Room, isDirect: boolean): Promise<void> {
let newTarget; let newTarget;
if (isDirect) { if (isDirect) {
const guessedUserId = guessDMRoomTargetId( const guessedUserId = guessDMRoomTargetId(
@ -70,7 +72,7 @@ export function guessAndSetDMRoom(room, isDirect) {
this room as a DM room this room as a DM room
* @returns {object} A promise * @returns {object} A promise
*/ */
export function setDMRoom(roomId, userId) { export function setDMRoom(roomId: string, userId: string): Promise<void> {
if (MatrixClientPeg.get().isGuest()) { if (MatrixClientPeg.get().isGuest()) {
return Promise.resolve(); return Promise.resolve();
} }
@ -114,7 +116,7 @@ export function setDMRoom(roomId, userId) {
* @param {string} myUserId User ID of the current user * @param {string} myUserId User ID of the current user
* @returns {string} User ID of the user that the room is probably a DM with * @returns {string} User ID of the user that the room is probably a DM with
*/ */
function guessDMRoomTargetId(room, myUserId) { function guessDMRoomTargetId(room: Room, myUserId: string): string {
let oldestTs; let oldestTs;
let oldestUser; let oldestUser;

View file

@ -1,5 +1,5 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,9 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {MatrixClientPeg} from "./MatrixClientPeg"; import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
import { MatrixClientPeg } from "./MatrixClientPeg";
import shouldHideEvent from './shouldHideEvent'; import shouldHideEvent from './shouldHideEvent';
import {haveTileForEvent} from "./components/views/rooms/EventTile"; import { haveTileForEvent } from "./components/views/rooms/EventTile";
/** /**
* Returns true iff this event arriving in a room should affect the room's * Returns true iff this event arriving in a room should affect the room's
@ -25,28 +29,33 @@ import {haveTileForEvent} from "./components/views/rooms/EventTile";
* @param {Object} ev The event * @param {Object} ev The event
* @returns {boolean} True if the given event should affect the unread message count * @returns {boolean} True if the given event should affect the unread message count
*/ */
export function eventTriggersUnreadCount(ev) { export function eventTriggersUnreadCount(ev: MatrixEvent): boolean {
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) { if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) {
return false; return false;
} else if (ev.getType() == 'm.room.member') {
return false;
} else if (ev.getType() == 'm.room.third_party_invite') {
return false;
} else if (ev.getType() == 'm.call.answer' || ev.getType() == 'm.call.hangup') {
return false;
} else if (ev.getType() == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
return false;
} else if (ev.getType() == 'm.room.aliases' || ev.getType() == 'm.room.canonical_alias') {
return false;
} else if (ev.getType() == 'm.room.server_acl') {
return false;
} else if (ev.isRedacted()) {
return false;
} }
switch (ev.getType()) {
case EventType.RoomMember:
case EventType.RoomThirdPartyInvite:
case EventType.CallAnswer:
case EventType.CallHangup:
case EventType.RoomAliases:
case EventType.RoomCanonicalAlias:
case EventType.RoomServerAcl:
return false;
case EventType.RoomMessage:
if (ev.getContent().msgtype === MsgType.Notice) {
return false;
}
break;
}
if (ev.isRedacted()) return false;
return haveTileForEvent(ev); return haveTileForEvent(ev);
} }
export function doesRoomHaveUnreadMessages(room) { export function doesRoomHaveUnreadMessages(room: Room): boolean {
const myUserId = MatrixClientPeg.get().getUserId(); const myUserId = MatrixClientPeg.get().getUserId();
// get the most recent read receipt sent by our account. // get the most recent read receipt sent by our account.

View file

@ -1461,7 +1461,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}); });
const dft = new DecryptionFailureTracker((total, errorCode) => { const dft = new DecryptionFailureTracker((total, errorCode) => {
Analytics.trackEvent('E2E', 'Decryption failure', errorCode, total); Analytics.trackEvent('E2E', 'Decryption failure', errorCode, String(total));
CountlyAnalytics.instance.track("decryption_failure", { errorCode }, null, { sum: total }); CountlyAnalytics.instance.track("decryption_failure", { errorCode }, null, { sum: total });
}, (errorCode) => { }, (errorCode) => {
// Map JS-SDK error codes to tracker codes for aggregation // Map JS-SDK error codes to tracker codes for aggregation

View file

@ -14,30 +14,31 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, {useMemo, useState, useEffect} from "react"; import React, { useMemo, useState, useEffect } from "react";
import classnames from "classnames"; import classnames from "classnames";
import {MatrixEvent} from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import {Room} from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import {MatrixClient} from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import {_t} from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import {useSettingValue, useFeatureEnabled} from "../../../hooks/useSettings"; import { useSettingValue, useFeatureEnabled } from "../../../hooks/useSettings";
import {UIFeature} from "../../../settings/UIFeature"; import { UIFeature } from "../../../settings/UIFeature";
import {Layout} from "../../../settings/Layout"; import { Layout } from "../../../settings/Layout";
import {IDialogProps} from "./IDialogProps"; import { IDialogProps } from "./IDialogProps";
import BaseDialog from "./BaseDialog"; import BaseDialog from "./BaseDialog";
import {avatarUrlForUser} from "../../../Avatar"; import { avatarUrlForUser } from "../../../Avatar";
import EventTile from "../rooms/EventTile"; import EventTile from "../rooms/EventTile";
import SearchBox from "../../structures/SearchBox"; import SearchBox from "../../structures/SearchBox";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import {Alignment} from '../elements/Tooltip'; import { Alignment } from '../elements/Tooltip';
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
import NotificationBadge from "../rooms/NotificationBadge"; import NotificationBadge from "../rooms/NotificationBadge";
import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm"; import { sortRooms } from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
import QueryMatcher from "../../../autocomplete/QueryMatcher"; import QueryMatcher from "../../../autocomplete/QueryMatcher";
const AVATAR_SIZE = 30; const AVATAR_SIZE = 30;
@ -171,7 +172,7 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
); );
}, },
getMxcAvatarUrl: () => profileInfo.avatar_url, getMxcAvatarUrl: () => profileInfo.avatar_url,
}; } as RoomMember;
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const lcQuery = query.toLowerCase(); const lcQuery = query.toLowerCase();

View file

@ -17,13 +17,14 @@ limitations under the License.
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
import * as Avatar from '../../../Avatar'; import * as Avatar from '../../../Avatar';
import EventTile from '../rooms/EventTile'; import EventTile from '../rooms/EventTile';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import {Layout} from "../../../settings/Layout"; import { Layout } from "../../../settings/Layout";
import {UIFeature} from "../../../settings/UIFeature"; import { UIFeature } from "../../../settings/UIFeature";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps { interface IProps {
/** /**
@ -111,7 +112,7 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
); );
}, },
getMxcAvatarUrl: () => this.props.avatarUrl, getMxcAvatarUrl: () => this.props.avatarUrl,
}; } as RoomMember;
return event; return event;
} }

View file

@ -25,7 +25,7 @@ import RateLimitedFunc from '../../../ratelimitedfunc';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import { UIFeature } from "../../../settings/UIFeature"; import { UIFeature } from "../../../settings/UIFeature";
import { ResizeNotifier } from "../../../utils/ResizeNotifier"; import ResizeNotifier from "../../../utils/ResizeNotifier";
import CallViewForRoom from '../voip/CallViewForRoom'; import CallViewForRoom from '../voip/CallViewForRoom';
import { objectHasDiff } from "../../../utils/objects"; import { objectHasDiff } from "../../../utils/objects";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import classNames from "classnames"; import classNames from "classnames";
import { EventType } from "matrix-js-sdk/src/@types/event"; import { EventType } from "matrix-js-sdk/src/@types/event";
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Relations } from "matrix-js-sdk/src/models/relations"; import { Relations } from "matrix-js-sdk/src/models/relations";
@ -29,24 +28,24 @@ import { hasText } from "../../../TextForEvent";
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import {Layout} from "../../../settings/Layout"; import { Layout } from "../../../settings/Layout";
import {formatTime} from "../../../DateUtils"; import { formatTime } from "../../../DateUtils";
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { MatrixClientPeg } from '../../../MatrixClientPeg';
import {ALL_RULE_TYPES} from "../../../mjolnir/BanList"; import { ALL_RULE_TYPES } from "../../../mjolnir/BanList";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {E2E_STATE} from "./E2EIcon"; import { E2E_STATE } from "./E2EIcon";
import {toRem} from "../../../utils/units"; import { toRem } from "../../../utils/units";
import {WidgetType} from "../../../widgets/WidgetType"; import { WidgetType } from "../../../widgets/WidgetType";
import RoomAvatar from "../avatars/RoomAvatar"; import RoomAvatar from "../avatars/RoomAvatar";
import {WIDGET_LAYOUT_EVENT_TYPE} from "../../../stores/widgets/WidgetLayoutStore"; import { WIDGET_LAYOUT_EVENT_TYPE } from "../../../stores/widgets/WidgetLayoutStore";
import {objectHasDiff} from "../../../utils/objects"; import { objectHasDiff } from "../../../utils/objects";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import Tooltip from "../elements/Tooltip"; import Tooltip from "../elements/Tooltip";
import { EditorStateTransfer } from "../../../utils/EditorStateTransfer"; import EditorStateTransfer from "../../../utils/EditorStateTransfer";
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
import NotificationBadge from "./NotificationBadge"; import NotificationBadge from "./NotificationBadge";
import {ComposerInsertPayload} from "../../../dispatcher/payloads/ComposerInsertPayload"; import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from '../../../dispatcher/actions'; import { Action } from '../../../dispatcher/actions';
const eventTileTypes = { const eventTileTypes = {

View file

@ -22,7 +22,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
import { _t, _td } from "../../../languageHandler"; import { _t, _td } from "../../../languageHandler";
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex"; import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
import { ResizeNotifier } from "../../../utils/ResizeNotifier"; import ResizeNotifier from "../../../utils/ResizeNotifier";
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
import RoomViewStore from "../../../stores/RoomViewStore"; import RoomViewStore from "../../../stores/RoomViewStore";
import { ITagMap } from "../../../stores/room-list/algorithms/models"; import { ITagMap } from "../../../stores/room-list/algorithms/models";

View file

@ -45,7 +45,7 @@ import { ActionPayload } from "../../../dispatcher/payloads";
import { Enable, Resizable } from "re-resizable"; import { Enable, Resizable } from "re-resizable";
import { Direction } from "re-resizable/lib/resizer"; import { Direction } from "re-resizable/lib/resizer";
import { polyfillTouchEvent } from "../../../@types/polyfill"; import { polyfillTouchEvent } from "../../../@types/polyfill";
import { ResizeNotifier } from "../../../utils/ResizeNotifier"; import ResizeNotifier from "../../../utils/ResizeNotifier";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore"; import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore";
import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays"; import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays";

View file

@ -112,7 +112,7 @@ export interface IVariables {
[key: string]: SubstitutionValue; [key: string]: SubstitutionValue;
} }
type Tags = Record<string, SubstitutionValue>; export type Tags = Record<string, SubstitutionValue>;
export type TranslatedString = string | React.ReactNode; export type TranslatedString = string | React.ReactNode;

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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,36 +14,40 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { SerializedPart } from "../editor/parts";
import { Caret } from "../editor/caret";
/** /**
* Used while editing, to pass the event, and to preserve editor state * Used while editing, to pass the event, and to preserve editor state
* from one editor instance to another when remounting the editor * from one editor instance to another when remounting the editor
* upon receiving the remote echo for an unsent event. * upon receiving the remote echo for an unsent event.
*/ */
export default class EditorStateTransfer { export default class EditorStateTransfer {
constructor(event) { private serializedParts: SerializedPart[] = null;
this._event = event; private caret: Caret = null;
this._serializedParts = null;
this.caret = null; constructor(private readonly event: MatrixEvent) {}
public setEditorState(caret: Caret, serializedParts: SerializedPart[]) {
this.caret = caret;
this.serializedParts = serializedParts;
} }
setEditorState(caret, serializedParts) { public hasEditorState() {
this._caret = caret; return !!this.serializedParts;
this._serializedParts = serializedParts;
} }
hasEditorState() { public getSerializedParts() {
return !!this._serializedParts; return this.serializedParts;
} }
getSerializedParts() { public getCaret() {
return this._serializedParts; return this.caret;
} }
getCaret() { public getEvent() {
return this._caret; return this.event;
}
getEvent() {
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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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. 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 * 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' * for any tags in the strings apart from 'a'
* @returns {*} Translated string or react component * @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]; let errString = strings[limitType];
if (errString === undefined) errString = strings['']; 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') { if (err.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') {
const limitError = messageForResourceLimitError( const limitError = messageForResourceLimitError(
err.data.limit_type, 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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. limitations under the License.
*/ */
import { EventStatus } from 'matrix-js-sdk/src/models/event'; import { Room } from 'matrix-js-sdk/src/models/room';
import {MatrixClientPeg} from '../MatrixClientPeg'; import { MatrixEvent, EventStatus } from 'matrix-js-sdk/src/models/event';
import { MatrixClientPeg } from '../MatrixClientPeg';
import shouldHideEvent from "../shouldHideEvent"; import shouldHideEvent from "../shouldHideEvent";
/** /**
* Returns whether an event should allow actions like reply, reactions, edit, etc. * 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 * 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 * @param {MatrixEvent} mxEvent The event to check
* @returns {boolean} true if actionable * @returns {boolean} true if actionable
*/ */
export function isContentActionable(mxEvent) { export function isContentActionable(mxEvent: MatrixEvent): boolean {
const { status: eventStatus } = mxEvent; const { status: eventStatus } = mxEvent;
// status is SENT before remote-echo, null after // status is SENT before remote-echo, null after
@ -45,7 +48,7 @@ export function isContentActionable(mxEvent) {
return false; return false;
} }
export function canEditContent(mxEvent) { export function canEditContent(mxEvent: MatrixEvent): boolean {
if (mxEvent.status === EventStatus.CANCELLED || mxEvent.getType() !== "m.room.message" || mxEvent.isRedacted()) { if (mxEvent.status === EventStatus.CANCELLED || mxEvent.getType() !== "m.room.message" || mxEvent.isRedacted()) {
return false; return false;
} }
@ -56,7 +59,7 @@ export function canEditContent(mxEvent) {
mxEvent.getSender() === MatrixClientPeg.get().getUserId(); mxEvent.getSender() === MatrixClientPeg.get().getUserId();
} }
export function canEditOwnEvent(mxEvent) { export function canEditOwnEvent(mxEvent: MatrixEvent): boolean {
// for now we only allow editing // for now we only allow editing
// your own events. So this just call through // your own events. So this just call through
// In the future though, moderators will be able to // In the future though, moderators will be able to
@ -67,7 +70,7 @@ export function canEditOwnEvent(mxEvent) {
} }
const MAX_JUMP_DISTANCE = 100; 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 liveTimeline = room.getLiveTimeline();
const events = liveTimeline.getEvents().concat(room.getPendingEvents()); const events = liveTimeline.getEvents().concat(room.getPendingEvents());
const maxIdx = events.length - 1; const maxIdx = events.length - 1;

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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 { SERVICE_TYPES } from 'matrix-js-sdk/src/service-types';
import SdkConfig from '../SdkConfig'; import SdkConfig from '../SdkConfig';
import {MatrixClientPeg} from '../MatrixClientPeg'; import {MatrixClientPeg} from '../MatrixClientPeg';
export function getDefaultIdentityServerUrl() { export function getDefaultIdentityServerUrl(): string {
return SdkConfig.get()['validated_server_config']['isUrl']; return SdkConfig.get()['validated_server_config']['isUrl'];
} }
export function useDefaultIdentityServer() { export function useDefaultIdentityServer(): void {
const url = getDefaultIdentityServerUrl(); const url = getDefaultIdentityServerUrl();
// Account data change will update localstorage, client, etc through dispatcher // Account data change will update localstorage, client, etc through dispatcher
MatrixClientPeg.get().setAccountData("m.identity_server", { 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; let terms;
try { try {
terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IS, fullUrl); 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); 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"); const event = MatrixClientPeg.get().getAccountData("m.identity_server");
return event && event.getContent() && event.getContent()['base_url']; return event && event.getContent() && event.getContent()['base_url'];
} }

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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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. limitations under the License.
*/ */
import React from 'react'; import React, { ReactNode } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import DiffMatchPatch from 'diff-match-patch'; import { diff_match_patch as DiffMatchPatch } from 'diff-match-patch';
import {DiffDOM} from "diff-dom"; import { Action, DiffDOM, IDiff } from "diff-dom";
import { checkBlockNode, bodyToHtml } from "../HtmlUtils"; import { IContent } from "matrix-js-sdk/src/models/event";
import { bodyToHtml, checkBlockNode, IOptsReturnString } from "../HtmlUtils";
const decodeEntities = (function() { const decodeEntities = (function() {
let textarea = null; let textarea = null;
return function(string) { return function(str: string): string {
if (!textarea) { if (!textarea) {
textarea = document.createElement("textarea"); textarea = document.createElement("textarea");
} }
textarea.innerHTML = string; textarea.innerHTML = str;
return textarea.value; return textarea.value;
}; };
})(); })();
function textToHtml(text) { function textToHtml(text: string): string {
const container = document.createElement("div"); const container = document.createElement("div");
container.textContent = text; container.textContent = text;
return container.innerHTML; return container.innerHTML;
} }
function getSanitizedHtmlBody(content) { function getSanitizedHtmlBody(content: IContent): string {
const opts = { const opts: IOptsReturnString = {
stripReplyFallback: true, stripReplyFallback: true,
returnString: 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"); const wrapper = document.createElement(checkBlockNode(child) ? "div" : "span");
wrapper.className = "mx_EditHistoryMessage_insertion"; wrapper.className = "mx_EditHistoryMessage_insertion";
wrapper.appendChild(child); wrapper.appendChild(child);
return wrapper; return wrapper;
} }
function wrapDeletion(child) { function wrapDeletion(child: Node): HTMLElement {
const wrapper = document.createElement(checkBlockNode(child) ? "div" : "span"); const wrapper = document.createElement(checkBlockNode(child) ? "div" : "span");
wrapper.className = "mx_EditHistoryMessage_deletion"; wrapper.className = "mx_EditHistoryMessage_deletion";
wrapper.appendChild(child); wrapper.appendChild(child);
return wrapper; return wrapper;
} }
function findRefNodes(root, route, isAddition) { function findRefNodes(root: Node, route: number[], isAddition = false) {
let refNode = root; let refNode = root;
let refParentNode; let refParentNode;
const end = isAddition ? route.length - 1 : route.length; const end = isAddition ? route.length - 1 : route.length;
@ -79,7 +81,7 @@ function findRefNodes(root, route, isAddition) {
refParentNode = refNode; refParentNode = refNode;
refNode = refNode.childNodes[route[i]]; refNode = refNode.childNodes[route[i]];
} }
return {refNode, refParentNode}; return { refNode, refParentNode };
} }
function diffTreeToDOM(desc) { 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) { if (nextSibling) {
parent.insertBefore(child, nextSibling); parent.insertBefore(child, nextSibling);
} else { } 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, // routes are arrays with indices,
// to be interpreted as a path in the dom tree // to be interpreted as a path in the dom tree
@ -127,7 +129,7 @@ function isRouteOfNextSibling(route1, route2) {
return route2[lastD1Idx] >= route1[lastD1Idx]; return route2[lastD1Idx] >= route1[lastD1Idx];
} }
function adjustRoutes(diff, remainingDiffs) { function adjustRoutes(diff: IDiff, remainingDiffs: IDiff[]): void {
if (diff.action === "removeTextElement" || diff.action === "removeElement") { if (diff.action === "removeTextElement" || diff.action === "removeElement") {
// as removed text is not removed from the html, but marked as deleted, // 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. // we need to readjust indices that assume the current node has been removed.
@ -140,14 +142,14 @@ function adjustRoutes(diff, remainingDiffs) {
} }
} }
function stringAsTextNode(string) { function stringAsTextNode(string: string): Text {
return document.createTextNode(decodeEntities(string)); return document.createTextNode(decodeEntities(string));
} }
function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) { function renderDifferenceInDOM(originalRootNode: Node, diff: IDiff, diffMathPatch: DiffMatchPatch): void {
const {refNode, refParentNode} = findRefNodes(originalRootNode, diff.route); const {refNode, refParentNode} = findRefNodes(originalRootNode, diff.route);
switch (diff.action) { switch (diff.action) {
case "replaceElement": { case Action.ReplaceElement: {
const container = document.createElement("span"); const container = document.createElement("span");
const delNode = wrapDeletion(diffTreeToDOM(diff.oldValue)); const delNode = wrapDeletion(diffTreeToDOM(diff.oldValue));
const insNode = wrapInsertion(diffTreeToDOM(diff.newValue)); const insNode = wrapInsertion(diffTreeToDOM(diff.newValue));
@ -156,22 +158,22 @@ function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) {
refNode.parentNode.replaceChild(container, refNode); refNode.parentNode.replaceChild(container, refNode);
break; break;
} }
case "removeTextElement": { case Action.RemoveTextElement: {
const delNode = wrapDeletion(stringAsTextNode(diff.value)); const delNode = wrapDeletion(stringAsTextNode(diff.value));
refNode.parentNode.replaceChild(delNode, refNode); refNode.parentNode.replaceChild(delNode, refNode);
break; break;
} }
case "removeElement": { case Action.RemoveElement: {
const delNode = wrapDeletion(diffTreeToDOM(diff.element)); const delNode = wrapDeletion(diffTreeToDOM(diff.element));
refNode.parentNode.replaceChild(delNode, refNode); refNode.parentNode.replaceChild(delNode, refNode);
break; break;
} }
case "modifyTextElement": { case Action.ModifyTextElement: {
const textDiffs = diffMathPatch.diff_main(diff.oldValue, diff.newValue); const textDiffs = diffMathPatch.diff_main(diff.oldValue, diff.newValue);
diffMathPatch.diff_cleanupSemantic(textDiffs); diffMathPatch.diff_cleanupSemantic(textDiffs);
const container = document.createElement("span"); const container = document.createElement("span");
for (const [modifier, text] of textDiffs) { for (const [modifier, text] of textDiffs) {
let textDiffNode = stringAsTextNode(text); let textDiffNode: Node = stringAsTextNode(text);
if (modifier < 0) { if (modifier < 0) {
textDiffNode = wrapDeletion(textDiffNode); textDiffNode = wrapDeletion(textDiffNode);
} else if (modifier > 0) { } else if (modifier > 0) {
@ -182,12 +184,12 @@ function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) {
refNode.parentNode.replaceChild(container, refNode); refNode.parentNode.replaceChild(container, refNode);
break; break;
} }
case "addElement": { case Action.AddElement: {
const insNode = wrapInsertion(diffTreeToDOM(diff.element)); const insNode = wrapInsertion(diffTreeToDOM(diff.element));
insertBefore(refParentNode, refNode, insNode); insertBefore(refParentNode, refNode, insNode);
break; break;
} }
case "addTextElement": { case Action.AddTextElement: {
// XXX: sometimes diffDOM says insert a newline when there shouldn't be one // XXX: sometimes diffDOM says insert a newline when there shouldn't be one
// but we must insert the node anyway so that we don't break the route child IDs. // but we must insert the node anyway so that we don't break the route child IDs.
// See https://github.com/fiduswriter/diffDOM/issues/100 // See https://github.com/fiduswriter/diffDOM/issues/100
@ -197,11 +199,11 @@ function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) {
} }
// e.g. when changing a the href of a link, // e.g. when changing a the href of a link,
// show the link with old href as removed and with the new href as added // show the link with old href as removed and with the new href as added
case "removeAttribute": case Action.RemoveAttribute:
case "addAttribute": case Action.AddAttribute:
case "modifyAttribute": { case Action.ModifyAttribute: {
const delNode = wrapDeletion(refNode.cloneNode(true)); 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") { if (diff.action === "addAttribute" || diff.action === "modifyAttribute") {
updatedNode.setAttribute(diff.name, diff.newValue); updatedNode.setAttribute(diff.name, diff.newValue);
} else { } 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]); return r1.length === r2.length && !r1.some((e, i) => e !== r2[i]);
} }
// workaround for https://github.com/fiduswriter/diffDOM/issues/90 // workaround for https://github.com/fiduswriter/diffDOM/issues/90
function filterCancelingOutDiffs(originalDiffActions) { function filterCancelingOutDiffs(originalDiffActions: IDiff[]): IDiff[] {
const diffActions = originalDiffActions.slice(); const diffActions = originalDiffActions.slice();
for (let i = 0; i < diffActions.length; ++i) { for (let i = 0; i < diffActions.length; ++i) {
@ -252,7 +254,7 @@ function filterCancelingOutDiffs(originalDiffActions) {
* @param {object} editContent the content for the edit message * @param {object} editContent the content for the edit message
* @return {object} a react element similar to what `bodyToHtml` returns * @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 // wrap the body in a div, DiffDOM needs a root element
const originalBody = `<div>${getSanitizedHtmlBody(originalContent)}</div>`; const originalBody = `<div>${getSanitizedHtmlBody(originalContent)}</div>`;
const editBody = `<div>${getSanitizedHtmlBody(editContent)}</div>`; const editBody = `<div>${getSanitizedHtmlBody(editContent)}</div>`;

View file

@ -14,13 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
export default class PinningUtils { export default class PinningUtils {
/** /**
* Determines if the given event may be pinned. * Determines if the given event may be pinned.
* @param {MatrixEvent} event The event to check. * @param {MatrixEvent} event The event to check.
* @return {boolean} True if the event may be pinned, false otherwise. * @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) return false;
if (event.getType() !== "m.room.message") return false; if (event.getType() !== "m.room.message") return false;
if (event.isRedacted()) 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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. limitations under the License.
*/ */
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
/** /**
* Given MatrixEvent containing receipts, return the first * Given MatrixEvent containing receipts, return the first
* read receipt from the given user ID, or null if no such * 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 * @param {string} userId A user ID
* @returns {Object} Read receipt * @returns {Object} Read receipt
*/ */
export function findReadReceiptFromUserId(receiptEvent, userId) { export function findReadReceiptFromUserId(receiptEvent: MatrixEvent, userId: string): object | null {
const receiptKeys = Object.keys(receiptEvent.getContent()); const receiptKeys = Object.keys(receiptEvent.getContent());
for (let i = 0; i < receiptKeys.length; ++i) { for (let i = 0; i < receiptKeys.length; ++i) {
const rcpt = receiptEvent.getContent()[receiptKeys[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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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. * Fires when the middle panel has been resized by a pixel.
* @event module:utils~ResizeNotifier#"middlePanelResizedNoisy" * @event module:utils~ResizeNotifier#"middlePanelResizedNoisy"
*/ */
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { throttle } from "lodash"; import { throttle } from "lodash";
export default class ResizeNotifier extends EventEmitter { export default class ResizeNotifier extends EventEmitter {
constructor() { private _isResizing = false;
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;
}
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; return this._isResizing;
} }
startResizing() { public startResizing() {
this._isResizing = true; this._isResizing = true;
this.emit("isResizing", true); this.emit("isResizing", true);
} }
stopResizing() { public stopResizing() {
this._isResizing = false; this._isResizing = false;
this.emit("isResizing", false); this.emit("isResizing", false);
} }
_noisyMiddlePanel() { private noisyMiddlePanel() {
this.emit("middlePanelResizedNoisy"); this.emit("middlePanelResizedNoisy");
} }
_updateMiddlePanel() { private updateMiddlePanel() {
this._throttledMiddlePanel(); this.throttledMiddlePanel();
this._noisyMiddlePanel(); this.noisyMiddlePanel();
} }
// can be called in quick succession // can be called in quick succession
notifyLeftHandleResized() { public notifyLeftHandleResized() {
// don't emit event for own region // don't emit event for own region
this._updateMiddlePanel(); this.updateMiddlePanel();
} }
// can be called in quick succession // can be called in quick succession
notifyRightHandleResized() { public notifyRightHandleResized() {
this._updateMiddlePanel(); this.updateMiddlePanel();
} }
notifyTimelineHeightChanged() { public notifyTimelineHeightChanged() {
this._updateMiddlePanel(); this.updateMiddlePanel();
} }
// can be called in quick succession // can be called in quick succession
notifyWindowResized() { public notifyWindowResized() {
this._updateMiddlePanel(); this.updateMiddlePanel();
} }
} }

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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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. limitations under the License.
*/ */
import {createClient} from "matrix-js-sdk/src/matrix"; import { createClient, ICreateClientOpts } from "matrix-js-sdk/src/matrix";
import {IndexedDBCryptoStore} from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store"; import { IndexedDBCryptoStore } from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store";
import {WebStorageSessionStore} from "matrix-js-sdk/src/store/session/webstorage"; import { WebStorageSessionStore } from "matrix-js-sdk/src/store/session/webstorage";
import {IndexedDBStore} from "matrix-js-sdk/src/store/indexeddb"; import { IndexedDBStore } from "matrix-js-sdk/src/store/indexeddb";
const localStorage = window.localStorage; const localStorage = window.localStorage;
@ -41,8 +41,8 @@ try {
* *
* @returns {MatrixClient} the newly-created MatrixClient * @returns {MatrixClient} the newly-created MatrixClient
*/ */
export default function createMatrixClient(opts) { export default function createMatrixClient(opts: ICreateClientOpts) {
const storeOpts = { const storeOpts: Partial<ICreateClientOpts> = {
useAuthorizationHeader: true, useAuthorizationHeader: true,
}; };
@ -65,9 +65,10 @@ export default function createMatrixClient(opts) {
); );
} }
opts = Object.assign(storeOpts, opts); return createClient({
...storeOpts,
return createClient(opts); ...opts,
});
} }
createMatrixClient.indexedDbWorkerScript = null; createMatrixClient.indexedDbWorkerScript = null;

View file

@ -1486,6 +1486,11 @@
resolved "https://registry.yarnpkg.com/@types/counterpart/-/counterpart-0.18.1.tgz#b1b784d9e54d9879f0a8cb12f2caedab65430fe8" resolved "https://registry.yarnpkg.com/@types/counterpart/-/counterpart-0.18.1.tgz#b1b784d9e54d9879f0a8cb12f2caedab65430fe8"
integrity sha512-PRuFlBBkvdDOtxlIASzTmkEFar+S66Ek48NVVTWMUjtJAdn5vyMSN8y6IZIoIymGpR36q2nZbIYazBWyFxL+IQ== integrity sha512-PRuFlBBkvdDOtxlIASzTmkEFar+S66Ek48NVVTWMUjtJAdn5vyMSN8y6IZIoIymGpR36q2nZbIYazBWyFxL+IQ==
"@types/diff-match-patch@^1.0.32":
version "1.0.32"
resolved "https://registry.yarnpkg.com/@types/diff-match-patch/-/diff-match-patch-1.0.32.tgz#d9c3b8c914aa8229485351db4865328337a3d09f"
integrity sha512-bPYT5ECFiblzsVzyURaNhljBH2Gh1t9LowgUwciMrNAhFewLkHT2H0Mto07Y4/3KCOGZHRQll3CTtQZ0X11D/A==
"@types/events@^3.0.0": "@types/events@^3.0.0":
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"