Merge branch 'develop' into germain-gg/facepile-offset

This commit is contained in:
Germain 2023-09-25 11:33:28 +01:00 committed by GitHub
commit d99618263a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
144 changed files with 12828 additions and 10514 deletions

View file

@ -1277,7 +1277,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
const errCode = err.errcode || _td("unknown error code");
Modal.createDialog(ErrorDialog, {
title: _t("Failed to forget room %(errCode)s", { errCode }),
description: err && err.message ? err.message : _t("Operation failed"),
description: err && err.message ? err.message : _t("invite|failed_generic"),
});
});
}

View file

@ -73,7 +73,7 @@ export default class UserView extends React.Component<IProps, IState> {
} catch (err) {
Modal.createDialog(ErrorDialog, {
title: _t("Could not load user profile"),
description: err instanceof Error ? err.message : _t("Operation failed"),
description: err instanceof Error ? err.message : _t("invite|failed_generic"),
});
this.setState({ loading: false });
return;

View file

@ -177,10 +177,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
if (err?.name === "ConnectionError") {
this.setState({
errorText:
_t("Cannot reach homeserver") +
": " +
_t("Ensure you have a stable internet connection, or get in touch with the server admin"),
errorText: _t("cannot_reach_homeserver") + ": " + _t("cannot_reach_homeserver_detail"),
});
return;
}

View file

@ -97,7 +97,7 @@ export default class InteractiveAuthDialog<T> extends React.Component<Interactiv
private getDefaultDialogAesthetics(): DialogAesthetics {
const ssoAesthetics = {
[SSOAuthEntry.PHASE_PREAUTH]: {
title: _t("Use Single Sign On to continue"),
title: _t("auth|uia|sso_title"),
body: _t("To continue, use Single Sign On to prove your identity."),
continueText: _t("auth|sso"),
continueKind: "primary",

View file

@ -1337,7 +1337,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
const room = MatrixClientPeg.get()?.getRoom(roomId);
const isSpace = room?.isSpaceRoom();
title = isSpace
? _t("Invite to %(spaceName)s", {
? _t("invite|to_space", {
spaceName: room?.name || _t("common|unnamed_space"),
})
: _t("Invite to %(roomName)s", {

View file

@ -165,9 +165,9 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
} else {
content = (
<p className="mx_MessageEditHistoryDialog_error">
{_t("Cannot reach homeserver")}
{_t("cannot_reach_homeserver")}
<br />
{_t("Ensure you have a stable internet connection, or get in touch with the server admin")}
{_t("cannot_reach_homeserver_detail")}
</p>
);
}

View file

@ -89,7 +89,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
logger.error("Unable to add email address " + emailAddress + " " + err);
Modal.createDialog(ErrorDialog, {
title: _t("Unable to add email address"),
description: extractErrorMessageFromError(err, _t("Operation failed")),
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
});
},
);
@ -138,7 +138,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
logger.error("Unable to verify email address: " + err);
Modal.createDialog(ErrorDialog, {
title: _t("Unable to verify email address."),
description: extractErrorMessageFromError(err, _t("Operation failed")),
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
});
}
},

View file

@ -239,7 +239,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
await cli.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (makeRequest): Promise<void> => {
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
title: _t("Setting up keys"),
title: _t("encryption|bootstrap_title"),
matrixClient: cli,
makeRequest,
});

View file

@ -112,7 +112,7 @@ export default class CreateCrossSigningDialog extends React.PureComponent<IProps
} else {
const dialogAesthetics = {
[SSOAuthEntry.PHASE_PREAUTH]: {
title: _t("Use Single Sign On to continue"),
title: _t("auth|uia|sso_title"),
body: _t("To continue, use Single Sign On to prove your identity."),
continueText: _t("auth|sso"),
continueKind: "primary",
@ -126,7 +126,7 @@ export default class CreateCrossSigningDialog extends React.PureComponent<IProps
};
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
title: _t("Setting up keys"),
title: _t("encryption|bootstrap_title"),
matrixClient: MatrixClientPeg.safeGet(),
makeRequest,
aestheticsForStagePhases: {
@ -195,7 +195,7 @@ export default class CreateCrossSigningDialog extends React.PureComponent<IProps
<BaseDialog
className="mx_CreateCrossSigningDialog"
onFinished={this.props.onFinished}
title={_t("Setting up keys")}
title={_t("encryption|bootstrap_title")}
hasCancel={false}
fixedWidth={false}
>

View file

@ -221,7 +221,7 @@ export default class LegacyCallEvent extends React.PureComponent<IProps, IState>
// in which case we show the error code)
reason = _t("An unknown error occurred");
} else if (hangupReason === CallErrorCode.UserBusy) {
reason = _t("The user you called is busy.");
reason = _t("voip|user_busy_description");
} else {
reason = _t("Unknown failure: %(reason)s", { reason: hangupReason });
}

View file

@ -467,18 +467,19 @@ export const UserOptionsSection: React.FC<{
if (errorStringFromInviterUtility) {
throw new Error(errorStringFromInviterUtility);
} else {
throw new UserFriendlyError(
`User (%(user)s) did not end up as invited to %(roomId)s but no error was given from the inviter utility`,
{ user: member.userId, roomId, cause: undefined },
);
throw new UserFriendlyError("slash_command|invite_failed", {
user: member.userId,
roomId,
cause: undefined,
});
}
}
});
} catch (err) {
const description = err instanceof Error ? err.message : _t("Operation failed");
const description = err instanceof Error ? err.message : _t("invite|failed_generic");
Modal.createDialog(ErrorDialog, {
title: _t("Failed to invite"),
title: _t("invite|failed_title"),
description,
});
}
@ -1367,7 +1368,7 @@ const BasicUserInfo: React.FC<{
logger.error("Failed to deactivate user");
logger.error(err);
const description = err instanceof Error ? err.message : _t("Operation failed");
const description = err instanceof Error ? err.message : _t("invite|failed_generic");
Modal.createDialog(ErrorDialog, {
title: _t("Failed to deactivate user"),

View file

@ -270,7 +270,7 @@ const startVoiceBroadcastButton: React.FC<IProps> = (props: IProps): ReactElemen
className="mx_MessageComposer_button"
iconClassName="mx_MessageComposer_voiceBroadcast"
onClick={props.onStartVoiceBroadcastClick}
title={_t("Voice broadcast")}
title={_t("voice_broadcast|action")}
/>
) : null;
};

View file

@ -190,7 +190,7 @@ const NewRoomIntro: React.FC = () => {
showSpaceInvite(parentSpace!);
}}
>
{_t("Invite to %(spaceName)s", { spaceName: parentSpace.name })}
{_t("invite|to_space", { spaceName: parentSpace.name })}
</AccessibleButton>
{room.canInvite(cli.getSafeUserId()) && (
<AccessibleButton

View file

@ -543,7 +543,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
private renderSuggestedRooms(): ReactComponentElement<typeof ExtraTile>[] {
return this.state.suggestedRooms.map((room) => {
const name = room.name || room.canonical_alias || room.aliases?.[0] || _t("Empty room");
const name = room.name || room.canonical_alias || room.aliases?.[0] || _t("empty_room");
const avatar = (
<RoomAvatar
oobData={{

View file

@ -133,7 +133,7 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> {
await cli.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (makeRequest): Promise<void> => {
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
title: _t("Setting up keys"),
title: _t("encryption|bootstrap_title"),
matrixClient: cli,
makeRequest,
});

View file

@ -47,7 +47,7 @@ interface IState {
export default class FontScalingPanel extends React.Component<IProps, IState> {
private readonly MESSAGE_PREVIEW_TEXT = _t("common|preview_message");
private layoutWatcherRef?: string;
private unmounted = false;
public constructor(props: IProps) {
@ -65,6 +65,15 @@ export default class FontScalingPanel extends React.Component<IProps, IState> {
const client = MatrixClientPeg.safeGet();
const userId = client.getSafeUserId();
const profileInfo = await client.getProfileInfo(userId);
this.layoutWatcherRef = SettingsStore.watchSetting("layout", null, () => {
// Update the layout for the preview window according to the user selection
const value = SettingsStore.getValue("layout");
if (this.state.layout !== value) {
this.setState({
layout: value,
});
}
});
if (this.unmounted) return;
this.setState({
@ -76,6 +85,9 @@ export default class FontScalingPanel extends React.Component<IProps, IState> {
public componentWillUnmount(): void {
this.unmounted = true;
if (this.layoutWatcherRef) {
SettingsStore.unwatchSetting(this.layoutWatcherRef);
}
}
private onFontSizeChanged = (size: number): void => {

View file

@ -16,10 +16,9 @@ limitations under the License.
*/
import React, { ReactNode } from "react";
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
import { TrustInfo } from "matrix-js-sdk/src/crypto/backup";
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
import { logger } from "matrix-js-sdk/src/logger";
import { BackupTrustInfo, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
import type CreateKeyBackupDialog from "../../../async-components/views/dialogs/security/CreateKeyBackupDialog";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
@ -41,9 +40,34 @@ interface IState {
backupKeyWellFormed: boolean | null;
secretStorageKeyInAccount: boolean | null;
secretStorageReady: boolean | null;
backupInfo: IKeyBackupInfo | null;
backupSigStatus: TrustInfo | null;
sessionsRemaining: number;
/** Information on the current key backup version, as returned by the server.
*
* `null` could mean any of:
* * we haven't yet requested the data from the server.
* * we were unable to reach the server.
* * the server returned key backup version data we didn't understand or was malformed.
* * there is actually no backup on the server.
*/
backupInfo: KeyBackupInfo | null;
/**
* Information on whether the backup in `backupInfo` is correctly signed, and whether we have the right key to
* decrypt it.
*
* `undefined` if `backupInfo` is null, or if crypto is not enabled in the client.
*/
backupTrustInfo: BackupTrustInfo | undefined;
/**
* If key backup is currently enabled, the backup version we are backing up to.
*/
activeBackupVersion: string | null;
/**
* Number of sessions remaining to be backed up. `null` if we have no information on this.
*/
sessionsRemaining: number | null;
}
export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
@ -61,8 +85,9 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
secretStorageKeyInAccount: null,
secretStorageReady: null,
backupInfo: null,
backupSigStatus: null,
sessionsRemaining: 0,
backupTrustInfo: undefined,
activeBackupVersion: null,
sessionsRemaining: null,
};
}
@ -101,14 +126,19 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
this.setState({ loading: true });
this.getUpdatedDiagnostics();
try {
const backupInfo = await MatrixClientPeg.safeGet().getKeyBackupVersion();
const backupSigStatus = backupInfo ? await MatrixClientPeg.safeGet().isKeyBackupTrusted(backupInfo) : null;
const cli = MatrixClientPeg.safeGet();
const backupInfo = await cli.getKeyBackupVersion();
const backupTrustInfo = backupInfo ? await cli.getCrypto()?.isKeyBackupTrusted(backupInfo) : undefined;
const activeBackupVersion = (await cli.getCrypto()?.getActiveSessionBackupVersion()) ?? null;
if (this.unmounted) return;
this.setState({
loading: false,
error: false,
backupInfo,
backupSigStatus,
backupTrustInfo,
activeBackupVersion,
});
} catch (e) {
logger.log("Unable to fetch key backup status", e);
@ -117,7 +147,8 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
loading: false,
error: true,
backupInfo: null,
backupSigStatus: null,
backupTrustInfo: undefined,
activeBackupVersion: null,
});
}
}
@ -173,8 +204,10 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
onFinished: (proceed) => {
if (!proceed) return;
this.setState({ loading: true });
const versionToDelete = this.state.backupInfo!.version!;
MatrixClientPeg.safeGet()
.deleteKeyBackupVersion(this.state.backupInfo!.version!)
.getCrypto()
?.deleteKeyBackupVersion(versionToDelete)
.then(() => {
this.loadBackupStatus();
});
@ -209,7 +242,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
secretStorageKeyInAccount,
secretStorageReady,
backupInfo,
backupSigStatus,
backupTrustInfo,
sessionsRemaining,
} = this.state;
@ -228,7 +261,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
} else if (backupInfo) {
let restoreButtonCaption = _t("Restore from Backup");
if (MatrixClientPeg.safeGet().getKeyBackupEnabled()) {
if (this.state.activeBackupVersion !== null) {
statusDescription = (
<SettingsSubsectionText> {_t("This session is backing up your keys.")}</SettingsSubsectionText>
);
@ -253,7 +286,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
}
let uploadStatus: ReactNode;
if (!MatrixClientPeg.safeGet().getKeyBackupEnabled()) {
if (sessionsRemaining === null) {
// No upload status to show when backup disabled.
uploadStatus = "";
} else if (sessionsRemaining > 0) {
@ -271,19 +304,21 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
}
let trustedLocally: string | undefined;
if (backupSigStatus?.trusted_locally) {
trustedLocally = _t("This backup is trusted because it has been restored on this session");
if (backupTrustInfo?.matchesDecryptionKey) {
trustedLocally = _t("This backup can be restored on this session");
}
extraDetailsTableRows = (
<>
<tr>
<th scope="row">{_t("Backup version:")}</th>
<td>{backupInfo.version}</td>
<th scope="row">{_t("Latest backup version on server:")}</th>
<td>
{backupInfo.version} ({_t("Algorithm:")} <code>{backupInfo.algorithm}</code>)
</td>
</tr>
<tr>
<th scope="row">{_t("Algorithm:")}</th>
<td>{backupInfo.algorithm}</td>
<th scope="row">{_t("Active backup version:")}</th>
<td>{this.state.activeBackupVersion === null ? _t("None") : this.state.activeBackupVersion}</td>
</tr>
</>
);

View file

@ -223,13 +223,13 @@ export default class SetIdServer extends React.Component<IProps, IState> {
private showNoTermsWarning(fullUrl: string): Promise<[ok?: boolean]> {
const { finished } = Modal.createDialog(QuestionDialog, {
title: _t("Identity server has no terms of service"),
title: _t("terms|identity_server_no_terms_title"),
description: (
<div>
<span className="warning">
{_t("The identity server you have chosen does not have any terms of service.")}
</span>
<span>&nbsp;{_t("Only continue if you trust the owner of the server.")}</span>
<span>&nbsp;{_t("terms|identity_server_no_terms_description_2")}</span>
</div>
),
button: _t("action|continue"),

View file

@ -89,7 +89,7 @@ export class ExistingEmailAddress extends React.Component<IExistingEmailAddressP
logger.error("Unable to remove contact information: " + err);
Modal.createDialog(ErrorDialog, {
title: _t("Unable to remove contact information"),
description: err && err.message ? err.message : _t("Operation failed"),
description: err && err.message ? err.message : _t("invite|failed_generic"),
});
});
};
@ -200,7 +200,7 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
this.setState({ verifying: false, continueDisabled: false, addTask: null });
Modal.createDialog(ErrorDialog, {
title: _t("Unable to add email address"),
description: extractErrorMessageFromError(err, _t("Operation failed")),
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
});
});
};
@ -247,7 +247,7 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
} else {
Modal.createDialog(ErrorDialog, {
title: _t("Unable to verify email address."),
description: extractErrorMessageFromError(err, _t("Operation failed")),
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
});
}
});

View file

@ -85,7 +85,7 @@ export class ExistingPhoneNumber extends React.Component<IExistingPhoneNumberPro
logger.error("Unable to remove contact information: " + err);
Modal.createDialog(ErrorDialog, {
title: _t("Unable to remove contact information"),
description: extractErrorMessageFromError(err, _t("Operation failed")),
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
});
});
};
@ -202,7 +202,7 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
this.setState({ verifying: false, continueDisabled: false, addTask: null });
Modal.createDialog(ErrorDialog, {
title: _t("common|error"),
description: extractErrorMessageFromError(err, _t("Operation failed")),
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
});
});
};
@ -245,7 +245,7 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
if (underlyingError.errcode !== "M_THREEPID_AUTH_FAILED") {
Modal.createDialog(ErrorDialog, {
title: _t("Unable to verify phone number."),
description: extractErrorMessageFromError(err, _t("Operation failed")),
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
});
} else {
this.setState({ verifyError: _t("Incorrect verification code") });

View file

@ -52,7 +52,7 @@ export const deleteDevicesWithInteractiveAuth = async (
const numDevices = deviceIds.length;
const dialogAesthetics = {
[SSOAuthEntry.PHASE_PREAUTH]: {
title: _t("Use Single Sign On to continue"),
title: _t("auth|uia|sso_title"),
body: _t("settings|sessions|confirm_sign_out_sso", {
count: numDevices,
}),

View file

@ -105,7 +105,7 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
});
Modal.createDialog(ErrorDialog, {
title: errorTitle,
description: extractErrorMessageFromError(err, _t("Operation failed")),
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
});
}
}
@ -161,7 +161,7 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
logger.error("Unable to verify email address: " + err);
Modal.createDialog(ErrorDialog, {
title: _t("Unable to verify email address."),
description: extractErrorMessageFromError(err, _t("Operation failed")),
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
});
}
} finally {

View file

@ -106,7 +106,7 @@ export class PhoneNumber extends React.Component<IPhoneNumberProps, IPhoneNumber
});
Modal.createDialog(ErrorDialog, {
title: errorTitle,
description: extractErrorMessageFromError(err, _t("Operation failed")),
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
});
}
}
@ -164,7 +164,7 @@ export class PhoneNumber extends React.Component<IPhoneNumberProps, IPhoneNumber
if (underlyingError instanceof MatrixError && underlyingError.errcode !== "M_THREEPID_AUTH_FAILED") {
Modal.createDialog(ErrorDialog, {
title: _t("Unable to verify phone number."),
description: extractErrorMessageFromError(err, _t("Operation failed")),
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
});
} else {
this.setState({ verifyError: _t("Incorrect verification code") });

View file

@ -110,7 +110,7 @@ export class BannedUser extends React.Component<IBannedUserProps> {
kind="danger_sm"
onClick={this.onUnbanClick}
>
{_t("Unban")}
{_t("action|unban")}
</AccessibleButton>
);
}

View file

@ -172,7 +172,7 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
if (description === userId) {
const user = client.getUser(userId);
if (user && user.displayName) {
description = _t("%(name)s (%(userId)s)", { name: user.displayName, userId });
description = _t("name_and_id", { name: user.displayName, userId });
}
}
}