Merge branch 'develop' into t3chguy/dedup-icons

This commit is contained in:
Michael Telatynski
2024-10-08 10:46:33 +01:00
committed by GitHub
55 changed files with 207 additions and 306 deletions
+1 -1
View File
@@ -74,7 +74,7 @@
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
"@matrix-org/analytics-events": "^0.26.0", "@matrix-org/analytics-events": "^0.26.0",
"@matrix-org/emojibase-bindings": "^1.1.2", "@matrix-org/emojibase-bindings": "^1.1.2",
"@matrix-org/matrix-wysiwyg": "2.37.9", "@vector-im/matrix-wysiwyg": "2.37.13",
"@matrix-org/react-sdk-module-api": "^2.4.0", "@matrix-org/react-sdk-module-api": "^2.4.0",
"@matrix-org/spec": "^1.7.0", "@matrix-org/spec": "^1.7.0",
"@sentry/browser": "^8.0.0", "@sentry/browser": "^8.0.0",
+1 -18
View File
@@ -85,9 +85,7 @@ export default class DeviceListener {
public start(matrixClient: MatrixClient): void { public start(matrixClient: MatrixClient): void {
this.running = true; this.running = true;
this.client = matrixClient; this.client = matrixClient;
this.client.on(CryptoEvent.WillUpdateDevices, this.onWillUpdateDevices);
this.client.on(CryptoEvent.DevicesUpdated, this.onDevicesUpdated); this.client.on(CryptoEvent.DevicesUpdated, this.onDevicesUpdated);
this.client.on(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
this.client.on(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged); this.client.on(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged);
this.client.on(CryptoEvent.KeysChanged, this.onCrossSingingKeysChanged); this.client.on(CryptoEvent.KeysChanged, this.onCrossSingingKeysChanged);
this.client.on(ClientEvent.AccountData, this.onAccountData); this.client.on(ClientEvent.AccountData, this.onAccountData);
@@ -109,9 +107,7 @@ export default class DeviceListener {
public stop(): void { public stop(): void {
this.running = false; this.running = false;
if (this.client) { if (this.client) {
this.client.removeListener(CryptoEvent.WillUpdateDevices, this.onWillUpdateDevices);
this.client.removeListener(CryptoEvent.DevicesUpdated, this.onDevicesUpdated); this.client.removeListener(CryptoEvent.DevicesUpdated, this.onDevicesUpdated);
this.client.removeListener(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
this.client.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged); this.client.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged);
this.client.removeListener(CryptoEvent.KeysChanged, this.onCrossSingingKeysChanged); this.client.removeListener(CryptoEvent.KeysChanged, this.onCrossSingingKeysChanged);
this.client.removeListener(ClientEvent.AccountData, this.onAccountData); this.client.removeListener(ClientEvent.AccountData, this.onAccountData);
@@ -170,7 +166,7 @@ export default class DeviceListener {
return await getUserDeviceIds(cli, cli.getSafeUserId()); return await getUserDeviceIds(cli, cli.getSafeUserId());
} }
private onWillUpdateDevices = async (users: string[], initialFetch?: boolean): Promise<void> => { private onDevicesUpdated = async (users: string[], initialFetch?: boolean): Promise<void> => {
if (!this.client) return; if (!this.client) return;
// If we didn't know about *any* devices before (ie. it's fresh login), // If we didn't know about *any* devices before (ie. it's fresh login),
// then they are all pre-existing devices, so ignore this and set the // then they are all pre-existing devices, so ignore this and set the
@@ -180,19 +176,6 @@ export default class DeviceListener {
const myUserId = this.client.getSafeUserId(); const myUserId = this.client.getSafeUserId();
if (users.includes(myUserId)) await this.ensureDeviceIdsAtStartPopulated(); if (users.includes(myUserId)) await this.ensureDeviceIdsAtStartPopulated();
// No need to do a recheck here: we just need to get a snapshot of our devices
// before we download any new ones.
};
private onDevicesUpdated = (users: string[]): void => {
if (!this.client) return;
if (!users.includes(this.client.getSafeUserId())) return;
this.recheck();
};
private onDeviceVerificationChanged = (userId: string): void => {
if (!this.client) return;
if (userId !== this.client.getUserId()) return;
this.recheck(); this.recheck();
}; };
+2
View File
@@ -43,6 +43,7 @@ import { formatList } from "./utils/FormattingUtils";
import SdkConfig from "./SdkConfig"; import SdkConfig from "./SdkConfig";
import { Features } from "./settings/Settings"; import { Features } from "./settings/Settings";
import { setDeviceIsolationMode } from "./settings/controllers/DeviceIsolationModeController.ts"; import { setDeviceIsolationMode } from "./settings/controllers/DeviceIsolationModeController.ts";
import { ReadyWatchingStore } from "./stores/ReadyWatchingStore.ts";
export interface IMatrixClientCreds { export interface IMatrixClientCreds {
homeserverUrl: string; homeserverUrl: string;
@@ -309,6 +310,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
MatrixActionCreators.start(this.matrixClient); MatrixActionCreators.start(this.matrixClient);
MatrixClientBackedSettingsHandler.matrixClient = this.matrixClient; MatrixClientBackedSettingsHandler.matrixClient = this.matrixClient;
MatrixClientBackedController.matrixClient = this.matrixClient; MatrixClientBackedController.matrixClient = this.matrixClient;
ReadyWatchingStore.matrixClient = this.matrixClient;
return opts; return opts;
} }
-17
View File
@@ -82,7 +82,6 @@ import Spinner from "../views/elements/Spinner";
import QuestionDialog from "../views/dialogs/QuestionDialog"; import QuestionDialog from "../views/dialogs/QuestionDialog";
import UserSettingsDialog from "../views/dialogs/UserSettingsDialog"; import UserSettingsDialog from "../views/dialogs/UserSettingsDialog";
import CreateRoomDialog from "../views/dialogs/CreateRoomDialog"; import CreateRoomDialog from "../views/dialogs/CreateRoomDialog";
import KeySignatureUploadFailedDialog from "../views/dialogs/KeySignatureUploadFailedDialog";
import IncomingSasDialog from "../views/dialogs/IncomingSasDialog"; import IncomingSasDialog from "../views/dialogs/IncomingSasDialog";
import CompleteSecurity from "./auth/CompleteSecurity"; import CompleteSecurity from "./auth/CompleteSecurity";
import Welcome from "../views/auth/Welcome"; import Welcome from "../views/auth/Welcome";
@@ -1629,18 +1628,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
room.setBlacklistUnverifiedDevices(blacklistEnabled); room.setBlacklistUnverifiedDevices(blacklistEnabled);
} }
}); });
cli.on(CryptoEvent.Warning, (type) => {
switch (type) {
case "CRYPTO_WARNING_OLD_VERSION_DETECTED":
Modal.createDialog(ErrorDialog, {
title: _t("encryption|old_version_detected_title"),
description: _t("encryption|old_version_detected_description", {
brand: SdkConfig.get().brand,
}),
});
break;
}
});
cli.on(CryptoEvent.KeyBackupFailed, async (errcode): Promise<void> => { cli.on(CryptoEvent.KeyBackupFailed, async (errcode): Promise<void> => {
let haveNewVersion: boolean | undefined; let haveNewVersion: boolean | undefined;
let newVersionInfo: KeyBackupInfo | null = null; let newVersionInfo: KeyBackupInfo | null = null;
@@ -1674,10 +1661,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
}); });
cli.on(CryptoEvent.KeySignatureUploadFailure, (failures, source, continuation) => {
Modal.createDialog(KeySignatureUploadFailedDialog, { failures, source, continuation });
});
cli.on(CryptoEvent.VerificationRequestReceived, (request) => { cli.on(CryptoEvent.VerificationRequestReceived, (request) => {
if (request.verifier) { if (request.verifier) {
Modal.createDialog( Modal.createDialog(
-10
View File
@@ -428,7 +428,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
context.client.on(RoomStateEvent.Update, this.onRoomStateUpdate); context.client.on(RoomStateEvent.Update, this.onRoomStateUpdate);
context.client.on(RoomEvent.MyMembership, this.onMyMembership); context.client.on(RoomEvent.MyMembership, this.onMyMembership);
context.client.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus); context.client.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
context.client.on(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
context.client.on(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged); context.client.on(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
context.client.on(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged); context.client.on(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
context.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted); context.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
@@ -974,7 +973,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.context.client.removeListener(RoomEvent.MyMembership, this.onMyMembership); this.context.client.removeListener(RoomEvent.MyMembership, this.onMyMembership);
this.context.client.removeListener(RoomStateEvent.Update, this.onRoomStateUpdate); this.context.client.removeListener(RoomStateEvent.Update, this.onRoomStateUpdate);
this.context.client.removeListener(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus); this.context.client.removeListener(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
this.context.client.removeListener(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
this.context.client.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged); this.context.client.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
this.context.client.removeListener(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged); this.context.client.removeListener(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
this.context.client.removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted); this.context.client.removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted);
@@ -1438,14 +1436,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
); );
}; };
private onDeviceVerificationChanged = (userId: string): void => {
const room = this.state.room;
if (!room?.currentState.getMember(userId)) {
return;
}
this.updateE2EStatus(room);
};
private onUserVerificationChanged = (userId: string): void => { private onUserVerificationChanged = (userId: string): void => {
const room = this.state.room; const room = this.state.room;
if (!room || !room.currentState.getMember(userId)) { if (!room || !room.currentState.getMember(userId)) {
@@ -1,117 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import React, { useState, useCallback, useRef } from "react";
import { _t } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
import Spinner from "../elements/Spinner";
interface IProps {
failures: Record<
string,
Record<
string,
{
errcode: string;
error: string;
}
>
>;
source: string;
continuation: (opts: { shouldEmit: boolean }) => Promise<void>;
onFinished(): void;
}
const KeySignatureUploadFailedDialog: React.FC<IProps> = ({ failures, source, continuation, onFinished }) => {
const RETRIES = 2;
const [retry, setRetry] = useState(RETRIES);
const [cancelled, setCancelled] = useState(false);
const [retrying, setRetrying] = useState(false);
const [success, setSuccess] = useState(false);
const onCancel = useRef(onFinished);
const causes = new Map([
["_afterCrossSigningLocalKeyChange", _t("encryption|key_signature_upload_failed_master_key_signature")],
["checkOwnCrossSigningTrust", _t("encryption|key_signature_upload_failed_cross_signing_key_signature")],
["setDeviceVerification", _t("encryption|key_signature_upload_failed_device_cross_signing_key_signature")],
]);
const defaultCause = _t("encryption|key_signature_upload_failed_key_signature");
const onRetry = useCallback(async (): Promise<void> => {
try {
setRetrying(true);
const cancel = new Promise((resolve, reject) => {
onCancel.current = reject;
}).finally(() => {
setCancelled(true);
});
await Promise.race([continuation({ shouldEmit: false }), cancel]);
setSuccess(true);
} catch (e) {
setRetry((r) => r - 1);
} finally {
onCancel.current = onFinished;
setRetrying(false);
}
}, [continuation, onFinished]);
let body;
if (!success && !cancelled && retry > 0) {
const reason = causes.get(source) || defaultCause;
const brand = SdkConfig.get().brand;
body = (
<div>
<p>{_t("encryption|key_signature_upload_failed_body", { brand })}</p>
<p>{reason}</p>
{retrying && <Spinner />}
<pre>{JSON.stringify(failures, null, 2)}</pre>
<DialogButtons
primaryButton="Retry"
hasCancel={true}
onPrimaryButtonClick={onRetry}
onCancel={onCancel.current}
primaryDisabled={retrying}
/>
</div>
);
} else {
let text = _t("encryption|key_signature_upload_completed");
if (!success) {
text = cancelled
? _t("encryption|key_signature_upload_cancelled")
: _t("encryption|key_signature_upload_failed");
}
body = (
<div>
<span>{text}</span>
<DialogButtons primaryButton={_t("action|ok")} hasCancel={false} onPrimaryButtonClick={onFinished} />
</div>
);
}
return (
<BaseDialog
title={
success
? _t("encryption|key_signature_upload_success_title")
: _t("encryption|key_signature_upload_failed_title")
}
fixedWidth={false}
onFinished={() => {}}
>
{body}
</BaseDialog>
);
};
export default KeySignatureUploadFailedDialog;
+37 -20
View File
@@ -190,25 +190,42 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
} }
} }
return BodyType ? ( const hasCaption =
<BodyType [MsgType.Image, MsgType.File, MsgType.Audio, MsgType.Video].includes(msgtype as MsgType) &&
ref={this.body} content.filename &&
mxEvent={this.props.mxEvent} content.filename !== content.body;
highlights={this.props.highlights} const bodyProps: IBodyProps = {
highlightLink={this.props.highlightLink} ref: this.body,
showUrlPreview={this.props.showUrlPreview} mxEvent: this.props.mxEvent,
forExport={this.props.forExport} highlights: this.props.highlights,
maxImageHeight={this.props.maxImageHeight} highlightLink: this.props.highlightLink,
replacingEventId={this.props.replacingEventId} showUrlPreview: this.props.showUrlPreview,
editState={this.props.editState} forExport: this.props.forExport,
onHeightChanged={this.props.onHeightChanged} maxImageHeight: this.props.maxImageHeight,
onMessageAllowed={this.onTileUpdate} replacingEventId: this.props.replacingEventId,
permalinkCreator={this.props.permalinkCreator} editState: this.props.editState,
mediaEventHelper={this.mediaHelper} onHeightChanged: this.props.onHeightChanged,
getRelationsForEvent={this.props.getRelationsForEvent} onMessageAllowed: this.onTileUpdate,
isSeeingThroughMessageHiddenForModeration={this.props.isSeeingThroughMessageHiddenForModeration} permalinkCreator: this.props.permalinkCreator,
inhibitInteraction={this.props.inhibitInteraction} mediaEventHelper: this.mediaHelper,
/> getRelationsForEvent: this.props.getRelationsForEvent,
) : null; isSeeingThroughMessageHiddenForModeration: this.props.isSeeingThroughMessageHiddenForModeration,
inhibitInteraction: this.props.inhibitInteraction,
};
if (hasCaption) {
return <CaptionBody {...bodyProps} WrappedBodyType={BodyType} />;
}
return BodyType ? <BodyType {...bodyProps} /> : null;
} }
} }
const CaptionBody: React.FunctionComponent<IBodyProps & { WrappedBodyType: React.ComponentType<IBodyProps> }> = ({
WrappedBodyType,
...props
}) => (
<div className="mx_EventTile_content">
<WrappedBodyType {...props} />
<TextualBody {...{ ...props, ref: undefined }} />
</div>
);
@@ -556,6 +556,9 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
const content = mxEvent.getContent(); const content = mxEvent.getContent();
const isNotice = content.msgtype === MsgType.Notice; const isNotice = content.msgtype === MsgType.Notice;
const isEmote = content.msgtype === MsgType.Emote; const isEmote = content.msgtype === MsgType.Emote;
const isCaption = [MsgType.Image, MsgType.File, MsgType.Audio, MsgType.Video].includes(
content.msgtype as MsgType,
);
const willHaveWrapper = const willHaveWrapper =
this.props.replacingEventId || this.props.isSeeingThroughMessageHiddenForModeration || isEmote; this.props.replacingEventId || this.props.isSeeingThroughMessageHiddenForModeration || isEmote;
@@ -635,6 +638,14 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
</div> </div>
); );
} }
if (isCaption) {
return (
<div className="mx_MTextBody mx_EventTile_caption" onClick={this.onBodyLinkClick}>
{body}
{widgets}
</div>
);
}
return ( return (
<div className="mx_MTextBody mx_EventTile_content" onClick={this.onBodyLinkClick}> <div className="mx_MTextBody mx_EventTile_content" onClick={this.onBodyLinkClick}>
{body} {body}
@@ -1381,22 +1381,16 @@ export const useDevices = (userId: string): IDevice[] | undefined | null => {
if (!users.includes(userId)) return; if (!users.includes(userId)) return;
updateDevices(); updateDevices();
}; };
const onDeviceVerificationChanged = (_userId: string, deviceId: string): void => {
if (_userId !== userId) return;
updateDevices();
};
const onUserTrustStatusChanged = (_userId: string, trustLevel: UserVerificationStatus): void => { const onUserTrustStatusChanged = (_userId: string, trustLevel: UserVerificationStatus): void => {
if (_userId !== userId) return; if (_userId !== userId) return;
updateDevices(); updateDevices();
}; };
cli.on(CryptoEvent.DevicesUpdated, onDevicesUpdated); cli.on(CryptoEvent.DevicesUpdated, onDevicesUpdated);
cli.on(CryptoEvent.DeviceVerificationChanged, onDeviceVerificationChanged);
cli.on(CryptoEvent.UserTrustStatusChanged, onUserTrustStatusChanged); cli.on(CryptoEvent.UserTrustStatusChanged, onUserTrustStatusChanged);
// Handle being unmounted // Handle being unmounted
return () => { return () => {
cancel = true; cancel = true;
cli.removeListener(CryptoEvent.DevicesUpdated, onDevicesUpdated); cli.removeListener(CryptoEvent.DevicesUpdated, onDevicesUpdated);
cli.removeListener(CryptoEvent.DeviceVerificationChanged, onDeviceVerificationChanged);
cli.removeListener(CryptoEvent.UserTrustStatusChanged, onUserTrustStatusChanged); cli.removeListener(CryptoEvent.UserTrustStatusChanged, onUserTrustStatusChanged);
}; };
}, [cli, userId]); }, [cli, userId]);
-8
View File
@@ -385,7 +385,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
this.suppressReadReceiptAnimation = false; this.suppressReadReceiptAnimation = false;
const client = MatrixClientPeg.safeGet(); const client = MatrixClientPeg.safeGet();
if (!this.props.forExport) { if (!this.props.forExport) {
client.on(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
client.on(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged); client.on(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
this.props.mxEvent.on(MatrixEventEvent.Decrypted, this.onDecrypted); this.props.mxEvent.on(MatrixEventEvent.Decrypted, this.onDecrypted);
this.props.mxEvent.on(MatrixEventEvent.Replaced, this.onReplaced); this.props.mxEvent.on(MatrixEventEvent.Replaced, this.onReplaced);
@@ -425,7 +424,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
public componentWillUnmount(): void { public componentWillUnmount(): void {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (client) { if (client) {
client.removeListener(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
client.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged); client.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
client.removeListener(RoomEvent.Receipt, this.onRoomReceipt); client.removeListener(RoomEvent.Receipt, this.onRoomReceipt);
const room = client.getRoom(this.props.mxEvent.getRoomId()); const room = client.getRoom(this.props.mxEvent.getRoomId());
@@ -564,12 +562,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
this.forceUpdate(this.props.onHeightChanged); this.forceUpdate(this.props.onHeightChanged);
}; };
private onDeviceVerificationChanged = (userId: string, device: string): void => {
if (userId === this.props.mxEvent.getSender()) {
this.verifyEvent();
}
};
private onUserVerificationChanged = (userId: string, _trustStatus: UserVerificationStatus): void => { private onUserVerificationChanged = (userId: string, _trustStatus: UserVerificationStatus): void => {
if (userId === this.props.mxEvent.getSender()) { if (userId === this.props.mxEvent.getSender()) {
this.verifyEvent(); this.verifyEvent();
@@ -9,7 +9,6 @@ Please see LICENSE files in the repository root for full details.
import React from "react"; import React from "react";
import { RoomMember, RoomStateEvent, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix"; import { RoomMember, RoomStateEvent, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
import { CryptoEvent } from "matrix-js-sdk/src/crypto"; import { CryptoEvent } from "matrix-js-sdk/src/crypto";
import { UserVerificationStatus } from "matrix-js-sdk/src/crypto-api"; import { UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
@@ -62,7 +61,6 @@ export default class MemberTile extends React.Component<IProps, IState> {
}); });
if (isRoomEncrypted) { if (isRoomEncrypted) {
cli.on(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged); cli.on(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged);
cli.on(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
this.updateE2EStatus(); this.updateE2EStatus();
} else { } else {
// Listen for room to become encrypted // Listen for room to become encrypted
@@ -77,7 +75,6 @@ export default class MemberTile extends React.Component<IProps, IState> {
if (cli) { if (cli) {
cli.removeListener(RoomStateEvent.Events, this.onRoomStateEvents); cli.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
cli.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged); cli.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged);
cli.removeListener(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
} }
} }
@@ -100,11 +97,6 @@ export default class MemberTile extends React.Component<IProps, IState> {
this.updateE2EStatus(); this.updateE2EStatus();
}; };
private onDeviceVerificationChanged = (userId: string, deviceId: string, deviceInfo: DeviceInfo): void => {
if (userId !== this.props.member.userId) return;
this.updateE2EStatus();
};
private async updateE2EStatus(): Promise<void> { private async updateE2EStatus(): Promise<void> {
const cli = MatrixClientPeg.safeGet(); const cli = MatrixClientPeg.safeGet();
const { userId } = this.props.member; const { userId } = this.props.member;
@@ -10,7 +10,7 @@ import React, { ComponentProps, lazy, Suspense } from "react";
import { ISendEventResponse } from "matrix-js-sdk/src/matrix"; import { ISendEventResponse } from "matrix-js-sdk/src/matrix";
// we need to import the types for TS, but do not import the sendMessage // we need to import the types for TS, but do not import the sendMessage
// function to avoid importing from "@matrix-org/matrix-wysiwyg" // function to avoid importing from "@vector-im/matrix-wysiwyg"
import { SendMessageParams } from "./utils/message"; import { SendMessageParams } from "./utils/message";
import { retry } from "../../../../utils/promise"; import { retry } from "../../../../utils/promise";
@@ -55,7 +55,7 @@ export const dynamicImportConversionFunctions = async (): Promise<{
*/ */
plainToRich(plain: string, inMessageFormat: boolean): Promise<string>; plainToRich(plain: string, inMessageFormat: boolean): Promise<string>;
}> => { }> => {
const { richToPlain, plainToRich } = await retry(() => import("@matrix-org/matrix-wysiwyg"), RETRY_COUNT); const { richToPlain, plainToRich } = await retry(() => import("@vector-im/matrix-wysiwyg"), RETRY_COUNT);
return { richToPlain, plainToRich }; return { richToPlain, plainToRich };
}; };
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/ */
import React, { MouseEventHandler, ReactNode } from "react"; import React, { MouseEventHandler, ReactNode } from "react";
import { FormattingFunctions, AllActionStates, ActionState } from "@matrix-org/matrix-wysiwyg"; import { FormattingFunctions, AllActionStates, ActionState } from "@vector-im/matrix-wysiwyg";
import classNames from "classnames"; import classNames from "classnames";
import BoldIcon from "@vector-im/compound-design-tokens/assets/web/icons/bold"; import BoldIcon from "@vector-im/compound-design-tokens/assets/web/icons/bold";
import BulletedListIcon from "@vector-im/compound-design-tokens/assets/web/icons/list-bulleted"; import BulletedListIcon from "@vector-im/compound-design-tokens/assets/web/icons/list-bulleted";
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import { FormattingFunctions } from "@matrix-org/matrix-wysiwyg"; import { FormattingFunctions } from "@vector-im/matrix-wysiwyg";
import React, { ChangeEvent, useState } from "react"; import React, { ChangeEvent, useState } from "react";
import { _t } from "../../../../../languageHandler"; import { _t } from "../../../../../languageHandler";
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/ */
import React, { ForwardedRef, forwardRef, FunctionComponent } from "react"; import React, { ForwardedRef, forwardRef, FunctionComponent } from "react";
import { FormattingFunctions, MappedSuggestion } from "@matrix-org/matrix-wysiwyg"; import { FormattingFunctions, MappedSuggestion } from "@vector-im/matrix-wysiwyg";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { useRoomContext } from "../../../../../contexts/RoomContext"; import { useRoomContext } from "../../../../../contexts/RoomContext";
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
import React, { memo, MutableRefObject, ReactNode, useEffect, useMemo, useRef } from "react"; import React, { memo, MutableRefObject, ReactNode, useEffect, useMemo, useRef } from "react";
import { IEventRelation } from "matrix-js-sdk/src/matrix"; import { IEventRelation } from "matrix-js-sdk/src/matrix";
import { EMOTICON_TO_EMOJI } from "@matrix-org/emojibase-bindings"; import { EMOTICON_TO_EMOJI } from "@matrix-org/emojibase-bindings";
import { useWysiwyg, FormattingFunctions } from "@matrix-org/matrix-wysiwyg"; import { useWysiwyg, FormattingFunctions } from "@vector-im/matrix-wysiwyg";
import classNames from "classnames"; import classNames from "classnames";
import Autocomplete from "../../Autocomplete"; import Autocomplete from "../../Autocomplete";
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import { Wysiwyg, WysiwygEvent } from "@matrix-org/matrix-wysiwyg"; import { Wysiwyg, WysiwygEvent } from "@vector-im/matrix-wysiwyg";
import { useCallback } from "react"; import { useCallback } from "react";
import { IEventRelation, MatrixClient } from "matrix-js-sdk/src/matrix"; import { IEventRelation, MatrixClient } from "matrix-js-sdk/src/matrix";
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/ */
import { KeyboardEvent, RefObject, SyntheticEvent, useCallback, useRef, useState } from "react"; import { KeyboardEvent, RefObject, SyntheticEvent, useCallback, useRef, useState } from "react";
import { AllowedMentionAttributes, MappedSuggestion } from "@matrix-org/matrix-wysiwyg"; import { AllowedMentionAttributes, MappedSuggestion } from "@vector-im/matrix-wysiwyg";
import { IEventRelation } from "matrix-js-sdk/src/matrix"; import { IEventRelation } from "matrix-js-sdk/src/matrix";
import { useSettingValue } from "../../../../../hooks/useSettings"; import { useSettingValue } from "../../../../../hooks/useSettings";
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/ */
import { EMOTICON_TO_EMOJI } from "@matrix-org/emojibase-bindings"; import { EMOTICON_TO_EMOJI } from "@matrix-org/emojibase-bindings";
import { AllowedMentionAttributes, MappedSuggestion } from "@matrix-org/matrix-wysiwyg"; import { AllowedMentionAttributes, MappedSuggestion } from "@vector-im/matrix-wysiwyg";
import { SyntheticEvent, useState, SetStateAction } from "react"; import { SyntheticEvent, useState, SetStateAction } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import { MutableRefObject, RefObject } from "react"; import { MutableRefObject, RefObject } from "react";
import { IEventRelation, MatrixClient } from "matrix-js-sdk/src/matrix"; import { IEventRelation, MatrixClient } from "matrix-js-sdk/src/matrix";
import { WysiwygEvent } from "@matrix-org/matrix-wysiwyg"; import { WysiwygEvent } from "@vector-im/matrix-wysiwyg";
import { TimelineRenderingType } from "../../../../../contexts/RoomContext"; import { TimelineRenderingType } from "../../../../../contexts/RoomContext";
import { IRoomState } from "../../../../structures/RoomView"; import { IRoomState } from "../../../../structures/RoomView";
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import { AllowedMentionAttributes, MappedSuggestion } from "@matrix-org/matrix-wysiwyg"; import { AllowedMentionAttributes, MappedSuggestion } from "@vector-im/matrix-wysiwyg";
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix"; import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { ICompletion } from "../../../../../autocomplete/Autocompleter"; import { ICompletion } from "../../../../../autocomplete/Autocompleter";
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import { richToPlain, plainToRich } from "@matrix-org/matrix-wysiwyg"; import { richToPlain, plainToRich } from "@vector-im/matrix-wysiwyg";
import { IContent, IEventRelation, MatrixEvent, MsgType } from "matrix-js-sdk/src/matrix"; import { IContent, IEventRelation, MatrixEvent, MsgType } from "matrix-js-sdk/src/matrix";
import { ReplacementEvent, RoomMessageEventContent, RoomMessageTextEventContent } from "matrix-js-sdk/src/types"; import { ReplacementEvent, RoomMessageEventContent, RoomMessageTextEventContent } from "matrix-js-sdk/src/types";
-12
View File
@@ -904,16 +904,6 @@
"incompatible_database_disable": "Continue With Encryption Disabled", "incompatible_database_disable": "Continue With Encryption Disabled",
"incompatible_database_sign_out_description": "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this", "incompatible_database_sign_out_description": "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this",
"incompatible_database_title": "Incompatible Database", "incompatible_database_title": "Incompatible Database",
"key_signature_upload_cancelled": "Cancelled signature upload",
"key_signature_upload_completed": "Upload completed",
"key_signature_upload_failed": "Unable to upload",
"key_signature_upload_failed_body": "%(brand)s encountered an error during upload of:",
"key_signature_upload_failed_cross_signing_key_signature": "a new cross-signing key signature",
"key_signature_upload_failed_device_cross_signing_key_signature": "a device cross-signing signature",
"key_signature_upload_failed_key_signature": "a key signature",
"key_signature_upload_failed_master_key_signature": "a new master key signature",
"key_signature_upload_failed_title": "Signature upload failed",
"key_signature_upload_success_title": "Signature upload success",
"messages_not_secure": { "messages_not_secure": {
"cause_1": "Your homeserver", "cause_1": "Your homeserver",
"cause_2": "The homeserver the user you're verifying is connected to", "cause_2": "The homeserver the user you're verifying is connected to",
@@ -929,8 +919,6 @@
"warning": "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings." "warning": "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings."
}, },
"not_supported": "<not supported>", "not_supported": "<not supported>",
"old_version_detected_description": "Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.",
"old_version_detected_title": "Old cryptography data detected",
"recovery_method_removed": { "recovery_method_removed": {
"description_1": "This session has detected that your Security Phrase and key for Secure Messages have been removed.", "description_1": "This session has detected that your Security Phrase and key for Secure Messages have been removed.",
"description_2": "If you did this accidentally, you can setup Secure Messages on this session which will re-encrypt this session's message history with a new recovery method.", "description_2": "If you did this accidentally, you can setup Secure Messages on this session which will re-encrypt this session's message history with a new recovery method.",
+7 -2
View File
@@ -36,8 +36,13 @@ export abstract class AsyncStoreWithClient<T extends Object> extends AsyncStore<
})(dispatcher); })(dispatcher);
} }
public async start(): Promise<void> { protected async start(matrixClient: MatrixClient | null): Promise<void> {
await this.readyStore.start(); await this.readyStore.start(matrixClient);
}
// XXX: This method is intended only for use in tests.
public async useUnitTestClient(cli: MatrixClient): Promise<void> {
await this.readyStore.useUnitTestClient(cli);
} }
public get matrixClient(): MatrixClient | null { public get matrixClient(): MatrixClient | null {
+1 -3
View File
@@ -46,9 +46,7 @@ interface IState {
*/ */
export default class AutoRageshakeStore extends AsyncStoreWithClient<IState> { export default class AutoRageshakeStore extends AsyncStoreWithClient<IState> {
private static readonly internalInstance = (() => { private static readonly internalInstance = (() => {
const instance = new AutoRageshakeStore(); return new AutoRageshakeStore();
instance.start();
return instance;
})(); })();
private constructor() { private constructor() {
+1 -3
View File
@@ -30,9 +30,7 @@ interface IState {
export class BreadcrumbsStore extends AsyncStoreWithClient<IState> { export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
private static readonly internalInstance = (() => { private static readonly internalInstance = (() => {
const instance = new BreadcrumbsStore(); return new BreadcrumbsStore();
instance.start();
return instance;
})(); })();
private waitingRooms: { roomId: string; addedTs: number }[] = []; private waitingRooms: { roomId: string; addedTs: number }[] = [];
-1
View File
@@ -31,7 +31,6 @@ export class CallStore extends AsyncStoreWithClient<{}> {
public static get instance(): CallStore { public static get instance(): CallStore {
if (!this._instance) { if (!this._instance) {
this._instance = new CallStore(); this._instance = new CallStore();
this._instance.start();
} }
return this._instance; return this._instance;
} }
-1
View File
@@ -24,7 +24,6 @@ interface IState {
export class ModalWidgetStore extends AsyncStoreWithClient<IState> { export class ModalWidgetStore extends AsyncStoreWithClient<IState> {
private static readonly internalInstance = (() => { private static readonly internalInstance = (() => {
const instance = new ModalWidgetStore(); const instance = new ModalWidgetStore();
instance.start();
return instance; return instance;
})(); })();
private modalInstance: IHandle<typeof ModalWidgetDialog> | null = null; private modalInstance: IHandle<typeof ModalWidgetDialog> | null = null;
-1
View File
@@ -87,7 +87,6 @@ const getLocallyCreatedBeaconEventIds = (): string[] => {
export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> { export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
private static readonly internalInstance = (() => { private static readonly internalInstance = (() => {
const instance = new OwnBeaconStore(); const instance = new OwnBeaconStore();
instance.start();
return instance; return instance;
})(); })();
// users beacons, keyed by event type // users beacons, keyed by event type
-1
View File
@@ -28,7 +28,6 @@ const KEY_AVATAR_URL = "mx_profile_avatar_url";
export class OwnProfileStore extends AsyncStoreWithClient<IState> { export class OwnProfileStore extends AsyncStoreWithClient<IState> {
private static readonly internalInstance = (() => { private static readonly internalInstance = (() => {
const instance = new OwnProfileStore(); const instance = new OwnProfileStore();
instance.start();
return instance; return instance;
})(); })();
+26 -11
View File
@@ -9,27 +9,40 @@
import { MatrixClient, SyncState } from "matrix-js-sdk/src/matrix"; import { MatrixClient, SyncState } from "matrix-js-sdk/src/matrix";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { MatrixClientPeg } from "../MatrixClientPeg";
import { ActionPayload } from "../dispatcher/payloads"; import { ActionPayload } from "../dispatcher/payloads";
import { IDestroyable } from "../utils/IDestroyable"; import { IDestroyable } from "../utils/IDestroyable";
import { Action } from "../dispatcher/actions"; import { Action } from "../dispatcher/actions";
import { MatrixDispatcher } from "../dispatcher/dispatcher"; import { MatrixDispatcher } from "../dispatcher/dispatcher";
export abstract class ReadyWatchingStore extends EventEmitter implements IDestroyable { export abstract class ReadyWatchingStore extends EventEmitter implements IDestroyable {
protected matrixClient: MatrixClient | null = null; private static instances: ReadyWatchingStore[] = [];
protected _matrixClient: MatrixClient | null = null;
private dispatcherRef: string | null = null; private dispatcherRef: string | null = null;
public static set matrixClient(client: MatrixClient) {
for (const instance of ReadyWatchingStore.instances) {
instance.start(client);
}
}
public constructor(protected readonly dispatcher: MatrixDispatcher) { public constructor(protected readonly dispatcher: MatrixDispatcher) {
super(); super();
this.dispatcherRef = this.dispatcher.register(this.onAction);
} }
public async start(): Promise<void> { public get matrixClient(): MatrixClient | null {
this.dispatcherRef = this.dispatcher.register(this.onAction); return this._matrixClient;
}
// MatrixClientPeg can be undefined in tests because of circular dependencies with other stores public async start(matrixClient: MatrixClient | null): Promise<void> {
const matrixClient = MatrixClientPeg?.get(); const oldClient = this._matrixClient;
this._matrixClient = matrixClient;
if (oldClient !== matrixClient) {
await this.onNotReady();
}
if (matrixClient) { if (matrixClient) {
this.matrixClient = matrixClient;
await this.onReady(); await this.onReady();
} }
} }
@@ -38,8 +51,10 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro
return this.matrixClient; // for external readonly access return this.matrixClient; // for external readonly access
} }
public useUnitTestClient(cli: MatrixClient): void { // XXX: This method is intended only for use in tests.
this.matrixClient = cli; public async useUnitTestClient(cli: MatrixClient): Promise<void> {
this._matrixClient = cli;
await this.onReady();
} }
public destroy(): void { public destroy(): void {
@@ -74,13 +89,13 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro
if (this.matrixClient) { if (this.matrixClient) {
await this.onNotReady(); await this.onNotReady();
} }
this.matrixClient = payload.matrixClient; this._matrixClient = payload.matrixClient;
await this.onReady(); await this.onReady();
} }
} else if (payload.action === "on_client_not_viable" || payload.action === Action.OnLoggedOut) { } else if (payload.action === "on_client_not_viable" || payload.action === Action.OnLoggedOut) {
if (this.matrixClient) { if (this.matrixClient) {
await this.onNotReady(); await this.onNotReady();
this.matrixClient = null; this._matrixClient = null;
} }
} }
}; };
-1
View File
@@ -30,7 +30,6 @@ export class VoiceRecordingStore extends AsyncStoreWithClient<IState> {
public static get instance(): VoiceRecordingStore { public static get instance(): VoiceRecordingStore {
if (!this.internalInstance) { if (!this.internalInstance) {
this.internalInstance = new VoiceRecordingStore(); this.internalInstance = new VoiceRecordingStore();
this.internalInstance.start();
} }
return this.internalInstance; return this.internalInstance;
} }
-1
View File
@@ -45,7 +45,6 @@ interface IRoomWidgets {
export default class WidgetStore extends AsyncStoreWithClient<IState> { export default class WidgetStore extends AsyncStoreWithClient<IState> {
private static readonly internalInstance = (() => { private static readonly internalInstance = (() => {
const instance = new WidgetStore(); const instance = new WidgetStore();
instance.start();
return instance; return instance;
})(); })();
-1
View File
@@ -38,7 +38,6 @@ export class EchoStore extends AsyncStoreWithClient<IState> {
public static get instance(): EchoStore { public static get instance(): EchoStore {
if (!this._instance) { if (!this._instance) {
this._instance = new EchoStore(); this._instance = new EchoStore();
this._instance.start();
} }
return this._instance; return this._instance;
} }
@@ -26,7 +26,6 @@ export const UPDATE_STATUS_INDICATOR = Symbol("update-status-indicator");
export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> { export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
private static readonly internalInstance = (() => { private static readonly internalInstance = (() => {
const instance = new RoomNotificationStateStore(); const instance = new RoomNotificationStateStore();
instance.start();
return instance; return instance;
})(); })();
private roomMap = new Map<Room, RoomNotificationState>(); private roomMap = new Map<Room, RoomNotificationState>();
@@ -403,7 +403,6 @@ export default class RightPanelStore extends ReadyWatchingStore {
public static get instance(): RightPanelStore { public static get instance(): RightPanelStore {
if (!this.internalInstance) { if (!this.internalInstance) {
this.internalInstance = new RightPanelStore(); this.internalInstance = new RightPanelStore();
this.internalInstance.start();
} }
return this.internalInstance; return this.internalInstance;
} }
+1 -5
View File
@@ -124,11 +124,7 @@ const mkMessagePreview = (text: string, event: MatrixEvent): MessagePreview => {
}; };
export class MessagePreviewStore extends AsyncStoreWithClient<IState> { export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
private static readonly internalInstance = (() => { private static readonly internalInstance = (() => new MessagePreviewStore())();
const instance = new MessagePreviewStore();
instance.start();
return instance;
})();
/** /**
* @internal Public for test only * @internal Public for test only
@@ -28,7 +28,6 @@ export default class RoomListLayoutStore extends AsyncStoreWithClient<IState> {
public static get instance(): RoomListLayoutStore { public static get instance(): RoomListLayoutStore {
if (!this.internalInstance) { if (!this.internalInstance) {
this.internalInstance = new RoomListLayoutStore(); this.internalInstance = new RoomListLayoutStore();
this.internalInstance.start();
} }
return RoomListLayoutStore.internalInstance; return RoomListLayoutStore.internalInstance;
} }
-2
View File
@@ -643,11 +643,9 @@ export default class RoomListStore {
if (SettingsStore.getValue("feature_sliding_sync")) { if (SettingsStore.getValue("feature_sliding_sync")) {
logger.info("using SlidingRoomListStoreClass"); logger.info("using SlidingRoomListStoreClass");
const instance = new SlidingRoomListStoreClass(defaultDispatcher, SdkContextClass.instance); const instance = new SlidingRoomListStoreClass(defaultDispatcher, SdkContextClass.instance);
instance.start();
RoomListStore.internalInstance = instance; RoomListStore.internalInstance = instance;
} else { } else {
const instance = new RoomListStoreClass(defaultDispatcher); const instance = new RoomListStoreClass(defaultDispatcher);
instance.start();
RoomListStore.internalInstance = instance; RoomListStore.internalInstance = instance;
} }
} }
+3 -3
View File
@@ -17,6 +17,7 @@ import {
MatrixEvent, MatrixEvent,
ClientEvent, ClientEvent,
ISendEventResponse, ISendEventResponse,
MatrixClient,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types"; import { KnownMembership } from "matrix-js-sdk/src/types";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
@@ -1397,7 +1398,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
export default class SpaceStore { export default class SpaceStore {
private static readonly internalInstance = (() => { private static readonly internalInstance = (() => {
const instance = new SpaceStoreClass(); const instance = new SpaceStoreClass();
instance.start();
return instance; return instance;
})(); })();
@@ -1408,9 +1408,9 @@ export default class SpaceStore {
/** /**
* @internal for test only * @internal for test only
*/ */
public static testInstance(): SpaceStoreClass { public static testInstance(client: MatrixClient): SpaceStoreClass {
const store = new SpaceStoreClass(); const store = new SpaceStoreClass();
store.start(); store.useUnitTestClient(client);
return store; return store;
} }
} }
-1
View File
@@ -60,7 +60,6 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
public static get instance(): WidgetLayoutStore { public static get instance(): WidgetLayoutStore {
if (!this.internalInstance) { if (!this.internalInstance) {
this.internalInstance = new WidgetLayoutStore(); this.internalInstance = new WidgetLayoutStore();
this.internalInstance.start();
} }
return this.internalInstance; return this.internalInstance;
} }
@@ -27,7 +27,6 @@ export enum WidgetMessagingStoreEvent {
export class WidgetMessagingStore extends AsyncStoreWithClient<{}> { export class WidgetMessagingStore extends AsyncStoreWithClient<{}> {
private static readonly internalInstance = (() => { private static readonly internalInstance = (() => {
const instance = new WidgetMessagingStore(); const instance = new WidgetMessagingStore();
instance.start();
return instance; return instance;
})(); })();
+3 -1
View File
@@ -38,7 +38,9 @@ export function presentableTextForFile(
shortened = false, shortened = false,
): string { ): string {
let text = fallbackText; let text = fallbackText;
if (content.body?.length) { if (content.filename?.length) {
text = content.filename;
} else if (content.body?.length) {
// The content body should be the name of the file including a // The content body should be the name of the file including a
// file extension. // file extension.
text = content.body; text = content.body;
@@ -8,7 +8,10 @@ Please see LICENSE files in the repository root for full details.
import React from "react"; import React from "react";
import { render, RenderResult } from "@testing-library/react"; import { render, RenderResult } from "@testing-library/react";
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; import { MatrixClient, MatrixEvent, EventType, Room, MsgType } from "matrix-js-sdk/src/matrix";
import fetchMock from "fetch-mock-jest";
import fs from "fs";
import path from "path";
import SettingsStore from "../../../../src/settings/SettingsStore"; import SettingsStore from "../../../../src/settings/SettingsStore";
import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "../../../../src/voice-broadcast"; import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "../../../../src/voice-broadcast";
@@ -25,6 +28,26 @@ jest.mock("../../../../src/voice-broadcast/components/VoiceBroadcastBody", () =>
VoiceBroadcastBody: () => <div data-testid="voice-broadcast-body" />, VoiceBroadcastBody: () => <div data-testid="voice-broadcast-body" />,
})); }));
jest.mock("../../../../src/components/views/messages/MImageBody", () => ({
__esModule: true,
default: () => <div data-testid="image-body" />,
}));
jest.mock("../../../../src/components/views/messages/MImageReplyBody", () => ({
__esModule: true,
default: () => <div data-testid="image-reply-body" />,
}));
jest.mock("../../../../src/components/views/messages/MStickerBody", () => ({
__esModule: true,
default: () => <div data-testid="sticker-body" />,
}));
jest.mock("../../../../src/components/views/messages/TextualBody.tsx", () => ({
__esModule: true,
default: () => <div data-testid="textual-body" />,
}));
describe("MessageEvent", () => { describe("MessageEvent", () => {
let room: Room; let room: Room;
let client: MatrixClient; let client: MatrixClient;
@@ -68,4 +91,42 @@ describe("MessageEvent", () => {
result.getByTestId("voice-broadcast-body"); result.getByTestId("voice-broadcast-body");
}); });
}); });
describe("when an image with a caption is sent", () => {
let result: RenderResult;
beforeEach(() => {
event = mkEvent({
event: true,
type: EventType.RoomMessage,
user: client.getUserId()!,
room: room.roomId,
content: {
body: "caption for a test image",
format: "org.matrix.custom.html",
formatted_body: "<strong>caption for a test image</strong>",
msgtype: MsgType.Image,
filename: "image.webp",
info: {
w: 40,
h: 50,
},
url: "mxc://server/image",
},
});
result = renderMessageEvent();
});
it("should render a TextualBody and an ImageBody", () => {
fetchMock.getOnce(
"https://server/_matrix/media/v3/download/server/image",
{
body: fs.readFileSync(path.resolve(__dirname, "..", "..", "..", "images", "animated-logo.webp")),
},
{ sendAsJson: false },
);
result.getByTestId("image-body");
result.getByTestId("textual-body");
});
});
}); });
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react"; import React from "react";
import { cleanup, render, screen, waitFor } from "@testing-library/react"; import { cleanup, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import { ActionState, ActionTypes, AllActionStates, FormattingFunctions } from "@matrix-org/matrix-wysiwyg"; import { ActionState, ActionTypes, AllActionStates, FormattingFunctions } from "@vector-im/matrix-wysiwyg";
import { FormattingButtons } from "../../../../../../src/components/views/rooms/wysiwyg_composer/components/FormattingButtons"; import { FormattingButtons } from "../../../../../../src/components/views/rooms/wysiwyg_composer/components/FormattingButtons";
import * as LinkModal from "../../../../../../src/components/views/rooms/wysiwyg_composer/components/LinkModal"; import * as LinkModal from "../../../../../../src/components/views/rooms/wysiwyg_composer/components/LinkModal";
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import { FormattingFunctions } from "@matrix-org/matrix-wysiwyg"; import { FormattingFunctions } from "@vector-im/matrix-wysiwyg";
import { render, screen, waitFor } from "@testing-library/react"; import { render, screen, waitFor } from "@testing-library/react";
import React from "react"; import React from "react";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
+1 -1
View File
@@ -47,7 +47,7 @@ describe("AutoRageshakeStore", () => {
// @ts-ignore bypass private ctor for tests // @ts-ignore bypass private ctor for tests
autoRageshakeStore = new AutoRageshakeStore(); autoRageshakeStore = new AutoRageshakeStore();
autoRageshakeStore.start(); autoRageshakeStore.useUnitTestClient(client);
utdEvent = mkEvent({ utdEvent = mkEvent({
event: true, event: true,
+3 -3
View File
@@ -40,7 +40,7 @@ describe("OwnProfileStore", () => {
displayname: "Display Name", displayname: "Display Name",
avatar_url: "mxc://example.com/abc123", avatar_url: "mxc://example.com/abc123",
}); });
await ownProfileStore.start(); await ownProfileStore.useUnitTestClient(client);
expect(onUpdate).toHaveBeenCalled(); expect(onUpdate).toHaveBeenCalled();
expect(ownProfileStore.displayName).toBe("Display Name"); expect(ownProfileStore.displayName).toBe("Display Name");
@@ -54,7 +54,7 @@ describe("OwnProfileStore", () => {
errcode: "M_NOT_FOUND", errcode: "M_NOT_FOUND",
}), }),
); );
await ownProfileStore.start(); await ownProfileStore.useUnitTestClient(client);
expect(onUpdate).toHaveBeenCalled(); expect(onUpdate).toHaveBeenCalled();
expect(ownProfileStore.displayName).toBe(client.getSafeUserId()); expect(ownProfileStore.displayName).toBe(client.getSafeUserId());
@@ -69,7 +69,7 @@ describe("OwnProfileStore", () => {
}), }),
); );
try { try {
await ownProfileStore.start(); await ownProfileStore.useUnitTestClient(client);
} catch (ignore) {} } catch (ignore) {}
expect(onUpdate).not.toHaveBeenCalled(); expect(onUpdate).not.toHaveBeenCalled();
+2 -2
View File
@@ -1428,7 +1428,7 @@ describe("SpaceStore", () => {
it("passes that value in calls to getVisibleRooms during getSpaceFilteredRoomIds", () => { it("passes that value in calls to getVisibleRooms during getSpaceFilteredRoomIds", () => {
// Given a store // Given a store
const store = SpaceStore.testInstance(); const store = SpaceStore.testInstance(client);
// When we ask for filtered room ids // When we ask for filtered room ids
store.getSpaceFilteredRoomIds(MetaSpace.Home); store.getSpaceFilteredRoomIds(MetaSpace.Home);
@@ -1478,7 +1478,7 @@ describe("SpaceStore", () => {
it("passes that value in calls to getVisibleRooms during getSpaceFilteredRoomIds", () => { it("passes that value in calls to getVisibleRooms during getSpaceFilteredRoomIds", () => {
// Given a store // Given a store
const store = SpaceStore.testInstance(); const store = SpaceStore.testInstance(client);
// When we ask for filtered room ids // When we ask for filtered room ids
store.getSpaceFilteredRoomIds(MetaSpace.Home); store.getSpaceFilteredRoomIds(MetaSpace.Home);
+1 -1
View File
@@ -31,7 +31,7 @@ describe("VoiceRecordingStore", () => {
const mkStore = (): VoiceRecordingStore => { const mkStore = (): VoiceRecordingStore => {
const store = new VoiceRecordingStore(); const store = new VoiceRecordingStore();
store.start(); store.useUnitTestClient(stubClient);
return store; return store;
}; };
+4 -4
View File
@@ -167,7 +167,7 @@ describe("WidgetLayoutStore", () => {
it("should recalculate all rooms when the client is ready", async () => { it("should recalculate all rooms when the client is ready", async () => {
mocked(client.getVisibleRooms).mockReturnValue([mockRoom]); mocked(client.getVisibleRooms).mockReturnValue([mockRoom]);
await store.start(); await store.start(client);
expect(roomUpdateListener).toHaveBeenCalled(); expect(roomUpdateListener).toHaveBeenCalled();
expect(store.getContainerWidgets(mockRoom, Container.Top)).toEqual([]); expect(store.getContainerWidgets(mockRoom, Container.Top)).toEqual([]);
@@ -243,7 +243,7 @@ describe("WidgetLayoutStore", () => {
}); });
it("should copy the layout to the room", async () => { it("should copy the layout to the room", async () => {
await store.start(); await store.start(client);
store.recalculateRoom(mockRoom); store.recalculateRoom(mockRoom);
store.moveToContainer(mockRoom, mockApps[0], Container.Top); store.moveToContainer(mockRoom, mockApps[0], Container.Top);
store.copyLayoutToRoom(mockRoom); store.copyLayoutToRoom(mockRoom);
@@ -297,7 +297,7 @@ describe("WidgetLayoutStore", () => {
mocked(client.getVisibleRooms).mockReturnValue([]); mocked(client.getVisibleRooms).mockReturnValue([]);
// @ts-ignore bypass private ctor for tests // @ts-ignore bypass private ctor for tests
const store = new WidgetLayoutStore(); const store = new WidgetLayoutStore();
await store.start(); await store.start(client);
expect(client.getVisibleRooms).toHaveBeenCalledWith(false); expect(client.getVisibleRooms).toHaveBeenCalledWith(false);
}); });
}); });
@@ -314,7 +314,7 @@ describe("WidgetLayoutStore", () => {
mocked(client.getVisibleRooms).mockReturnValue([]); mocked(client.getVisibleRooms).mockReturnValue([]);
// @ts-ignore bypass private ctor for tests // @ts-ignore bypass private ctor for tests
const store = new WidgetLayoutStore(); const store = new WidgetLayoutStore();
await store.start(); await store.start(client);
expect(client.getVisibleRooms).toHaveBeenCalledWith(true); expect(client.getVisibleRooms).toHaveBeenCalledWith(true);
}); });
}); });
@@ -80,7 +80,7 @@ describe("MessagePreviewStore", () => {
mocked(client.getRoom).mockReturnValue(room); mocked(client.getRoom).mockReturnValue(room);
store = MessagePreviewStore.testInstance(); store = MessagePreviewStore.testInstance();
await store.start(); await store.useUnitTestClient(client);
await setupAsyncStoreWithClient(store, client); await setupAsyncStoreWithClient(store, client);
}); });
+10 -5
View File
@@ -29,6 +29,9 @@ import DMRoomMap from "../../../src/utils/DMRoomMap";
import { flushPromises, stubClient, upsertRoomStateEvents, mkRoom } from "../../test-utils"; import { flushPromises, stubClient, upsertRoomStateEvents, mkRoom } from "../../test-utils";
import { DEFAULT_PUSH_RULES, makePushRule } from "../../test-utils/pushRules"; import { DEFAULT_PUSH_RULES, makePushRule } from "../../test-utils/pushRules";
// Mock out the SpaceWatcher as it messes with the prefilterConditions
jest.mock("../../../src/stores/room-list/SpaceWatcher.ts");
describe("RoomListStore", () => { describe("RoomListStore", () => {
const client = stubClient(); const client = stubClient();
const newRoomId = "!roomid:example.com"; const newRoomId = "!roomid:example.com";
@@ -91,6 +94,10 @@ describe("RoomListStore", () => {
await (RoomListStore.instance as RoomListStoreClass).makeReady(client); await (RoomListStore.instance as RoomListStoreClass).makeReady(client);
}); });
beforeEach(() => {
DMRoomMap.makeShared(client);
});
it.each(OrderedDefaultTagIDs)("defaults to importance ordering for %s=", (tagId) => { it.each(OrderedDefaultTagIDs)("defaults to importance ordering for %s=", (tagId) => {
expect(RoomListStore.instance.getTagSorting(tagId)).toBe(SortAlgorithm.Recent); expect(RoomListStore.instance.getTagSorting(tagId)).toBe(SortAlgorithm.Recent);
}); });
@@ -102,11 +109,11 @@ describe("RoomListStore", () => {
function createStore(): { store: RoomListStoreClass; handleRoomUpdate: jest.Mock<any, any> } { function createStore(): { store: RoomListStoreClass; handleRoomUpdate: jest.Mock<any, any> } {
const fakeDispatcher = { register: jest.fn() } as unknown as MatrixDispatcher; const fakeDispatcher = { register: jest.fn() } as unknown as MatrixDispatcher;
const store = new RoomListStoreClass(fakeDispatcher); const store = new RoomListStoreClass(fakeDispatcher);
// @ts-ignore accessing private member to set client
store.readyStore.matrixClient = client;
const handleRoomUpdate = jest.fn(); const handleRoomUpdate = jest.fn();
// @ts-ignore accessing private member to mock it // @ts-ignore accessing private member to mock it
store.algorithm.handleRoomUpdate = handleRoomUpdate; store.algorithm.handleRoomUpdate = handleRoomUpdate;
// @ts-ignore accessing private member to set client
store.readyStore.useUnitTestClient(client);
return { store, handleRoomUpdate }; return { store, handleRoomUpdate };
} }
@@ -157,7 +164,6 @@ describe("RoomListStore", () => {
room1.updateMyMembership(KnownMembership.Join); room1.updateMyMembership(KnownMembership.Join);
room2.updateMyMembership(KnownMembership.Join); room2.updateMyMembership(KnownMembership.Join);
room3.updateMyMembership(KnownMembership.Join); room3.updateMyMembership(KnownMembership.Join);
DMRoomMap.makeShared(client);
const { store } = createStore(); const { store } = createStore();
client.getVisibleRooms = jest.fn().mockReturnValue([room1, room2, room3]); client.getVisibleRooms = jest.fn().mockReturnValue([room1, room2, room3]);
@@ -269,7 +275,6 @@ describe("RoomListStore", () => {
it("Passes the feature flag on to the client when asking for visible rooms", () => { it("Passes the feature flag on to the client when asking for visible rooms", () => {
// Given a store that we can ask for a room list // Given a store that we can ask for a room list
DMRoomMap.makeShared(client);
const { store } = createStore(); const { store } = createStore();
client.getVisibleRooms = jest.fn().mockReturnValue([]); client.getVisibleRooms = jest.fn().mockReturnValue([]);
@@ -285,7 +290,7 @@ describe("RoomListStore", () => {
describe("room updates", () => { describe("room updates", () => {
const makeStore = async () => { const makeStore = async () => {
const store = new RoomListStoreClass(defaultDispatcher); const store = new RoomListStoreClass(defaultDispatcher);
await store.start(); await store.useUnitTestClient(client);
return store; return store;
}; };
@@ -65,7 +65,7 @@ describe("SlidingRoomListStore", () => {
describe("spaces", () => { describe("spaces", () => {
it("alters 'filters.spaces' on the DefaultTagID.Untagged list when the selected space changes", async () => { it("alters 'filters.spaces' on the DefaultTagID.Untagged list when the selected space changes", async () => {
await store.start(); // call onReady await store.useUnitTestClient(context.client!); // call onReady
const spaceRoomId = "!foo:bar"; const spaceRoomId = "!foo:bar";
const p = untilEmission(store, LISTS_LOADING_EVENT, (listName, isLoading) => { const p = untilEmission(store, LISTS_LOADING_EVENT, (listName, isLoading) => {
@@ -92,7 +92,7 @@ describe("SlidingRoomListStore", () => {
}, },
); );
activeSpace = MetaSpace.Home; activeSpace = MetaSpace.Home;
await store.start(); // call onReady await store.useUnitTestClient(context.client!); // call onReady
expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(DefaultTagID.Untagged, { expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(DefaultTagID.Untagged, {
filters: expect.objectContaining({ filters: expect.objectContaining({
@@ -108,7 +108,7 @@ describe("SlidingRoomListStore", () => {
const p = untilEmission(store, LISTS_LOADING_EVENT, (listName, isLoading) => { const p = untilEmission(store, LISTS_LOADING_EVENT, (listName, isLoading) => {
return listName === DefaultTagID.Untagged && !isLoading; return listName === DefaultTagID.Untagged && !isLoading;
}); });
await store.start(); // call onReady await store.useUnitTestClient(context.client!); // call onReady
await p; await p;
expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith( expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(
DefaultTagID.Untagged, DefaultTagID.Untagged,
@@ -121,7 +121,7 @@ describe("SlidingRoomListStore", () => {
}); });
it("includes subspaces in 'filters.spaces' when the selected space has subspaces", async () => { it("includes subspaces in 'filters.spaces' when the selected space has subspaces", async () => {
await store.start(); // call onReady await store.useUnitTestClient(context.client!); // call onReady
const spaceRoomId = "!foo:bar"; const spaceRoomId = "!foo:bar";
const subSpace1 = "!ss1:bar"; const subSpace1 = "!ss1:bar";
const subSpace2 = "!ss2:bar"; const subSpace2 = "!ss2:bar";
@@ -168,7 +168,7 @@ describe("SlidingRoomListStore", () => {
}); });
it("getTagsForRoom gets the tags for the room", async () => { it("getTagsForRoom gets the tags for the room", async () => {
await store.start(); await store.useUnitTestClient(context.client!);
const roomA = "!a:localhost"; const roomA = "!a:localhost";
const roomB = "!b:localhost"; const roomB = "!b:localhost";
const keyToListData: Record<string, { joinedCount: number; roomIndexToRoomId: Record<number, string> }> = { const keyToListData: Record<string, { joinedCount: number; roomIndexToRoomId: Record<number, string> }> = {
@@ -200,7 +200,7 @@ describe("SlidingRoomListStore", () => {
}); });
it("emits LISTS_UPDATE_EVENT when slidingSync lists update", async () => { it("emits LISTS_UPDATE_EVENT when slidingSync lists update", async () => {
await store.start(); await store.useUnitTestClient(context.client!);
const roomA = "!a:localhost"; const roomA = "!a:localhost";
const roomB = "!b:localhost"; const roomB = "!b:localhost";
const roomC = "!c:localhost"; const roomC = "!c:localhost";
@@ -236,7 +236,7 @@ describe("SlidingRoomListStore", () => {
}); });
it("sets the sticky room on the basis of the viewed room in RoomViewStore", async () => { it("sets the sticky room on the basis of the viewed room in RoomViewStore", async () => {
await store.start(); await store.useUnitTestClient(context.client!);
// seed the store with 3 rooms // seed the store with 3 rooms
const roomIdA = "!a:localhost"; const roomIdA = "!a:localhost";
const roomIdB = "!b:localhost"; const roomIdB = "!b:localhost";
@@ -301,7 +301,7 @@ describe("SlidingRoomListStore", () => {
}); });
it("gracefully handles unknown room IDs", async () => { it("gracefully handles unknown room IDs", async () => {
await store.start(); await store.useUnitTestClient(context.client!);
const roomIdA = "!a:localhost"; const roomIdA = "!a:localhost";
const roomIdB = "!b:localhost"; // does not exist const roomIdB = "!b:localhost"; // does not exist
const roomIdC = "!c:localhost"; const roomIdC = "!c:localhost";
+7 -7
View File
@@ -1911,13 +1911,6 @@
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-7.0.0.tgz#8d6abdb9ded8656cc9e2a7909913a34bf3fc9b3a" resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-7.0.0.tgz#8d6abdb9ded8656cc9e2a7909913a34bf3fc9b3a"
integrity sha512-MOencXiW/gI5MuTtCNsuojjwT5DXCrjMqv9xOslJC9h2tPdLFFFMGr58dY5Lis4DRd9MRWcgrGowUIHOqieWTA== integrity sha512-MOencXiW/gI5MuTtCNsuojjwT5DXCrjMqv9xOslJC9h2tPdLFFFMGr58dY5Lis4DRd9MRWcgrGowUIHOqieWTA==
"@matrix-org/matrix-wysiwyg@2.37.9":
version "2.37.9"
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-2.37.9.tgz#4d5667df3c74e11fd01c4b5be920caff72bf2f48"
integrity sha512-Jn2ug6ySX5es5/SV5BVgSBhVPS7GXggwZ/GGWmnJvhnp/IArit4kgUAsNRhQeKebuVvNphwQykCGwyBOn2PLBA==
dependencies:
eslint-plugin-unicorn "^54.0.0"
"@matrix-org/olm@3.2.15": "@matrix-org/olm@3.2.15":
version "3.2.15" version "3.2.15"
resolved "https://registry.yarnpkg.com/@matrix-org/olm/-/olm-3.2.15.tgz#55f3c1b70a21bbee3f9195cecd6846b1083451ec" resolved "https://registry.yarnpkg.com/@matrix-org/olm/-/olm-3.2.15.tgz#55f3c1b70a21bbee3f9195cecd6846b1083451ec"
@@ -3101,6 +3094,13 @@
ts-xor "^1.3.0" ts-xor "^1.3.0"
vaul "^0.7.0" vaul "^0.7.0"
"@vector-im/matrix-wysiwyg@2.37.13":
version "2.37.13"
resolved "https://registry.yarnpkg.com/@vector-im/matrix-wysiwyg/-/matrix-wysiwyg-2.37.13.tgz#7b64e7652a196e811c0470dcbff8d36e2229cd46"
integrity sha512-3ilnJBJSzeTfAQXNJJ29EAqa14exrkca3acTPssdoh3WhTKwXyQ0xU8sS50d6gqlAP/Nahw1y2k+PzajXo1SsQ==
dependencies:
eslint-plugin-unicorn "^54.0.0"
"@zxcvbn-ts/core@^3.0.4": "@zxcvbn-ts/core@^3.0.4":
version "3.0.4" version "3.0.4"
resolved "https://registry.yarnpkg.com/@zxcvbn-ts/core/-/core-3.0.4.tgz#c5bde72235eb6c273cec78b672bb47c0d7045cad" resolved "https://registry.yarnpkg.com/@zxcvbn-ts/core/-/core-3.0.4.tgz#c5bde72235eb6c273cec78b672bb47c0d7045cad"