Merge branch 'develop' into dbkr/i18n_back_to_ew

This commit is contained in:
David Baker 2025-11-20 18:44:23 +00:00 committed by GitHub
commit c8f4eff50b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 328 additions and 136 deletions

View File

@ -1,3 +1,35 @@
Changes in [1.12.4](https://github.com/element-hq/element-web/releases/tag/v1.12.4) (2025-11-18)
================================================================================================
## ✨ Features
* Apply aria-hidden to emoji in SAS verification ([#31204](https://github.com/element-hq/element-web/pull/31204)). Contributed by @t3chguy.
* Add options to hide header and composer of room view for the module api ([#31095](https://github.com/element-hq/element-web/pull/31095)). Contributed by @florianduros.
* Experimental Module API Additions ([#30863](https://github.com/element-hq/element-web/pull/30863)). Contributed by @dbkr.
* Change polls to use fieldset/legend markup ([#31160](https://github.com/element-hq/element-web/pull/31160)). Contributed by @langleyd.
* Use compound Button styles for Jitsi button ([#31159](https://github.com/element-hq/element-web/pull/31159)). Contributed by @Half-Shot.
* Add FocusLock to emoji picker ([#31146](https://github.com/element-hq/element-web/pull/31146)). Contributed by @langleyd.
* Move room name, avatar, and topic to IOpts. ([#30981](https://github.com/element-hq/element-web/pull/30981)). Contributed by @kaylendog.
* Add a devtool for looking at users and their devices ([#30983](https://github.com/element-hq/element-web/pull/30983)). Contributed by @uhoreg.
## 🐛 Bug Fixes
* Fix room list handling of membership changes ([#31197](https://github.com/element-hq/element-web/pull/31197)). Contributed by @t3chguy.
* Fix room list unable to be resized when displayed after a module ([#31186](https://github.com/element-hq/element-web/pull/31186)). Contributed by @florianduros.
* Inhibit keyboard highlights in dialogs when effector is not in focus ([#31181](https://github.com/element-hq/element-web/pull/31181)). Contributed by @t3chguy.
* Strip mentions from forwarded messages ([#30884](https://github.com/element-hq/element-web/pull/30884)). Contributed by @twassman.
* Don't allow pin or edit of messages with a send status ([#31158](https://github.com/element-hq/element-web/pull/31158)). Contributed by @langleyd.
* Hide room header buttons if the room hasn't been created yet. ([#31092](https://github.com/element-hq/element-web/pull/31092)). Contributed by @Half-Shot.
* Fix screen readers not indicating the emoji picker search field is focused. ([#31128](https://github.com/element-hq/element-web/pull/31128)). Contributed by @langleyd.
* Fix emoji picker highlight missing when not active element ([#31148](https://github.com/element-hq/element-web/pull/31148)). Contributed by @t3chguy.
* Add relevant aria attribute for selected emoji in the emoji picker ([#31125](https://github.com/element-hq/element-web/pull/31125)). Contributed by @t3chguy.
* Fix tooltips within context menu portals being unreliable ([#31129](https://github.com/element-hq/element-web/pull/31129)). Contributed by @t3chguy.
* Avoid excessive re-render of room list and member list ([#31131](https://github.com/element-hq/element-web/pull/31131)). Contributed by @florianduros.
* Make emoji picker height responsive. ([#31130](https://github.com/element-hq/element-web/pull/31130)). Contributed by @langleyd.
* Emoji Picker: Focused emoji does not move with the arrow keys ([#30893](https://github.com/element-hq/element-web/pull/30893)). Contributed by @langleyd.
* Fix audio player seek bar position ([#31127](https://github.com/element-hq/element-web/pull/31127)). Contributed by @florianduros.
* Add aria label to emoji picker search ([#31126](https://github.com/element-hq/element-web/pull/31126)). Contributed by @langleyd.
Changes in [1.12.3](https://github.com/element-hq/element-web/releases/tag/v1.12.3) (2025-11-04)
================================================================================================
## 🦖 Deprecations

View File

@ -1,6 +1,6 @@
{
"name": "element-web",
"version": "1.12.3",
"version": "1.12.4",
"description": "Element: the future of secure communication",
"author": "New Vector Ltd.",
"repository": {
@ -313,7 +313,7 @@
"relativePaths": true
},
"engines": {
"node": ">=24"
"node": ">=22.18"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View File

@ -1,6 +1,6 @@
{
"name": "@element-hq/web-shared-components",
"version": "0.0.0-test.7",
"version": "0.0.0-test.8",
"description": "Shared components for Element",
"author": "New Vector Ltd.",
"repository": {

View File

@ -88,7 +88,7 @@ export default abstract class BasePlatform {
protected onAction(payload: ActionPayload): void {
switch (payload.action) {
case "on_client_not_viable":
case Action.ClientNotViable:
case Action.OnLoggedOut:
this.setNotificationCount(0);
break;

View File

@ -318,12 +318,6 @@ export default class DeviceListener {
const cli = this.client;
// cross-signing support was added to Matrix in MSC1756, which landed in spec v1.1
if (!(await cli.isVersionSupported("v1.1"))) {
logSpan.debug("cross-signing not supported");
return;
}
const crypto = cli.getCrypto();
if (!crypto) {
logSpan.debug("crypto not enabled");

View File

@ -1001,7 +1001,7 @@ export function softLogout(): void {
// Ensure that we dispatch a view change **before** stopping the client so
// so that React components unmount first. This avoids React soft crashes
// that can occur when components try to use a null client.
dis.dispatch({ action: "on_client_not_viable" }); // generic version of on_logged_out
dis.dispatch({ action: Action.ClientNotViable }); // generic version of on_logged_out
stopMatrixClient(/*unsetClient=*/ false);
// DO NOT CALL LOGOUT. A soft logout preserves data, logout does not.
@ -1034,7 +1034,7 @@ async function startMatrixClient(
// to add listeners for the 'sync' event so otherwise we'd have
// a race condition (and we need to dispatch synchronously for this
// to work).
dis.dispatch({ action: "will_start_client" }, true);
dis.dispatch({ action: Action.WillStartClient }, true);
// reset things first just in case
SdkContextClass.instance.typingStore.reset();
@ -1080,7 +1080,7 @@ async function startMatrixClient(
// dispatch that we finished starting up to wire up any other bits
// of the matrix client that cannot be set prior to starting up.
dis.dispatch({ action: "client_started" });
dis.dispatch({ action: Action.ClientStarted });
if (isSoftLogout()) {
softLogout();

View File

@ -15,6 +15,7 @@ import { MatrixClientPeg } from "./MatrixClientPeg";
import dis from "./dispatcher/dispatcher";
import Timer from "./utils/Timer";
import { type ActionPayload } from "./dispatcher/payloads";
import { Action } from "./dispatcher/actions.ts";
// Time in ms after that a user is considered as unavailable/away
const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins
@ -61,7 +62,7 @@ class Presence {
}
private onAction = (payload: ActionPayload): void => {
if (payload.action === "user_activity") {
if (payload.action === Action.UserActivity) {
this.setState(SetPresence.Online);
this.unavailableTimer?.restart();
}

View File

@ -939,7 +939,13 @@ for (const evType of ElementCallEventType.names) {
*/
export function hasText(ev: MatrixEvent, client: MatrixClient, showHiddenEvents?: boolean): boolean {
const handler = (ev.isState() ? stateHandlers : handlers)[ev.getType()];
return Boolean(handler?.(ev, client, false, showHiddenEvents));
try {
return Boolean(handler?.(ev, client, false, showHiddenEvents));
} catch (e) {
console.error(`Error encountered when trying to render event type=${ev.getType()} id=${ev.getId()}`, e);
// Returning true if we have a handler so we can show an error tile rather than no tile at all
return !!handler;
}
}
/**

View File

@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import dis from "./dispatcher/dispatcher";
import Timer from "./utils/Timer";
import { Action } from "./dispatcher/actions.ts";
// important these are larger than the timeouts of timers
// used with UserActivity.timeWhileActive*,
@ -190,11 +191,9 @@ export default class UserActivity {
this.lastScreenY = event.screenY;
}
dis.dispatch({ action: "user_activity" });
dis.dispatch({ action: Action.UserActivity });
if (!this.activeNowTimeout.isRunning()) {
this.activeNowTimeout.start();
dis.dispatch({ action: "user_activity_start" });
UserActivity.runTimersUntilTimeout(this.attachedActiveNowTimers, this.activeNowTimeout);
} else {
this.activeNowTimeout.restart();

View File

@ -18,6 +18,7 @@ import { MatrixClientPeg } from "../../MatrixClientPeg";
import MatrixClientContext from "../../contexts/MatrixClientContext";
import AutoHideScrollbar from "./AutoHideScrollbar";
import { type ActionPayload } from "../../dispatcher/payloads";
import { Action } from "../../dispatcher/actions.ts";
interface IProps {
// URL to request embedded page content from
@ -109,7 +110,7 @@ export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
private onAction = (payload: ActionPayload): void => {
// HACK: Workaround for the context's MatrixClient not being set up at render time.
if (payload.action === "client_started") {
if (payload.action === Action.ClientStarted) {
this.forceUpdate();
}
};

View File

@ -427,10 +427,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} else {
this.setStateForNewView({ view: Views.COMPLETE_SECURITY });
}
} else if (
(await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) &&
!(await shouldSkipSetupEncryption(cli))
) {
} else if (!(await shouldSkipSetupEncryption(cli))) {
// if cross-signing is not yet set up, do so now if possible.
InitialCryptoSetupStore.sharedInstance().startInitialCryptoSetup(
cli,
@ -815,13 +812,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
break;
}
case "view_last_screen":
// This function does what we want, despite the name. The idea is that it shows
// the last room we were looking at or some reasonable default/guess. We don't
// have to worry about email invites or similar being re-triggered because the
// function will have cleared that state and not execute that path.
this.showScreenAfterLogin();
break;
case "hide_left_panel":
this.setState(
{
@ -859,13 +849,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.onLoggedIn();
}
break;
case "on_client_not_viable":
case Action.ClientNotViable:
this.onSoftLogout();
break;
case Action.OnLoggedOut:
this.onLoggedOut();
break;
case "will_start_client":
case Action.WillStartClient:
this.setState({ ready: false }, () => {
// if the client is about to start, we are, by definition, not ready.
// Set ready to false now, then it'll be set to true when the sync
@ -873,7 +863,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.onWillStartClient();
});
break;
case "client_started":
case Action.ClientStarted:
// No need to make this handler async to wait for the result of this
this.onClientStarted().catch((e) => {
logger.error("Exception in onClientStarted", e);
@ -2104,7 +2094,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} else if (this.state.view === Views.COMPLETE_SECURITY) {
view = <CompleteSecurity onFinished={this.onCompleteSecurityE2eSetupFinished} />;
} else if (this.state.view === Views.E2E_SETUP) {
view = <E2eSetup onFinished={this.onCompleteSecurityE2eSetupFinished} />;
view = <E2eSetup onCancelled={this.onCompleteSecurityE2eSetupFinished} />;
} else if (this.state.view === Views.LOGGED_IN) {
// `ready` and `view==LOGGED_IN` may be set before `page_type` (because the
// latter is set via the dispatcher). If we don't yet have a `page_type`,

View File

@ -13,15 +13,19 @@ import CompleteSecurityBody from "../../views/auth/CompleteSecurityBody";
import { InitialCryptoSetupDialog } from "../../views/dialogs/security/InitialCryptoSetupDialog";
interface IProps {
onFinished: () => void;
/** Callback which is called if the crypto setup failed, and the user clicked the 'cancel' button */
onCancelled: () => void;
}
/**
* An {@link AuthPage} which shows the {@link InitialCryptoSetupDialog}.
*/
export default class E2eSetup extends React.Component<IProps> {
public render(): React.ReactNode {
return (
<AuthPage>
<CompleteSecurityBody>
<InitialCryptoSetupDialog onFinished={this.props.onFinished} />
<InitialCryptoSetupDialog onCancelled={this.props.onCancelled} />
</CompleteSecurityBody>
</AuthPage>
);

View File

@ -29,16 +29,6 @@ export interface UserInfoVerificationSectionState {
verifySelectedUser: () => Promise<void>;
}
const useHomeserverSupportsCrossSigning = (cli: MatrixClient): boolean => {
return useAsyncMemo<boolean>(
async () => {
return cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing");
},
[cli],
false,
);
};
const useHasCrossSigningKeys = (cli: MatrixClient, member: User, canVerify: boolean): boolean | undefined => {
return useAsyncMemo(async () => {
if (!canVerify) return undefined;
@ -56,8 +46,6 @@ export const useUserInfoVerificationViewModel = (
): UserInfoVerificationSectionState => {
const cli = useContext(MatrixClientContext);
const homeserverSupportsCrossSigning = useHomeserverSupportsCrossSigning(cli);
const userTrust = useAsyncMemo<UserVerificationStatus | undefined>(
async () => cli.getCrypto()?.getUserVerificationStatus(member.userId),
[member.userId],
@ -67,13 +55,7 @@ export const useUserInfoVerificationViewModel = (
const hasUserVerificationStatus = Boolean(userTrust);
const isUserVerified = Boolean(userTrust?.isVerified());
const isMe = member.userId === cli.getUserId();
const canVerify =
hasUserVerificationStatus &&
homeserverSupportsCrossSigning &&
!isUserVerified &&
!isMe &&
devices &&
devices.length > 0;
const canVerify = hasUserVerificationStatus && !isUserVerified && !isMe && devices && devices.length > 0;
const hasCrossSigningKeys = useHasCrossSigningKeys(cli, member as User, canVerify);
const verifySelectedUser = (): Promise<void> => verifyUser(cli, member as User);

View File

@ -16,23 +16,21 @@ import Spinner from "../../elements/Spinner";
import { InitialCryptoSetupStore, useInitialCryptoSetupStatus } from "../../../../stores/InitialCryptoSetupStore";
interface Props {
onFinished: (success?: boolean) => void;
/** Callback which is called if the crypto setup failed, and the user clicked the 'cancel' button */
onCancelled: () => void;
}
/*
* Walks the user through the process of creating a cross-signing keys.
/**
* Walks the user through the process of creating cross-signing keys.
*
* In most cases, only a spinner is shown, but for more
* complex auth like SSO, the user may need to complete some steps to proceed.
*/
export const InitialCryptoSetupDialog: React.FC<Props> = ({ onFinished }) => {
export const InitialCryptoSetupDialog: React.FC<Props> = ({ onCancelled }) => {
const onRetryClick = useCallback(() => {
InitialCryptoSetupStore.sharedInstance().retry();
}, []);
const onCancelClick = useCallback(() => {
onFinished(false);
}, [onFinished]);
const status = useInitialCryptoSetupStatus(InitialCryptoSetupStore.sharedInstance());
let content;
@ -44,7 +42,7 @@ export const InitialCryptoSetupDialog: React.FC<Props> = ({ onFinished }) => {
<DialogButtons
primaryButton={_t("action|retry")}
onPrimaryButtonClick={onRetryClick}
onCancel={onCancelClick}
onCancel={onCancelled}
/>
</div>
</div>
@ -60,7 +58,6 @@ export const InitialCryptoSetupDialog: React.FC<Props> = ({ onFinished }) => {
return (
<BaseDialog
className="mx_CreateCrossSigningDialog"
onFinished={onFinished}
title={_t("encryption|bootstrap_title")}
hasCancel={false}
fixedWidth={false}

View File

@ -316,16 +316,39 @@ export enum Action {
*/
ShowRoomTopic = "show_room_topic",
/**
* Fired when the client is no longer viable to use: specifically, that we have been "soft-logged out".
*/
ClientNotViable = "client_not_viable",
/**
* Fired when the client was logged out. No additional payload information required.
*/
OnLoggedOut = "on_logged_out",
/**
* Fired when the client was logged in. No additional payload information required.
* Fired when the client was logged in, or has otherwise been set up with authentication data (e.g., by loading the
* access token from local storage). Note that this does not necessarily mean that a login action has happened,
* just that authentication creds have been set up.
*
* No additional payload information required.
*/
OnLoggedIn = "on_logged_in",
/**
* Fired when the client is about to be started, shortly after {@link OnLoggedIn}.
*
* No additional payload information required.
*/
WillStartClient = "will_start_client",
/**
* Fired when the client has started, shortly after {@link WillStartClient}.
*
* No additional payload information required.
*/
ClientStarted = "client_started",
/**
* Overwrites the existing login with fresh session credentials. Use with a OverwriteLoginPayload.
*/
@ -380,4 +403,10 @@ export enum Action {
* Open the create room dialog
*/
CreateRoom = "view_create_room",
/**
* The `UserActivity` tracker determined that there was some activity from the user (typically a mouse movement
* or keyboard event).
*/
UserActivity = "user_activity",
}

View File

@ -4094,9 +4094,11 @@
"user_busy_description": "Die angerufene Person ist momentan beschäftigt.",
"user_is_presenting": "%(sharerName)s präsentiert",
"video_call": "Videoanruf",
"video_call_incoming": "Eingehender Videoanruf",
"video_call_started": "Videoanruf hat begonnen",
"video_call_using": "Videoanruf mit:",
"voice_call": "Sprachanruf",
"voice_call_incoming": "Eingehender Anruf",
"you_are_presenting": "Du präsentierst"
},
"web_default_device_name": "%(appName)s: %(browserName)s auf %(osName)s",

View File

@ -603,6 +603,7 @@
"video": "Video",
"video_room": "Videotuba",
"view_message": "Vaata sõnumit",
"voice": "Hääl",
"warning": "Hoiatus"
},
"composer": {
@ -4096,9 +4097,11 @@
"user_busy_description": "Kasutaja, kellele sa helistasid, on hõivatud.",
"user_is_presenting": "%(sharerName)s esitab",
"video_call": "Videokõne",
"video_call_incoming": "Saabuv videokõne",
"video_call_started": "Videokõne algas",
"video_call_using": "Videokõne, kus on kasutusel:",
"voice_call": "Häälkõne",
"voice_call_incoming": "Saaduv häälkõne",
"you_are_presenting": "Sina esitad"
},
"web_default_device_name": "%(appName)s: %(browserName)s operatsioonisüsteemis %(osName)s",

View File

@ -603,6 +603,7 @@
"video": "Vidéo",
"video_room": "Salon vidéo",
"view_message": "Afficher le message",
"voice": "Voix",
"warning": "Attention"
},
"composer": {
@ -831,6 +832,7 @@
"failed_to_save": "Échec lors de la sauvegarde des paramètres.",
"failed_to_send": "Échec de lenvoi de lévénement !",
"id": "ID : ",
"invalid_device_key_id": "L'ID de la clé de l'appareil est invalide",
"invalid_json": "Ne semble pas être du JSON valide.",
"level": "Rang",
"low_bandwidth_mode": "Mode faible bande passante",
@ -841,6 +843,7 @@
"notification_state": "Létat des notifications est <strong>%(notificationState)s</strong>",
"notifications_debug": "Débogage des notifications",
"number_of_users": "Nombre dutilisateurs",
"only_joined_members": "Seuls les utilisateurs membres",
"original_event_source": "Évènement source original",
"room_encrypted": "Le salon est <strong>chiffré ✅</strong>",
"room_id": "Identifiant du salon : %(roomId)s",
@ -887,10 +890,23 @@
"toggle_event": "Afficher/masquer lévènement",
"toolbox": "Boîte à outils",
"use_at_own_risk": "Cette interface ne vérifie pas les types des valeurs. Utilisez la à vos propres risques.",
"user_avatar": "Avatar: %(avatar)s",
"user_displayname": "Nom d'affichage : %(displayname)s",
"user_id": "Identifiant utilisateur : %(userId)s",
"user_no_avatar": "Avatar: <i> Aucun</i>",
"user_no_displayname": "Nom d'affichage : <i>Aucun</i>",
"user_read_up_to": "Lutilisateur a lu jusquà : ",
"user_read_up_to_ignore_synthetic": "Lutilisateur a lu jusquà (ignoreSynthetic) : ",
"user_read_up_to_private": "Lutilisateur a lu jusquà (m.read.private) : ",
"user_read_up_to_private_ignore_synthetic": "Lutilisateur a lu jusquà (m.read.private;ignoreSynthetic) : ",
"user_room_membership": "Adhésion : %(membership)s",
"user_verification_status": {
"identity_changed": "Statut de la vérification : <E2EIcon /> Non vérifié, et identité modifiée",
"unverified": "Statut de la vérification vérification : <E2EIcon /> Non vérifié",
"verified": "Statut de la vérification : <E2EIcon /> Vérifié",
"was_verified": "Statut de la vérifcation vérification : <E2EIcon /> Etait vérifié, mais l'identité a changé."
},
"users": "Utilisateurs",
"value": "Valeur",
"value_colon": "Valeur :",
"value_in_this_room": "Valeur pour ce salon",
@ -2603,7 +2619,7 @@
"do_not_close_warning": "Ne fermez pas cette fenêtre tant que la réinitialisation n'est pas terminée",
"export_keys": "Exporter les clés",
"import_keys": "Importer les clés",
"other_people_device_description": "Attention : les utilisateurs qui ne se sont pas explicitement vérifiés auprès de vous (par exemple, via des émojis) ne recevront pas vos messages chiffrés. De même, les appareils non vérifiés des utilisateurs vérifiés ne recevront pas vos messages chiffrés.",
"other_people_device_description": "Attention : les utilisateurs qui ne se sont pas explicitement vérifiés auprès de vous (par exemple, via des émojis) ne recevront pas vos messages chiffrés. De même, les appareils non vérifiés des utilisateurs vérifiés ne recevront pas vos messages chiffrés. Cette fonctionalitée requiert le redémarrage de l'application pour prendre effet.",
"other_people_device_label": "Dans les salons chiffrés, envoyez des messages uniquement aux utilisateurs vérifiés",
"other_people_device_title": "Appareils d'autres personnes",
"reset_identity": "Réinitialiser l'identité cryptographique",
@ -4080,9 +4096,11 @@
"user_busy_description": "Lutilisateur que vous avez appelé est indisponible.",
"user_is_presenting": "%(sharerName)s est à lécran",
"video_call": "Appel vidéo",
"video_call_incoming": "Appel vidéo entrant",
"video_call_started": "Appel vidéo commencé",
"video_call_using": "Appel vidéo utilisant :",
"voice_call": "Appel audio",
"voice_call_incoming": "Appel vocal entrant",
"you_are_presenting": "Vous êtes à lécran"
},
"web_default_device_name": "%(appName)s : %(browserName)s pour %(osName)s",

View File

@ -2578,7 +2578,7 @@
"do_not_close_warning": "Não feche essa janela até que a redefinição seja concluída",
"export_keys": "Exportar chaves",
"import_keys": "Importar chaves",
"other_people_device_description": "Por padrão, em salas criptografadas, não envie mensagens criptografadas para ninguém até que você as tenha verificado",
"other_people_device_description": "Aviso: usuários que não verificaram explicitamente sua identidade com você (por exemplo, usando emojis) não receberão suas mensagens criptografadas. Além disso, dispositivos não verificados de usuários verificados também não receberão suas mensagens criptografadas. As alterações exigem a reinicialização do aplicativo para entrarem em vigor.",
"other_people_device_label": "Nunca envie mensagens criptografadas para dispositivos não verificados",
"other_people_device_title": "Dispositivos de outras pessoas",
"reset_identity": "Redefinir identidade criptográfica",

View File

@ -579,7 +579,7 @@
"setup_secure_messages": "Настроить безопасные сообщения",
"show_more": "Показать больше",
"someone": "Кто-то",
"space": одпространство",
"space": ространство",
"spaces": "Пространства",
"sticker": "Стикер",
"stickerpack": "Набор стикеров",
@ -606,6 +606,7 @@
"video": "Видео",
"video_room": "Видеокомната",
"view_message": "Посмотреть сообщение",
"voice": "Голос",
"warning": "Внимание"
},
"composer": {
@ -650,8 +651,8 @@
"placeholder_encrypted": "Отправить зашифрованное сообщение…",
"placeholder_reply": "Отправить незашифрованный ответ…",
"placeholder_reply_encrypted": "Отправить ответ…",
"placeholder_thread": "Ответить на обсуждение…",
"placeholder_thread_encrypted": "Ответить на зашифрованное обсуждение…",
"placeholder_thread": "Ответить в небезопасном обсуждение…",
"placeholder_thread_encrypted": "Ответить в обсуждение…",
"poll_button": "Опрос",
"poll_button_no_perms_description": "У вас нет разрешения начинать опросы в этой комнате.",
"poll_button_no_perms_title": "Требуется разрешение",
@ -808,6 +809,15 @@
},
"developer_mode": "Режим разработчика",
"developer_tools": "Инструменты разработчика",
"device_id": "ID устройства: %(deviceId)s",
"device_keys": "Ключи устройств",
"device_verification_status": {
"signed_by_owner": "Статус проверки: <E2EIcon /> Подписано владельцем",
"unknown": "Статус проверки: Неизвестно",
"unverified": "Статус проверки:<E2EIcon /> Не подписано владельцем",
"verified": "Статус проверки:<E2EIcon /> Подтверждено перекрестной подписью"
},
"devices": "Криптографические устройства (%(count)s )",
"edit_setting": "Изменить настройки",
"edit_values": "Редактировать значения",
"empty_string": "<empty string>",
@ -823,6 +833,7 @@
"failed_to_save": "Не удалось сохранить настройки.",
"failed_to_send": "Не удалось отправить событие!",
"id": "ID: ",
"invalid_device_key_id": "Неверный ключа ID устройства",
"invalid_json": "Не похоже на действующий JSON.",
"level": "Уровень",
"low_bandwidth_mode": "Режим низкой пропускной способности",
@ -833,6 +844,7 @@
"notification_state": "Состояние уведомления <strong>%(notificationState)s</strong>",
"notifications_debug": "Отладка уведомлений",
"number_of_users": "Количество пользователей",
"only_joined_members": "Только зарегистрированные пользователи",
"original_event_source": "Оригинальный исходный код",
"room_encrypted": "Комната <strong> зашифрована ✅</strong>",
"room_id": "ID комнаты: %(roomId)s",
@ -881,10 +893,23 @@
"toggle_event": "переключить событие",
"toolbox": "Панель инструментов",
"use_at_own_risk": "Этот пользовательский интерфейс НЕ проверяет типы значений. Используйте на свой страх и риск.",
"user_avatar": "Аватар: %(avatar)s",
"user_displayname": "Отображаемое имя: %(displayname)s",
"user_id": "ID пользователя: %(userId)s",
"user_no_avatar": "Аватар: <i>Нет</i>",
"user_no_displayname": "Отображаемое имя: <i>Отсутствует</i>",
"user_read_up_to": "Пользователь прочитал до: ",
"user_read_up_to_ignore_synthetic": "Пользователь прочитал до (IgnoreSynthetic): ",
"user_read_up_to_private": "Пользователь прочитал до (m.read.private): ",
"user_read_up_to_private_ignore_synthetic": "Пользователь прочитал до (m.read.private; ignoreSynthetic): ",
"user_room_membership": "Членство: %(membership)s",
"user_verification_status": {
"identity_changed": "Статус проверки:<E2EIcon /> Не проверено, и личность изменена",
"unverified": "Статус проверки: <E2EIcon /> Непроверено",
"verified": "Статус проверки: <E2EIcon /> Проверено",
"was_verified": "Статус проверки: <E2EIcon /> Был проверен, но личность изменилась"
},
"users": "Пользователи",
"value": "Значение",
"value_colon": "Значение:",
"value_in_this_room": "Значение в этой комнате",
@ -1089,6 +1114,7 @@
},
"verification_requested_toast_title": "Запрошено подтверждение",
"verified_identity_changed": "Подтвержденная личность %(displayName)s (<b>%(userId)s</b>) изменилась. <a>Узнайте больше</a>",
"verified_identity_changed_no_displayname": "<b>%(userId)s</b> личность была сброшена. <a>Узнать подробности</a>",
"verify_toast_description": "Другие пользователи могут не доверять этому сеансу",
"verify_toast_title": "Заверьте этот сеанс",
"withdraw_verification_action": "Подтверждение верификации"
@ -1372,6 +1398,7 @@
"name_mxid_share_room": "Пригласите кого-нибудь, используя его имя, имя пользователя (например, <userId/>) или <a>поделитесь этой комнатой</a>.",
"name_mxid_share_space": "Пригласите кого-нибудь, используя их отображаемое имя или имя учётной записи (например, <userId/>) или <a>поделитесь этим пространством</a>.",
"progress": {
"dont_close": "Не закрывайте приложение, до завершения.",
"preparing": "Подготовка приглашений..."
},
"recents_section": "Недавние Диалоги",
@ -1542,6 +1569,9 @@
"render_reaction_images_description": "Иногда их называют \"пользовательскими эмодзи\".",
"report_to_moderators": "Пожаловаться модераторам",
"report_to_moderators_description": "В поддерживающих модерирование комнатах, кнопка \"Пожаловаться\" позволит вам сообщить о нарушении модераторам комнаты.",
"share_history_on_invite": "Поделиться зашифрованной историей с новыми участниками",
"share_history_on_invite_description": "Приглашая пользователя в зашифрованную комнату, для которой установлена видимость истории как «общая», поделитесь зашифрованной историей с этим пользователем и примите зашифрованную историю, когда вас приглашают в такую комнату.",
"share_history_on_invite_warning": "Эта функция ЭКСПЕРИМЕНТАЛЬНАЯ и в ней реализованы не все меры безопасности. Не включайте её в рабочих учётных записях.",
"sliding_sync": "Режим Sliding Sync",
"sliding_sync_description": "В активной разработке, нельзя отключить.",
"sliding_sync_disabled_notice": "Выйдите из системы и снова войдите, чтобы отключить",
@ -2002,7 +2032,9 @@
"inaccessible_subtitle_1": "Повторите попытку позже или попросите администратора комнаты или пространства проверить, есть ли у вас доступ.",
"inaccessible_subtitle_2": "При попытке получить доступ к комнате или пространству была возвращена ошибка %(errcode)s. Если вы думаете, что вы видите это сообщение по ошибке, пожалуйста, <issueLink>отправьте отчет об ошибке</issueLink>.",
"intro": {
"display_topic": "Тема: <topic/>",
"dm_caption": "В этом разговоре только вы двое, если только кто-нибудь из вас не пригласит кого-нибудь присоединиться.",
"edit_topic": "Тема: <topic/> (<a>редактировать</a>)",
"enable_encryption_prompt": "Включите шифрование в настройках.",
"encrypted_3pid_dm_pending_join": "Как только все присоединятся, вы сможете общаться в чате",
"no_avatar_label": "Добавьте фото, чтобы люди могли легко заметить вашу комнату.",
@ -2066,7 +2098,7 @@
"pinned_message_banner": {
"button_close_list": "Закрыть список",
"button_view_all": "Посмотреть все",
"description": "В этой комнате есть закрепленные сообщения. Нажмите, чтобы просмотреть их.",
"description": "Закрепленные сообщения",
"title": "<bold>%(index)s из %(length)s</bold> Закрепленные сообщения"
},
"read_topic": "Нажмите, чтобы увидеть тему",
@ -2170,18 +2202,37 @@
"mark_unread": "Отметить как непрочитанное"
},
"notification_options": "Настройки уведомлений",
"open_space_menu": "Открыть меню пространств",
"primary_filters": "Фильтры комнат",
"redacting_messages_status": {
"one": "Удаляются сообщения в %(count)s комнате",
"other": "Удаляются сообщения в %(count)s комнатах"
},
"release_announcement": {
"next": "Далее"
"done": "Готово",
"filter": {
"description": "Фильтруйте ваши чаты одним кликом. Разверните, чтобы увидеть больше фильтров.",
"title": "Новые быстрые фильтры"
},
"intro": {
"description": "Список чатов обновили, чтобы он был понятнее и проще в использовании.",
"title": "Чаты теперь выглядят по-новому!"
},
"next": "Далее",
"settings": {
"description": "Чтобы включить или выключить предварительный просмотр сообщений, зайди в Все настройки > Параметры > Список комнат",
"title": "Некоторые настройки были перемещены"
},
"sort": {
"description": "Измените порядок ваших чатов от последних до A-Я.",
"title": "Сортируйте ваши чаты"
}
},
"room": {
"more_options": "Дополнительные параметры",
"open_room": "Открыть комнату %(roomName)s"
},
"room_options": "Настройки комнаты",
"show_less": "Показать меньше",
"show_n_more": {
"other": "Показать ещё %(count)s",
@ -2197,6 +2248,10 @@
"atoz": "А-Я"
},
"sort_unread_first": "Комнаты с непрочитанными сообщениями в начале",
"space_menu": {
"home": "Пространство — Главная",
"space_settings": "Настройки Пространства"
},
"space_menu_label": "Меню %(spaceName)s",
"sublist_options": "Настройки списка",
"suggested_rooms_heading": "Предлагаемые комнаты"
@ -2362,6 +2417,10 @@
"users_default": "Роль по умолчанию"
},
"security": {
"cannot_change_to_private_due_to_missing_history_visiblity_permissions": {
"description": "У вас нет прав на изменение настроек видимости истории чата. Это опасно, так как может позволить неприглашенным пользователям читать сообщения.",
"title": "Не получается сделать комнату приватной"
},
"enable_encryption_confirm_description": "После включения шифрования в комнате оно не может быть отключено. Сообщения, отправленные в шифрованной комнате, смогут прочитать только участники комнаты, но не сервер. Включенное шифрование может помешать корректной работе многим ботам и мостам. <a>Подробнее о шифровании.</a>",
"enable_encryption_confirm_title": "Разрешить шифрование?",
"enable_encryption_public_room_confirm_description_1": "<b>Не рекомендуется добавлять шифрование в публичные комнаты.</b> Кто угодно может найти и присоединиться к ним, тем самым позволяя читать сообщения. Вы не получите преимуществ шифрования и при этом не сможете его отключить. Шифрование сообщений в публичной комнате лишь замедлит их получение и отправку.",
@ -2379,7 +2438,7 @@
"history_visibility_joined": "Только участники (с момента их входа)",
"history_visibility_legend": "Кто может читать историю?",
"history_visibility_shared": "Только участники (с момента выбора этого параметра)",
"history_visibility_warning": "Изменения в том, кто может читать историю, будут применяться только к будущим сообщениям в этой комнате. Существующие истории останутся без изменений.",
"history_visibility_warning": "Видимость существующей истории сообщений в этой комнате не изменится.",
"history_visibility_world_readable": "Все",
"join_rule_description": "Укажите, кто может присоединиться к %(roomName)s.",
"join_rule_invite": "Приватное (только по приглашению)",
@ -2426,6 +2485,7 @@
"many": "Обновление пространств... (%(progress)s из %(count)s)"
},
"join_rule_upgrade_upgrading_room": "Обновление комнаты",
"join_rule_world_readable_description": "Изменение того, кто может присоединиться к комнате, также изменит видимость будущих сообщений.",
"public_without_alias_warning": "Для связи с этой комнатой, пожалуйста, добавьте адрес.",
"publish_room": "Сделать эту комнату видимой в каталоге общественных комнат.",
"publish_space": "Сделайте это пространство видимым в каталоге общественных комнат.",
@ -2483,6 +2543,10 @@
"recent_changes_heading": "Последние изменения, которые еще не были получены",
"title": "Сервер не отвечает"
},
"service_worker_error": {
"description": "%(brand)s Для загрузки аутентифицированных медиафайлов из репозиториев Matrix требуется сервис-воркер. Ваш браузер не поддерживает эту функцию, поэтому медиафайлы могут не загружаться.",
"title": "Не удалось загрузить Service Worker"
},
"seshat": {
"error_initialising": "Инициализация поиска сообщений не удалась, проверьте <a>ваши настройки</a> для получения дополнительной информации",
"reset_button": "Сброс хранилища событий",
@ -2560,13 +2624,15 @@
"breadcrumb_second_description": "Вы потеряете историю сообщений, которая хранится только на сервере",
"breadcrumb_third_description": "Вам нужно будет заново подтвердить все существующие устройства и контакты.",
"breadcrumb_title": "Вы уверены, что хотите сбросить свою идентификацию?",
"breadcrumb_title_cant_confirm": "Вам необходимо сбросить свою личность",
"breadcrumb_title_forgot": "Забыли ключ восстановления? Вам нужно будет восстановить свою идентификацию.",
"breadcrumb_title_sync_failed": "Не удалось синхронизировать хранилище ключей. Вам необходимо сбросить идентификационные данные.",
"breadcrumb_warning": "Делайте это только в том случае, если вы считаете, что ваша учетная запись взломана.",
"details_title": "Сведения о шифровании",
"do_not_close_warning": "Не закрывайте это окно до тех пор, пока сброс не будет завершен",
"export_keys": "Экспортировать ключи",
"import_keys": "Импортировать ключи",
"other_people_device_description": "Внимание: пользователи, которые явно не подтвердили вашу личность (например, с помощью emoji), не получат ваши зашифрованные сообщения. Кроме того, неверифицированные устройства верифицированных пользователей не будут получать ваши зашифрованные сообщения.",
"other_people_device_description": "Внимание: пользователи, которые явно не подтвердили вашу личность (например, с помощью эмодзи), не получат ваши зашифрованные сообщения. Кроме того, неверифицированные устройства верифицированных пользователей не будут получать ваши зашифрованные сообщения.",
"other_people_device_label": "В зашифрованных комнатах отправляйте сообщения только проверенным пользователям",
"other_people_device_title": "Устройства других людей",
"reset_identity": "Сбросить криптографическую идентификацию",
@ -2575,6 +2641,8 @@
"session_key": "Ключ сеанса:",
"title": "Дополнительно"
},
"confirm_key_storage_off": "Вы уверены, что хотите оставить хранилище ключей отключенным?",
"confirm_key_storage_off_description": "Если вы выйдете из системы на всех своих устройствах, вы потеряете историю сообщений и вам придется заново подтвердить все существующие контакты.<a>Узнать больше</a>",
"delete_key_storage": {
"breadcrumb_page": "Удалить хранилище ключей",
"confirm": "Удалить хранилище ключей",
@ -2633,6 +2701,7 @@
"allow_spellcheck": "Разрешить проверку орфографии",
"application_language": "Язык приложения",
"application_language_reload_hint": "Приложение перезагрузится после выбора другого языка",
"avatar_open_menu": "Открыть меню аватара",
"avatar_remove_progress": "Удаление изображения…",
"avatar_save_progress": "Загрузка изображения...",
"avatar_upload_error_text": "Формат файла не поддерживается или размер изображения превышает %(size)s.",
@ -2660,6 +2729,7 @@
"discovery_needs_terms_title": "Позвольте людям найти вас",
"display_name": "Отображаемое имя",
"display_name_error": "Невозможно установить отображаемое имя",
"email_adding_unsupported_by_hs": "Этот домашний сервер не позволяет добавлять адреса электронной почты в вашу учетную запись.",
"email_address_in_use": "Этот адрес электронной почты уже используется",
"email_address_label": "Адрес электронной почты",
"email_not_verified": "Ваш адрес электронной почты еще не проверен",
@ -2684,7 +2754,9 @@
"error_share_msisdn_discovery": "Не удается предоставить общий доступ к номеру телефона",
"identity_server_no_token": "Не найден токен доступа для идентификации",
"identity_server_not_set": "Сервер идентификации не установлен",
"invalid_phone_number": "Предоставленный номер телефона, по-видимому, недействителен.",
"language_section": "Язык",
"msisdn_adding_unsupported_by_hs": "Этот домашний сервер не позволяет добавлять телефонные номера в вашу учетную запись.",
"msisdn_in_use": "Этот номер телефона уже используется",
"msisdn_label": "Номер телефона",
"msisdn_verification_field_label": "Код подтверждения",
@ -2707,6 +2779,9 @@
"inline_url_previews_room": "Включить предпросмотр ссылок для участников этой комнаты по умолчанию",
"inline_url_previews_room_account": "Включить предпросмотр ссылок в этой комнате (влияет только на вас)",
"insert_trailing_colon_mentions": "Вставлять двоеточие после упоминания пользователя в начале сообщения",
"invite_controls": {
"default_label": "Разрешить пользователям приглашать вас в комнаты"
},
"jump_to_bottom_on_send": "Перейти к нижней части временной шкалы, когда вы отправляете сообщение",
"key_backup": {
"setup_secure_backup": {
@ -2763,6 +2838,8 @@
"media_preview": {
"hide_avatars": "Скрыть аватары комнаты и приглашающего",
"hide_media": "Всегда скрывать",
"media_preview_description": "Скрытый медиафайл всегда можно отобразить, нажав на него.",
"media_preview_label": "Показать медиафайлы в хронологии",
"show_in_private": "В личных комнатах",
"show_media": "Всегда показывать"
},
@ -2821,9 +2898,14 @@
"rule_suppress_notices": "Сообщения от ботов",
"rule_tombstone": "При обновлении комнат",
"show_message_desktop_notification": "Показывать текст сообщения в уведомлениях на рабочем столе",
"sounds_release_announcement": {
"description": "Ваши уведомления и сигналы вызова были обновлены — теперь они понятнее, быстрее и менее отвлекающие.",
"title": "Мы обновили ваши звуки"
},
"voip": "Аудио и видео звонки"
},
"preferences": {
"Electron.enableContentProtection": "Предотвратить захват содержимого окна другими приложениями",
"Electron.enableHardwareAcceleration": "Включите аппаратное ускорение (перезапустите %(appName)s, чтобы настройки вступили в силу)",
"always_show_menu_bar": "Всегда показывать строку меню",
"autocomplete_delay": "Задержка автодополнения (мс)",
@ -2832,6 +2914,7 @@
"composer_heading": "Редактор",
"default_timezone": "Браузер по умолчанию (%(timezone)s)",
"dialog_title": "<strong>Настройки:</strong> Параметры",
"enable_content_protection": "Включить защиту контента",
"enable_hardware_acceleration": "Включить аппаратное ускорение",
"enable_tray_icon": "Показывать значок в трее и сворачивать в него окно при закрытии",
"keyboard_heading": "Горячие клавиши",
@ -2845,6 +2928,7 @@
"room_list_heading": "Список комнат",
"show_avatars_pills": "Показывать аватары в упоминаниях пользователей, комнатах и событиях",
"show_polls_button": "Показывать кнопку опроса",
"startup_window_behaviour_label": "Запуск и поведение окна",
"surround_text": "Обводить выделенный текст при вводе специальных символов",
"time_heading": "Отображение времени",
"user_timezone": "Установить часовой пояс"
@ -2885,7 +2969,7 @@
"message_search_unsupported_web": "%(brand)s не может безопасно кэшировать зашифрованные сообщения локально во время работы в веб-браузере. Используйте <desktopLink>%(brand)s Desktop</desktopLink>, чтобы зашифрованные сообщения появились в результатах поиска.",
"record_session_details": "Записывать название клиента, версию и URL-адрес для более лёгкого распознавания сеансов в менеджере сеансов",
"send_analytics": "Отправить данные аналитики",
"strict_encryption": "Никогда не отправлять неподтверждённым сеансам зашифрованные сообщения через этот сеанс"
"strict_encryption": "Отправлять сообщения только проверенным пользователям"
},
"send_read_receipts": "Уведомлять о прочтении",
"send_read_receipts_unsupported": "Ваш сервер не поддерживает отключение отправки уведомлений о прочтении.",
@ -3025,6 +3109,12 @@
"spaces_explainer": "Пространства — это способ сгруппировать комнаты и людей. Помимо пространств, в которых вы находитесь, вы также можете использовать готовые помещения.",
"title": "Боковая панель"
},
"start_automatically": {
"disabled": "Нет",
"enabled": "Да",
"label": "Запустить %(brand)s при входе в систему",
"minimised": "Минимизированный"
},
"tac_only_notifications": "Показывать уведомления только в центре активностей обсуждений",
"use_12_hour_format": "Отображать время в 12 часовом формате (напр. 2:30pm)",
"use_command_enter_send_message": "Cmd + Enter, чтобы отправить сообщение",
@ -3108,6 +3198,8 @@
"jumptodate": "Перейти к заданной дате в ленте сообщений",
"jumptodate_invalid_input": "Мы не смогли распознать заданную дату (%(inputDate)s). Попробуйте использовать формат ГГГГ-ММ-ДД.",
"lenny": "Добавляет ( ͡° ͜ʖ ͡°) в начало сообщения",
"manual_device_verification_confirm_description": "Это позволит другому устройству отправлять и получать сообщения от вашего имени. ЕСЛИ КТО-ТО СКАЗАЛ ВАМ ВСТАВИТЬ ЗДЕСЬ ЧТО-ТО, ВЕРОЯТНО, ВАС ПЫТАЮТСЯ ОБМАНУТЬ! Вы уверены, что хотите подтвердить это другое устройство?",
"manual_device_verification_confirm_title": "Внимание: ручная проверка устройства",
"me": "Отображение действий",
"msg": "Отправить сообщение данному пользователю",
"myavatar": "Меняет изображение профиля во всех комнатах",
@ -3148,7 +3240,7 @@
"upgraderoom": "Обновляет комнату до новой версии",
"upgraderoom_permission_error": "У вас нет необходимых разрешений для использования этой команды.",
"usage": "Использование",
"verify": "Проверяет пользователя, сеанс и публичные ключи",
"verify": "Проверь вручную одно из своих устройств",
"view": "Просмотр комнаты с указанным адресом",
"whois": "Показать информацию о пользователе"
},
@ -3258,6 +3350,7 @@
"heading_without_query": "Поиск",
"join_button_text": "Присоединиться к %(roomAddress)s",
"keyboard_scroll_hint": "Используйте <arrows/> для прокрутки",
"messages_label": "Сообщения",
"other_rooms_in_space": "Прочие комнаты в %(spaceName)s",
"public_rooms_label": "Публичные комнаты",
"public_spaces_label": "Публичное пространство",
@ -3365,6 +3458,7 @@
"historical_event_unverified_device": "Вам необходимо подтвердить это устройство для доступа к истории сообщений.",
"historical_event_user_not_joined": "У вас нет доступа к этому сообщению",
"sender_identity_previously_verified": "Подтвержденная личность изменилась",
"sender_unsigned_device": "Отправлено с незащищенного устройства",
"unable_to_decrypt": "Не удалось расшифровать сообщение"
},
"disambiguated_profile": "%(displayName)s (%(matrixId)s)",
@ -3390,6 +3484,7 @@
"unable_to_find": "Попытка загрузить выбранный интервал истории чата этой комнаты не удалась, так как запрошенный элемент не найден."
},
"m.audio": {
"audio_player": "Аудиоплеер",
"error_downloading_audio": "Ошибка загрузки аудио",
"error_processing_audio": "Ошибка обработки звукового сообщения",
"error_processing_voice_message": "Ошибка при обработке голосового сообщения",
@ -3569,7 +3664,8 @@
},
"m.sticker": "%(senderDisplayName)s отправил(а) стикер.",
"m.video": {
"error_decrypting": "Ошибка расшифровки видео"
"error_decrypting": "Ошибка расшифровки видео",
"show_video": "Показать видео"
},
"m.widget": {
"added": "Виджет %(widgetName)s был добавлен %(senderName)s",
@ -3832,6 +3928,7 @@
"description": "Чтобы создать ссылку для совместного доступа, сделайте эту комнату <b>общедоступной</b> и разрешите пользователям <b>запрашивать присоединение</b>. Это позволит гостям присоединиться без приглашения.",
"dont_change_description": "Кроме того, вы можете провести звонок в отдельной комнате.",
"no_change": "Я не хочу менять уровень доступа.",
"revert_access_description": "(Это можно вернуть к прежнему значению в настройках комнаты: <b>Безопасность и конфиденциальность</b> / <b>Доступ</b>)",
"title": "Изменить уровень доступа в комнату"
},
"upload_failed_generic": "Файл '%(fileName)s' не был загружен.",
@ -3952,6 +4049,7 @@
"connection_lost": "Соединение с сервером потеряно",
"connection_lost_description": "Вы не можете совершать вызовы без подключения к серверу.",
"consulting": "Общение с %(transferTarget)s. <a>Перевод на %(transferee)s</a>",
"decline_call": "Отклонить",
"default_device": "Устройство по умолчанию",
"dial": "Набор",
"dialpad": "Панель набора номера",
@ -4003,6 +4101,7 @@
"show_sidebar_button": "Показать боковую панель",
"silence": "Тихий вызов",
"silenced": "Оповещения приглушены",
"skip_lobby_toggle_option": "Присоединиться прямо сейчас",
"start_screenshare": "Начать делиться экраном",
"stop_screenshare": "Перестать делиться экраном",
"too_many_calls": "Слишком много звонков",
@ -4023,9 +4122,11 @@
"user_busy_description": "Вызываемый пользователь занят.",
"user_is_presenting": "%(sharerName)s показывает",
"video_call": "Видеовызов",
"video_call_incoming": "Входящий видеозвонок",
"video_call_started": "Начался видеозвонок",
"video_call_using": "Видеозвонок с использованием:",
"voice_call": "Голосовой вызов",
"voice_call_incoming": "Входящий голосовой вызов",
"you_are_presenting": "Вы представляете"
},
"web_default_device_name": "%(appName)s: %(browserName)s на %(osName)s",

View File

@ -610,6 +610,7 @@
"video": "Video",
"video_room": "Video miestnosť",
"view_message": "Zobraziť správu",
"voice": "Hlas",
"warning": "Upozornenie"
},
"composer": {
@ -4175,9 +4176,11 @@
"user_busy_description": "Volaný používateľ má obsadené.",
"user_is_presenting": "%(sharerName)s prezentuje",
"video_call": "Video hovor",
"video_call_incoming": "Prichádzajúci videohovor",
"video_call_started": "Videohovor bol spustený",
"video_call_using": "Videohovor pomocou:",
"voice_call": "Hlasový hovor",
"voice_call_incoming": "Prichádzajúci hlasový hovor",
"you_are_presenting": "Prezentujete"
},
"web_default_device_name": "%(appName)s: %(browserName)s na %(osName)s",

View File

@ -69,7 +69,7 @@ class LifecycleStore extends AsyncStore<IState> {
dis.dispatch(deferredAction);
break;
}
case "on_client_not_viable":
case Action.ClientNotViable:
case Action.OnLoggedOut:
this.reset();
break;

View File

@ -77,7 +77,7 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro
this.matrixClient = payload.matrixClient;
await this.onReady();
}
} else if (payload.action === "on_client_not_viable" || payload.action === Action.OnLoggedOut) {
} else if (payload.action === Action.ClientNotViable || payload.action === Action.OnLoggedOut) {
if (this.matrixClient) {
await this.onNotReady();
this.matrixClient = undefined;

View File

@ -285,7 +285,7 @@ export class RoomViewStore extends EventEmitter {
break;
}
case "on_client_not_viable":
case Action.ClientNotViable:
case Action.OnLoggedOut:
this.reset();
break;

View File

@ -218,7 +218,7 @@ export default class ElectronPlatform extends BasePlatform {
this.electron.send("app_onAction", payload);
}
if (payload.action === "client_started") {
if (payload.action === Action.ClientStarted) {
this.clientStartedPromiseWithResolvers.resolve();
}
}

View File

@ -53,7 +53,7 @@ export default class WebPlatform extends BasePlatform {
super.onAction(payload);
switch (payload.action) {
case "client_started":
case Action.ClientStarted:
// Defer drawing the toast until the client is started as the lifecycle methods reset the ToastStore right before
this.registerServiceWorkerPromise.catch(this.handleServiceWorkerRegistrationError);
break;

View File

@ -275,13 +275,6 @@ describe("DeviceListener", () => {
});
describe("recheck", () => {
it("does nothing when cross signing feature is not supported", async () => {
mockClient!.isVersionSupported.mockResolvedValue(false);
await createAndStart();
expect(mockClient!.isVersionSupported).toHaveBeenCalledWith("v1.1");
expect(mockCrypto!.isCrossSigningReady).not.toHaveBeenCalled();
});
it("does nothing when crypto is not enabled", async () => {
mockClient!.getCrypto.mockReturnValue(undefined);
await createAndStart();

View File

@ -21,7 +21,7 @@ import { render } from "jest-matrix-react";
import { type ReactElement } from "react";
import { type Mocked, mocked } from "jest-mock";
import { textForEvent } from "../../src/TextForEvent";
import { hasText, textForEvent } from "../../src/TextForEvent";
import SettingsStore from "../../src/settings/SettingsStore";
import { createTestClient, stubClient } from "../test-utils";
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
@ -700,4 +700,20 @@ describe("TextForEvent", () => {
).toEqual(result);
});
});
describe("hasText", () => {
it("should return true for a known handler given an invalid event", async () => {
const cli = stubClient();
const ev = new MatrixEvent({
type: "m.room.name",
content: {
name: { foo: "bar" },
},
state_key: "",
room_id: "!roomId",
sender: cli.getUserId()!,
});
expect(hasText(ev, cli, false)).toBe(true);
});
});
});

View File

@ -547,15 +547,15 @@ describe("<MatrixChat />", () => {
getComponent({ realQueryParams });
defaultDispatcher.dispatch({
action: "will_start_client",
action: Action.WillStartClient,
});
// client successfully started
await waitFor(() =>
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "client_started" }),
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: Action.ClientStarted }),
);
// check we get to logged in view
await waitForSyncAndLoad(loginClient, true);
// set up keys screen is rendered
expect(screen.getByText("Setting up keys")).toBeInTheDocument();
});
it("should persist device language when available", async () => {
@ -1166,14 +1166,16 @@ describe("<MatrixChat />", () => {
// Given force_verification is on (outer describe)
// And we just logged in via OIDC (inner describe)
mocked(loginClient.getCrypto()!.userHasCrossSigningKeys).mockResolvedValue(true);
// When we load the page
getComponent({ realQueryParams });
defaultDispatcher.dispatch({
action: "will_start_client",
action: Action.WillStartClient,
});
await waitFor(() =>
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "client_started" }),
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: Action.ClientStarted }),
);
// Then we are not allowed in - we are being asked to verify
@ -1340,22 +1342,8 @@ describe("<MatrixChat />", () => {
expect(screen.getByRole("heading", { name: "Welcome Ernie" })).toBeInTheDocument();
});
it("should go straight to logged in view when user does not have cross signing keys and server does not support cross signing", async () => {
loginClient.doesServerSupportUnstableFeature.mockResolvedValue(false);
await getComponentAndLogin(false);
expect(loginClient.doesServerSupportUnstableFeature).toHaveBeenCalledWith(
"org.matrix.e2e_cross_signing",
);
// logged in
await screen.findByLabelText("User menu");
});
describe("when server supports cross signing and user does not have cross signing setup", () => {
beforeEach(() => {
loginClient.doesServerSupportUnstableFeature.mockResolvedValue(true);
jest.spyOn(loginClient.getCrypto()!, "userHasCrossSigningKeys").mockResolvedValue(false);
});
@ -1400,8 +1388,6 @@ describe("<MatrixChat />", () => {
});
it("should go to setup e2e screen", async () => {
loginClient.doesServerSupportUnstableFeature.mockResolvedValue(true);
await getComponentAndLogin();
expect(loginClient.getCrypto()!.userHasCrossSigningKeys).toHaveBeenCalled();
@ -1424,9 +1410,7 @@ describe("<MatrixChat />", () => {
expect(screen.getByText("Confirm your identity")).toBeInTheDocument();
});
it("should setup e2e when server supports cross signing", async () => {
loginClient.doesServerSupportUnstableFeature.mockResolvedValue(true);
it("should setup e2e", async () => {
await getComponentAndLogin();
expect(loginClient.getCrypto()!.userHasCrossSigningKeys).toHaveBeenCalled();
@ -1584,11 +1568,11 @@ describe("<MatrixChat />", () => {
it("should continue to post login setup when no session is found in local storage", async () => {
getComponent({ realQueryParams });
defaultDispatcher.dispatch({
action: "will_start_client",
action: Action.WillStartClient,
});
// logged in but waiting for sync screen
await screen.findByText("Logout");
// set up keys screen is rendered
expect(await screen.findByText("Setting up keys")).toBeInTheDocument();
});
});
});
@ -1844,7 +1828,7 @@ describe("<MatrixChat />", () => {
getComponent({});
defaultDispatcher.dispatch({
action: "will_start_client",
action: Action.WillStartClient,
});
await flushPromises();
mockClient.emit(CryptoEvent.KeyBackupFailed, "error code");
@ -1867,7 +1851,7 @@ describe("<MatrixChat />", () => {
getComponent({});
defaultDispatcher.dispatch({
action: "will_start_client",
action: Action.WillStartClient,
});
await flushPromises();
mockClient.emit(CryptoEvent.KeyBackupFailed, "error code");

View File

@ -0,0 +1,35 @@
/*
Copyright 2025 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { render } from "jest-matrix-react";
import { mocked } from "jest-mock";
import E2eSetup from "../../../../../src/components/structures/auth/E2eSetup.tsx";
import { InitialCryptoSetupStore } from "../../../../../src/stores/InitialCryptoSetupStore.ts";
afterEach(() => jest.restoreAllMocks());
describe("LeftPanel", () => {
it("should call `onCancelled` when the user clicks the cancel button", () => {
const mockInitialCryptoSetupStore = {
getStatus: jest.fn(),
on: jest.fn(),
off: jest.fn(),
};
jest.spyOn(InitialCryptoSetupStore, "sharedInstance").mockReturnValue(mockInitialCryptoSetupStore as any);
// We need the setup process to have failed, for the dialog to present a cancel button.
mocked(mockInitialCryptoSetupStore.getStatus).mockReturnValue("error");
const onCancelled = jest.fn();
const { getByRole } = render(<E2eSetup onCancelled={onCancelled} />);
getByRole("button", { name: "Cancel" }).click();
expect(onCancelled).toHaveBeenCalled();
});
});

View File

@ -31,11 +31,9 @@ describe("InitialCryptoSetupDialog", () => {
});
it("should show a spinner while the setup is in progress", async () => {
const onFinished = jest.fn();
storeMock.getStatus.mockReturnValue("in_progress");
render(<InitialCryptoSetupDialog onFinished={onFinished} />);
render(<InitialCryptoSetupDialog onCancelled={jest.fn()} />);
expect(screen.getByTestId("spinner")).toBeInTheDocument();
});
@ -43,16 +41,15 @@ describe("InitialCryptoSetupDialog", () => {
it("should display an error if setup has failed", async () => {
storeMock.getStatus.mockReturnValue("error");
render(<InitialCryptoSetupDialog onFinished={jest.fn()} />);
render(<InitialCryptoSetupDialog onCancelled={jest.fn()} />);
await expect(await screen.findByRole("button", { name: "Retry" })).toBeInTheDocument();
});
it("calls retry when retry button pressed", async () => {
const onFinished = jest.fn();
storeMock.getStatus.mockReturnValue("error");
render(<InitialCryptoSetupDialog onFinished={onFinished} />);
render(<InitialCryptoSetupDialog onCancelled={jest.fn()} />);
await userEvent.click(await screen.findByRole("button", { name: "Retry" }));

View File

@ -14,6 +14,7 @@ import { Container, WidgetLayoutStore } from "../../../src/stores/widgets/Widget
import { stubClient } from "../../test-utils";
import defaultDispatcher from "../../../src/dispatcher/dispatcher";
import SettingsStore from "../../../src/settings/SettingsStore";
import { Action } from "../../../src/dispatcher/actions.ts";
// setup test env values
const roomId = "!room:server";
@ -196,12 +197,7 @@ describe("WidgetLayoutStore", () => {
it("should clear the layout if the client is not viable", () => {
store.recalculateRoom(mockRoom);
defaultDispatcher.dispatch(
{
action: "on_client_not_viable",
},
true,
);
defaultDispatcher.dispatch({ action: Action.ClientNotViable }, true);
expect(store.getContainerWidgets(mockRoom, Container.Top)).toEqual([]);
expect(store.getContainerWidgets(mockRoom, Container.Center)).toEqual([]);

View File

@ -132,7 +132,7 @@ describe("ElectronPlatform", () => {
new ElectronPlatform();
dispatcher.dispatch(
{
action: "client_started",
action: Action.ClientStarted,
},
true,
);

View File

@ -15,6 +15,7 @@ import { setupLanguageMock } from "../../../setup/setupLanguage";
import ToastStore from "../../../../src/stores/ToastStore.ts";
import defaultDispatcher from "../../../../src/dispatcher/dispatcher.ts";
import { emitPromise } from "../../../test-utils";
import { Action } from "../../../../src/dispatcher/actions.ts";
fetchMock.config.overwriteRoutes = true;
@ -49,7 +50,7 @@ describe("WebPlatform", () => {
};
new WebPlatform();
defaultDispatcher.dispatch({ action: "client_started" });
defaultDispatcher.dispatch({ action: Action.ClientStarted });
await emitPromise(ToastStore.sharedInstance(), "update");
const toasts = ToastStore.sharedInstance().getToasts();
expect(toasts).toHaveLength(1);

View File

@ -4218,7 +4218,7 @@
classnames "^2.5.1"
vaul "^1.0.0"
"@vector-im/matrix-wysiwyg-wasm@link:../../Library/Caches/Yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm":
"@vector-im/matrix-wysiwyg-wasm@link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm":
version "0.0.0"
"@vector-im/matrix-wysiwyg@2.40.0":
@ -9695,6 +9695,14 @@ matrix-widget-api@^1.14.0:
"@types/events" "^3.0.0"
events "^3.2.0"
matrix-widget-api@^1.14.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.14.0.tgz#aa90c40ace27d3165299f7dbc760a53001ce1446"
integrity sha512-DDvZGOQhI/rilPWg5VlLN7pHIsPt0Jt14lsuHDP+KU+fmpAQNITJ6aIld1ZoXWsrVGv2PS3x6K/MHtfruIOQJQ==
dependencies:
"@types/events" "^3.0.0"
events "^3.2.0"
mdn-data@2.0.28:
version "2.0.28"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba"