Compare commits
2 Commits
t3chguy/re
...
t3chguy/st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a87807b0d9 | ||
|
|
a073cb115f |
@@ -266,9 +266,6 @@ module.exports = {
|
||||
parserOptions: {
|
||||
project: ["./playwright/tsconfig.json"],
|
||||
},
|
||||
rules: {
|
||||
"react-hooks/rules-of-hooks": ["off"],
|
||||
},
|
||||
},
|
||||
],
|
||||
settings: {
|
||||
|
||||
3
.github/workflows/release_prepare.yml
vendored
@@ -20,9 +20,6 @@ on:
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
# The order is specified bottom-up to avoid any races for allchange
|
||||
REPOS: matrix-js-sdk element-web element-desktop
|
||||
steps:
|
||||
- name: Checkout Element Desktop
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -1 +1 @@
|
||||
22
|
||||
20
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Builder
|
||||
FROM --platform=$BUILDPLATFORM node:22-bullseye as builder
|
||||
FROM --platform=$BUILDPLATFORM node:20-bullseye as builder
|
||||
|
||||
# Support custom branch of the js-sdk. This also helps us build images of element-web develop.
|
||||
ARG USE_CUSTOM_SDKS=false
|
||||
|
||||
@@ -46,13 +46,5 @@
|
||||
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
|
||||
"setting_defaults": {
|
||||
"RustCrypto.staged_rollout_percent": 60
|
||||
},
|
||||
"features": {
|
||||
"feature_video_rooms": true,
|
||||
"feature_group_calls": true,
|
||||
"feature_element_call_video_rooms": true
|
||||
},
|
||||
"element_call": {
|
||||
"url": "https://call.element.io"
|
||||
}
|
||||
}
|
||||
|
||||
10
package.json
@@ -84,7 +84,7 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@formatjs/intl-segmenter": "^11.5.7",
|
||||
"@matrix-org/analytics-events": "^0.28.0",
|
||||
"@matrix-org/analytics-events": "^0.26.0",
|
||||
"@matrix-org/emojibase-bindings": "^1.3.3",
|
||||
"@vector-im/matrix-wysiwyg": "2.37.13",
|
||||
"@matrix-org/react-sdk-module-api": "^2.4.0",
|
||||
@@ -148,7 +148,7 @@
|
||||
"tar-js": "^0.3.0",
|
||||
"temporal-polyfill": "^0.2.5",
|
||||
"ua-parser-js": "^1.0.2",
|
||||
"uuid": "^11.0.0",
|
||||
"uuid": "^10.0.0",
|
||||
"what-input": "^5.2.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -208,7 +208,7 @@
|
||||
"@types/qrcode": "^1.3.5",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-beautiful-dnd": "^13.0.0",
|
||||
"@types/react-dom": "18.3.1",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"@types/react-transition-group": "^4.4.0",
|
||||
"@types/sanitize-html": "2.13.0",
|
||||
"@types/sdp-transform": "^2.4.6",
|
||||
@@ -219,7 +219,7 @@
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"axe-core": "4.10.2",
|
||||
"axe-core": "4.10.0",
|
||||
"babel-jest": "^29.0.0",
|
||||
"babel-loader": "^9.0.0",
|
||||
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
|
||||
@@ -242,7 +242,7 @@
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-matrix-org": "^2.0.2",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-plugin-unicorn": "^56.0.0",
|
||||
"express": "^4.18.2",
|
||||
"fake-indexeddb": "^6.0.0",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/playwright:v1.48.2-jammy
|
||||
FROM mcr.microsoft.com/playwright:v1.48.0-jammy
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
|
||||
@@ -60,11 +60,6 @@ test.describe("User verification", () => {
|
||||
// Accept
|
||||
await toast.getByRole("button", { name: "Verify User" }).click();
|
||||
|
||||
// Wait for the QR code to be rendered. If we don't do this, then the QR code can be rendered just as
|
||||
// Playwright tries to click the "Verify by emoji" button, which seems to make it miss the button.
|
||||
// (richvdh: I thought Playwright was supposed to be resilient to such things, but empirically not.)
|
||||
await expect(page.getByAltText("QR Code")).toBeVisible();
|
||||
|
||||
// request verification by emoji
|
||||
await page.locator("#mx_RightPanel").getByRole("button", { name: "Verify by emoji" }).click();
|
||||
|
||||
@@ -106,20 +101,13 @@ test.describe("User verification", () => {
|
||||
const toast = await toasts.getToast("Verification requested");
|
||||
await toast.getByRole("button", { name: "Verify User" }).click();
|
||||
|
||||
// Wait for the QR code to be rendered. If we don't do this, then the QR code can be rendered just as
|
||||
// Playwright tries to click the "Verify by emoji" button, which seems to make it miss the button.
|
||||
// (richvdh: I thought Playwright was supposed to be resilient to such things, but empirically not.)
|
||||
await expect(page.getByAltText("QR Code")).toBeVisible();
|
||||
|
||||
// request verification by emoji
|
||||
await page.locator("#mx_RightPanel").getByRole("button", { name: "Verify by emoji" }).click();
|
||||
|
||||
/* on the bot side, wait for the verifier to exist ... */
|
||||
const botVerifier = await awaitVerifier(bobVerificationRequest);
|
||||
// ... and confirm. We expect the verification to fail; we catch the error on the DOM side
|
||||
// to stop playwright marking the evaluate as failing in the UI.
|
||||
const botVerification = botVerifier.evaluate((verifier) => verifier.verify().catch(() => {}));
|
||||
|
||||
// ... confirm ...
|
||||
botVerifier.evaluate((verifier) => verifier.verify()).catch(() => {});
|
||||
// ... and abort the verification
|
||||
await page.getByRole("button", { name: "They don't match" }).click();
|
||||
|
||||
@@ -127,8 +115,6 @@ test.describe("User verification", () => {
|
||||
await expect(dialog.getByText("Your messages are not secure")).toBeVisible();
|
||||
await dialog.getByRole("button", { name: "OK" }).click();
|
||||
await expect(dialog).not.toBeVisible();
|
||||
|
||||
await botVerification;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import { randB64Bytes } from "../../utils/rand";
|
||||
// Docker tag to use for synapse docker image.
|
||||
// We target a specific digest as every now and then a Synapse update will break our CI.
|
||||
// This digest is updated by the playwright-image-updates.yaml workflow periodically.
|
||||
const DOCKER_TAG = "develop@sha256:eee8e1551640da28f017b93dbf2264a89a092709d12475c785a52c91cf580c6a";
|
||||
const DOCKER_TAG = "develop@sha256:85dc2cf25f45ee91fd87efa0ddf2220a5933d212ed656886d5f3832ae3a9ddaf";
|
||||
|
||||
async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Omit<HomeserverConfig, "dockerUrl">> {
|
||||
const templateDir = path.join(__dirname, "templates", opts.template);
|
||||
|
||||
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 198 KiB |
@@ -286,6 +286,7 @@
|
||||
@import "./views/rooms/_HistoryTile.pcss";
|
||||
@import "./views/rooms/_IRCLayout.pcss";
|
||||
@import "./views/rooms/_JumpToBottomButton.pcss";
|
||||
@import "./views/rooms/_LegacyRoomHeader.pcss";
|
||||
@import "./views/rooms/_LinkPreviewGroup.pcss";
|
||||
@import "./views/rooms/_LinkPreviewWidget.pcss";
|
||||
@import "./views/rooms/_LiveContentSummary.pcss";
|
||||
|
||||
@@ -181,6 +181,11 @@ Please see LICENSE files in the repository root for full details.
|
||||
}
|
||||
}
|
||||
|
||||
/* Rooms with immersive content */
|
||||
.mx_RoomView_immersive .mx_LegacyRoomHeader_wrapper {
|
||||
border: unset;
|
||||
}
|
||||
|
||||
.mx_RoomView_inCall {
|
||||
.mx_RoomView_statusAreaBox_line {
|
||||
margin-top: 2px;
|
||||
|
||||
@@ -18,9 +18,10 @@ Please see LICENSE files in the repository root for full details.
|
||||
.mx_EditableItem_delete {
|
||||
@mixin customisedCancelButton;
|
||||
order: 3;
|
||||
margin-right: 5px;
|
||||
vertical-align: middle;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background-color: $alert;
|
||||
mask-size: 100%;
|
||||
}
|
||||
@@ -41,7 +42,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
.mx_EditableItem_item {
|
||||
flex: auto 1 0;
|
||||
order: 1;
|
||||
width: calc(100% - 28px); /* leave space for the remove button */
|
||||
width: calc(100% - 14px); /* leave space for the remove button */
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
281
res/css/views/rooms/_LegacyRoomHeader.pcss
Normal file
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--RoomHeader-indicator-dot-size: 8px;
|
||||
--RoomHeader-indicator-dot-offset: -3px;
|
||||
--RoomHeader-indicator-pulseColor: $alert;
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader {
|
||||
flex: 0 0 50px;
|
||||
border-bottom: 1px solid $primary-hairline-color;
|
||||
background-color: $background;
|
||||
|
||||
.mx_LegacyRoomHeader_icon {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
|
||||
&.mx_LegacyRoomHeader_icon_video {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
background-color: $secondary-content;
|
||||
mask-image: url("$(res)/img/element-icons/call/video-call.svg");
|
||||
mask-size: 100%;
|
||||
}
|
||||
|
||||
&.mx_E2EIcon {
|
||||
margin: 0;
|
||||
height: 100%; /* To give the tooltip room to breathe */
|
||||
}
|
||||
}
|
||||
|
||||
.mx_CallDuration {
|
||||
margin-top: calc(($font-15px - $font-13px) / 2); /* To align with the name */
|
||||
font-size: $font-13px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_wrapper {
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
padding: 10px 20px 9px 16px;
|
||||
border-bottom: 1px solid $separator;
|
||||
|
||||
.mx_InviteOnlyIcon_large {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mx_BetaCard_betaPill {
|
||||
margin-right: $spacing-8;
|
||||
}
|
||||
|
||||
/* The container of E2EIcon in the legacy header needs to have its height set */
|
||||
& > span {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_name {
|
||||
flex: 0 1 auto;
|
||||
overflow: hidden;
|
||||
color: $primary-content;
|
||||
font: var(--cpd-font-heading-sm-semibold);
|
||||
font-weight: var(--cpd-font-weight-semibold);
|
||||
min-height: 24px;
|
||||
align-items: center;
|
||||
border-radius: 6px;
|
||||
margin: 0 3px;
|
||||
padding: 1px 4px;
|
||||
display: flex;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: $quinary-content;
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_nametext {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_chevron {
|
||||
align-self: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
mask-position: center;
|
||||
mask-size: 20px;
|
||||
mask-repeat: no-repeat;
|
||||
mask-image: url("$(res)/img/feather-customised/chevron-down.svg");
|
||||
background-color: $tertiary-content;
|
||||
}
|
||||
|
||||
&.mx_LegacyRoomHeader_name--textonly {
|
||||
cursor: unset;
|
||||
|
||||
&:hover {
|
||||
background-color: unset;
|
||||
}
|
||||
}
|
||||
|
||||
&[aria-expanded="true"] {
|
||||
background-color: $separator;
|
||||
|
||||
.mx_LegacyRoomHeader_chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_settingsHint {
|
||||
color: $settings-grey-fg-color !important;
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_searchStatus {
|
||||
font-weight: normal;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.mx_RoomTopic {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_topic {
|
||||
$lines: 2;
|
||||
|
||||
flex: 1;
|
||||
color: $secondary-content;
|
||||
font: var(--cpd-font-body-sm-regular);
|
||||
line-height: 1rem;
|
||||
max-height: calc(1rem * $lines);
|
||||
|
||||
overflow: hidden;
|
||||
-webkit-line-clamp: $lines; /* See: https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp */
|
||||
-webkit-box-orient: vertical;
|
||||
display: -webkit-box;
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_topic .mx_Emoji {
|
||||
/* Undo font size increase to prevent vertical cropping and ensure the same size */
|
||||
/* as in plain text emojis */
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_avatar {
|
||||
flex: 0;
|
||||
margin: 0 7px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_button_unreadIndicator_bg {
|
||||
position: absolute;
|
||||
right: var(--RoomHeader-indicator-dot-offset);
|
||||
top: var(--RoomHeader-indicator-dot-offset);
|
||||
margin: 4px;
|
||||
width: var(--RoomHeader-indicator-dot-size);
|
||||
height: var(--RoomHeader-indicator-dot-size);
|
||||
border-radius: 50%;
|
||||
transform: scale(1.6);
|
||||
transform-origin: center center;
|
||||
background: $background;
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_button_unreadIndicator {
|
||||
position: absolute;
|
||||
right: var(--RoomHeader-indicator-dot-offset);
|
||||
top: var(--RoomHeader-indicator-dot-offset);
|
||||
margin: 4px;
|
||||
|
||||
&.mx_Indicator_highlight {
|
||||
background: var(--cpd-color-icon-critical-primary);
|
||||
box-shadow: var(--cpd-color-icon-critical-primary);
|
||||
}
|
||||
|
||||
&.mx_Indicator_notification {
|
||||
background: var(--cpd-color-icon-success-primary);
|
||||
box-shadow: var(--cpd-color-icon-success-primary);
|
||||
}
|
||||
|
||||
&.mx_Indicator_activity {
|
||||
background: var(--cpd-color-icon-primary);
|
||||
box-shadow: var(--cpd-color-icon-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_forgetButton::before {
|
||||
mask-image: url("$(res)/img/element-icons/leave.svg");
|
||||
width: 26px;
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_appsButton::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/apps.svg");
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_appsButton_highlight::before {
|
||||
background-color: $accent;
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_searchButton::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/search-inset.svg");
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_inviteButton::before {
|
||||
mask-image: url("$(res)/img/element-icons/room/invite.svg");
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_voiceCallButton::before {
|
||||
mask-image: url("$(res)/img/element-icons/call/voice-call.svg");
|
||||
|
||||
/* The call button SVG is padded slightly differently, so match it up to the size */
|
||||
/* of the other icons */
|
||||
mask-size: 20px;
|
||||
mask-position: center;
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_videoCallButton::before {
|
||||
mask-image: url("$(res)/img/element-icons/call/video-call.svg");
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_layoutButton--freedom::before,
|
||||
.mx_LegacyRoomHeader_freedomIcon::before {
|
||||
mask-image: url("$(res)/img/element-icons/call/freedom.svg");
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_layoutButton--spotlight::before,
|
||||
.mx_LegacyRoomHeader_spotlightIcon::before {
|
||||
mask-image: url("$(res)/img/element-icons/call/spotlight.svg");
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_closeButton {
|
||||
&::before {
|
||||
mask-image: url("@vector-im/compound-design-tokens/icons/close.svg");
|
||||
mask-size: 20px;
|
||||
mask-position: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: unset; /* remove background color on hover */
|
||||
|
||||
&::before {
|
||||
background-color: $icon-button-color; /* set the default background color */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_minimiseButton::before {
|
||||
mask-image: url("$(res)/img/element-icons/reduce.svg");
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader_layoutMenu .mx_IconizedContextMenu_icon::before {
|
||||
content: "";
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: block;
|
||||
mask-position: center;
|
||||
mask-size: 20px;
|
||||
mask-repeat: no-repeat;
|
||||
background: $primary-content;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
.mx_LegacyRoomHeader_wrapper {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mx_LegacyRoomHeader {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
@@ -88,8 +88,3 @@ Please see LICENSE files in the repository root for full details.
|
||||
.mx_RoomHeader .mx_BaseAvatar {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mx_RoomHeader_videoCallOption {
|
||||
/* Workaround for https://github.com/element-hq/compound/issues/331 */
|
||||
min-width: 240px;
|
||||
}
|
||||
|
||||
12
res/img/camera.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="17px" height="15px" viewBox="-1 -1 16 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.4.4 (17249) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>icon_camera</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="06a-Room-settings" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="06_4-Room-settings-admin" sketch:type="MSArtboardGroup" transform="translate(-248.000000, -71.000000)" fill="#454545">
|
||||
<path d="M255.5,76.25 C256.119795,76.25 256.649737,76.4700499 257.089844,76.9101562 C257.52995,77.3502626 257.75,77.8802052 257.75,78.5 C257.75,79.1197948 257.52995,79.6497374 257.089844,80.0898438 C256.649737,80.5299501 256.119795,80.75 255.5,80.75 C254.880205,80.75 254.350263,80.5299501 253.910156,80.0898438 C253.47005,79.6497374 253.25,79.1197948 253.25,78.5 C253.25,77.8802052 253.47005,77.3502626 253.910156,76.9101562 C254.350263,76.4700499 254.880205,76.25 255.5,76.25 L255.5,76.25 Z M261,73 C261.552086,73 262.023436,73.1953105 262.414062,73.5859375 C262.804689,73.9765645 263,74.4479139 263,75 L263,82 C263,82.5520861 262.804689,83.0234355 262.414062,83.4140625 C262.023436,83.8046895 261.552086,84 261,84 L250,84 C249.447914,84 248.976564,83.8046895 248.585938,83.4140625 C248.195311,83.0234355 248,82.5520861 248,82 L248,75 C248,74.4479139 248.195311,73.9765645 248.585938,73.5859375 C248.976564,73.1953105 249.447914,73 250,73 L251.75,73 L252.148438,71.9375 C252.247396,71.6822904 252.428384,71.4622405 252.691406,71.2773438 C252.954428,71.092447 253.223957,71 253.5,71 L257.5,71 C257.776043,71 258.045572,71.092447 258.308594,71.2773438 C258.571616,71.4622405 258.752604,71.6822904 258.851562,71.9375 L259.25,73 L261,73 Z M255.5,82 C256.463546,82 257.287757,81.6575555 257.972656,80.9726562 C258.657556,80.287757 259,79.4635465 259,78.5 C259,77.5364535 258.657556,76.712243 257.972656,76.0273438 C257.287757,75.3424445 256.463546,75 255.5,75 C254.536454,75 253.712243,75.3424445 253.027344,76.0273438 C252.342444,76.712243 252,77.5364535 252,78.5 C252,79.4635465 252.342444,80.287757 253.027344,80.9726562 C253.712243,81.6575555 254.536454,82 255.5,82 L255.5,82 Z" id="icon_camera" sketch:type="MSShapeGroup"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
3
res/img/element-icons/call/freedom.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.28571 3.66667C6.28571 2.74619 7.03191 2 7.95238 2H11.7619C12.6824 2 13.4286 2.74619 13.4286 3.66667V7.47619C13.4286 8.39666 12.6824 9.14286 11.7619 9.14286H7.95238C7.03191 9.14286 6.28571 8.39667 6.28571 7.47619V3.66667ZM16.5238 2C15.6033 2 14.8571 2.74619 14.8571 3.66667V7.47619C14.8571 8.39667 15.6033 9.14286 16.5238 9.14286H20.3333C21.2538 9.14286 22 8.39666 22 7.47619V3.66667C22 2.74619 21.2538 2 20.3333 2H16.5238ZM16.5238 10.5714C15.6033 10.5714 14.8571 11.3176 14.8571 12.2381V16.0476C14.8571 16.9681 15.6033 17.7143 16.5238 17.7143H20.3333C21.2538 17.7143 22 16.9681 22 16.0476V12.2381C22 11.3176 21.2538 10.5714 20.3333 10.5714H16.5238ZM3.63265 10.5714C2.73096 10.5714 2 11.3024 2 12.2041V20.3673C2 21.269 2.73097 22 3.63266 22H11.7959C12.6976 22 13.4286 21.269 13.4286 20.3673V12.2041C13.4286 11.3024 12.6976 10.5714 11.7959 10.5714H3.63265Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
3
res/img/element-icons/call/spotlight.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 17.8689V2.51551C4 2.06669 4.53728 1.83452 4.86986 2.13591C8.18767 5.14263 10.9111 7.48209 13.102 9.36399L13.102 9.36403C18.3295 13.8544 20.5243 15.7398 20.5243 17.8689C20.5243 19.4181 19.6538 20.0153 18.1044 20.79C16.5549 21.5648 14.4534 22 12.2621 22C10.0709 22 7.96938 21.5648 6.41992 20.79C4.87047 20.0153 4 18.9646 4 17.8689ZM12.2621 20.9673C16.2548 20.9673 19.4915 19.5801 19.4915 17.869C19.4915 16.1578 16.2548 14.7707 12.2621 14.7707C8.26947 14.7707 5.03277 16.1578 5.03277 17.869C5.03277 19.5801 8.26947 20.9673 12.2621 20.9673ZM16.2618 8.67876C16.1718 8.64549 16.1718 8.51831 16.2618 8.48504L17.84 7.90103C17.8683 7.89057 17.8906 7.86828 17.901 7.84001L18.4851 6.26174C18.5183 6.17182 18.6455 6.17182 18.6788 6.26174L19.2628 7.84001C19.2733 7.86828 19.2955 7.89057 19.3238 7.90103L20.9021 8.48504C20.992 8.51831 20.992 8.64549 20.9021 8.67876L19.3238 9.26277C19.2955 9.27323 19.2733 9.29552 19.2628 9.32379L18.6788 10.9021C18.6455 10.992 18.5183 10.992 18.4851 10.9021L17.901 9.32379C17.8906 9.29552 17.8683 9.27323 17.84 9.26277L16.2618 8.67876ZM13.2618 5.45232C13.1718 5.48559 13.1718 5.61276 13.2618 5.64604L14.0862 5.95111C14.1145 5.96157 14.1368 5.98386 14.1472 6.01213L14.4523 6.83657C14.4856 6.92649 14.6127 6.92649 14.646 6.83657L14.9511 6.01213C14.9615 5.98386 14.9838 5.96157 15.0121 5.95111L15.8365 5.64603C15.9265 5.61276 15.9265 5.48559 15.8365 5.45232L15.0121 5.14725C14.9838 5.13679 14.9615 5.1145 14.9511 5.08623L14.646 4.26178C14.6127 4.17187 14.4856 4.17187 14.4523 4.26178L14.1472 5.08623C14.1368 5.1145 14.1145 5.13679 14.0862 5.14725L13.2618 5.45232Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
4
res/img/element-icons/reduce.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.0759 13.6172C13.0273 13.7343 13.0004 13.8625 13 13.997C13 13.998 13 13.999 13 14L13 14.0007L13 20C13 20.5523 13.4477 21 14 21C14.5523 21 15 20.5523 15 20L15 16.4142L18.7929 20.2071C19.1834 20.5976 19.8166 20.5976 20.2071 20.2071C20.5976 19.8166 20.5976 19.1834 20.2071 18.7929L16.4142 15L20 15C20.5523 15 21 14.5523 21 14C21 13.4477 20.5523 13 20 13L14.0007 13L14 13C13.999 13 13.998 13 13.997 13C13.743 13.0008 13.4892 13.0977 13.295 13.2908C13.2943 13.2915 13.2936 13.2922 13.2929 13.2929C13.2922 13.2936 13.2915 13.2943 13.2908 13.295C13.196 13.3904 13.1243 13.5001 13.0759 13.6172ZM13.0759 13.6172C13.1262 13.4959 13.1996 13.3867 13.2908 13.295L13.0759 13.6172Z" fill="#737D8C"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.9241 10.3828C10.9727 10.2657 10.9996 10.1375 11 10.003C11 10.002 11 10.001 11 10V9.9993L11 4C11 3.44772 10.5523 3 10 3C9.44772 3 9 3.44772 9 4L9 7.58579L5.20711 3.79289C4.81658 3.40237 4.18342 3.40237 3.79289 3.79289C3.40237 4.18342 3.40237 4.81658 3.79289 5.20711L7.58579 9L4 9C3.44771 9 3 9.44771 3 10C3 10.5523 3.44771 11 4 11L9.9993 11H10C10.001 11 10.002 11 10.003 11C10.257 10.9992 10.5108 10.9023 10.705 10.7092C10.7057 10.7085 10.7064 10.7078 10.7071 10.7071C10.7078 10.7064 10.7085 10.7057 10.7092 10.705C10.804 10.6096 10.8757 10.4999 10.9241 10.3828ZM10.9241 10.3828C10.8738 10.5041 10.8004 10.6133 10.7092 10.705L10.9241 10.3828Z" fill="#737D8C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
4
res/img/feather-customised/edit.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14 2L18 6L7 17H3V13L14 2V2Z" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 22H21" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 333 B |
3
res/img/location/pointer.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="9" height="4" viewBox="0 0 9 4" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.5 4L0.602887 0.25L8.39711 0.250001L4.5 4Z" fill="#0DBD8B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 171 B |
3
res/img/markdown.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg height="45" viewBox="0 0 69 45" width="69" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m6 0h57c3.3137085 0 6 2.6862915 6 6v33c0 3.3137085-2.6862915 6-6 6h-57c-3.3137085 0-6-2.6862915-6-6v-33c0-3.3137085 2.6862915-6 6-6zm23.3703704 11c-.6518551 0-1.1841955.3860101-1.5970371 1.158042l-7.7244444 14.7006993-7.7896296-14.7006993c-.4128416-.7720319-.9560461-1.158042-1.6296297-1.158042-.4780271 0-.86913425.1554763-1.1733333.4664336-.30419906.3109572-.4562963.7130511-.4562963 1.2062937v19.7510489c0 .4717973.13580111.8524461.40740741 1.1419581.27160629.2895119.63555329.4342657 1.09185189.4342657.478027 0 .8474061-.1393925 1.1081481-.4181818.2607421-.2787893.3911111-.6647994.3911111-1.158042v-14.8293706l6.4207408 11.8699301c.4128415.7505865.9451819 1.1258741 1.597037 1.1258741s1.1841955-.3752876 1.597037-1.1258741l6.3881482-11.9986014v14.9580419c0 .4932426.130369.8792527.3911111 1.158042.260742.2787893.619257.4181818 1.0755555.4181818.4780271 0 .8528382-.1393925 1.1244445-.4181818s.4074074-.6647994.4074074-1.158042v-19.7510489c0-.4932426-.1520972-.8953365-.4562963-1.2062937-.304199-.3109573-.6953062-.4664336-1.1733333-.4664336zm18.1296296 17.8786797-7.3180195-7.3180195c-.5857864-.5857865-1.5355339-.5857865-2.1213203 0-.5857865.5857864-.5857865 1.5355339 0 2.1213203l9.8994949 9.899495c.3056756.3056756.7104567.4518432 1.1109127.438503.400456.0133402.8052371-.1328274 1.1109127-.438503l9.899495-9.899495c.5857864-.5857864.5857864-1.5355339 0-2.1213203-.5857865-.5857865-1.535534-.5857865-2.1213204 0l-7.4601551 7.4601551v-16.5208153c0-.8284271-.6715729-1.5-1.5-1.5s-1.5.6715729-1.5 1.5z" fill="#d8d8d8" fill-rule="evenodd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
5
res/img/voip/signal-bars.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="11" height="12" viewBox="0 0 11 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="0.5" width="2.2" height="11" fill="#0DBD8B"/>
|
||||
<rect x="4.40015" y="2.70001" width="2.2" height="8.8" fill="#0DBD8B"/>
|
||||
<rect x="8.79993" y="7.10004" width="2.2" height="4.4" fill="#0DBD8B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 302 B |
@@ -82,10 +82,6 @@ export class DecryptionFailureTracker {
|
||||
return "HistoricalMessage";
|
||||
case DecryptionFailureCode.HISTORICAL_MESSAGE_USER_NOT_JOINED:
|
||||
return "ExpectedDueToMembership";
|
||||
case DecryptionFailureCode.SENDER_IDENTITY_PREVIOUSLY_VERIFIED:
|
||||
return "ExpectedVerificationViolation";
|
||||
case DecryptionFailureCode.UNSIGNED_SENDER_DEVICE:
|
||||
return "ExpectedSentByInsecureDevice";
|
||||
default:
|
||||
return "UnknownError";
|
||||
}
|
||||
|
||||
@@ -113,9 +113,13 @@ export default class DeviceListener {
|
||||
this.client.removeListener(ClientEvent.Sync, this.onSync);
|
||||
this.client.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||
}
|
||||
SettingsStore.unwatchSetting(this.deviceClientInformationSettingWatcherRef);
|
||||
dis.unregister(this.dispatcherRef);
|
||||
this.dispatcherRef = undefined;
|
||||
if (this.deviceClientInformationSettingWatcherRef) {
|
||||
SettingsStore.unwatchSetting(this.deviceClientInformationSettingWatcherRef);
|
||||
}
|
||||
if (this.dispatcherRef) {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
this.dispatcherRef = undefined;
|
||||
}
|
||||
this.dismissed.clear();
|
||||
this.dismissedThisDeviceToast = false;
|
||||
this.keyBackupInfo = null;
|
||||
@@ -288,21 +292,27 @@ export default class DeviceListener {
|
||||
await crypto.getUserDeviceInfo([cli.getSafeUserId()]);
|
||||
|
||||
// cross signing isn't enabled - nag to enable it
|
||||
// There are 2 different toasts for:
|
||||
// There are 3 different toasts for:
|
||||
if (!(await crypto.getCrossSigningKeyId()) && (await crypto.userHasCrossSigningKeys())) {
|
||||
// Cross-signing on account but this device doesn't trust the master key (verify this session)
|
||||
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
|
||||
this.checkKeyBackupStatus();
|
||||
} else {
|
||||
// No cross-signing or key backup on account (set up encryption)
|
||||
await cli.waitForClientWellKnown();
|
||||
if (isSecureBackupRequired(cli) && isLoggedIn()) {
|
||||
// If we're meant to set up, and Secure Backup is required,
|
||||
// trigger the flow directly without a toast once logged in.
|
||||
hideSetupEncryptionToast();
|
||||
accessSecretStorage();
|
||||
const backupInfo = await this.getKeyBackupInfo();
|
||||
if (backupInfo) {
|
||||
// No cross-signing on account but key backup available (upgrade encryption)
|
||||
showSetupEncryptionToast(SetupKind.UPGRADE_ENCRYPTION);
|
||||
} else {
|
||||
showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION);
|
||||
// No cross-signing or key backup on account (set up encryption)
|
||||
await cli.waitForClientWellKnown();
|
||||
if (isSecureBackupRequired(cli) && isLoggedIn()) {
|
||||
// If we're meant to set up, and Secure Backup is required,
|
||||
// trigger the flow directly without a toast once logged in.
|
||||
hideSetupEncryptionToast();
|
||||
accessSecretStorage();
|
||||
} else {
|
||||
showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ 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, { StrictMode } from "react";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import classNames from "classnames";
|
||||
import { IDeferred, defer, sleep } from "matrix-js-sdk/src/utils";
|
||||
@@ -416,20 +416,18 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||
const classes = classNames("mx_Dialog_wrapper mx_Dialog_staticWrapper", this.staticModal.className);
|
||||
|
||||
const staticDialog = (
|
||||
<StrictMode>
|
||||
<TooltipProvider>
|
||||
<div className={classes}>
|
||||
<Glass className="mx_Dialog_border">
|
||||
<div className="mx_Dialog">{this.staticModal.elem}</div>
|
||||
</Glass>
|
||||
<div
|
||||
data-testid="dialog-background"
|
||||
className="mx_Dialog_background mx_Dialog_staticBackground"
|
||||
onClick={this.onBackgroundClick}
|
||||
/>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</StrictMode>
|
||||
<TooltipProvider>
|
||||
<div className={classes}>
|
||||
<Glass className="mx_Dialog_border">
|
||||
<div className="mx_Dialog">{this.staticModal.elem}</div>
|
||||
</Glass>
|
||||
<div
|
||||
data-testid="dialog-background"
|
||||
className="mx_Dialog_background mx_Dialog_staticBackground"
|
||||
onClick={this.onBackgroundClick}
|
||||
/>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
||||
ReactDOM.render(staticDialog, ModalManager.getOrCreateStaticContainer());
|
||||
@@ -445,20 +443,18 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||
});
|
||||
|
||||
const dialog = (
|
||||
<StrictMode>
|
||||
<TooltipProvider>
|
||||
<div className={classes}>
|
||||
<Glass className="mx_Dialog_border">
|
||||
<div className="mx_Dialog">{modal.elem}</div>
|
||||
</Glass>
|
||||
<div
|
||||
data-testid="dialog-background"
|
||||
className="mx_Dialog_background"
|
||||
onClick={this.onBackgroundClick}
|
||||
/>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</StrictMode>
|
||||
<TooltipProvider>
|
||||
<div className={classes}>
|
||||
<Glass className="mx_Dialog_border">
|
||||
<div className="mx_Dialog">{modal.elem}</div>
|
||||
</Glass>
|
||||
<div
|
||||
data-testid="dialog-background"
|
||||
className="mx_Dialog_background"
|
||||
onClick={this.onBackgroundClick}
|
||||
/>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
||||
setTimeout(() => ReactDOM.render(dialog, ModalManager.getOrCreateContainer()), 0);
|
||||
|
||||
@@ -326,7 +326,7 @@ export class PosthogAnalytics {
|
||||
if (this.enabled) {
|
||||
this.posthog.reset();
|
||||
}
|
||||
SettingsStore.unwatchSetting(this.watchSettingRef);
|
||||
if (this.watchSettingRef) SettingsStore.unwatchSetting(this.watchSettingRef);
|
||||
this.setAnonymity(Anonymity.Disabled);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ import { ActionPayload } from "./dispatcher/payloads";
|
||||
const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins
|
||||
|
||||
class Presence {
|
||||
private unavailableTimer?: Timer;
|
||||
private dispatcherRef?: string;
|
||||
private state?: SetPresence;
|
||||
private unavailableTimer: Timer | null = null;
|
||||
private dispatcherRef: string | null = null;
|
||||
private state: SetPresence | null = null;
|
||||
|
||||
/**
|
||||
* Start listening the user activity to evaluate his presence state.
|
||||
@@ -46,10 +46,14 @@ class Presence {
|
||||
* Stop tracking user activity
|
||||
*/
|
||||
public stop(): void {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
this.dispatcherRef = undefined;
|
||||
this.unavailableTimer?.abort();
|
||||
this.unavailableTimer = undefined;
|
||||
if (this.dispatcherRef) {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
this.dispatcherRef = null;
|
||||
}
|
||||
if (this.unavailableTimer) {
|
||||
this.unavailableTimer.abort();
|
||||
this.unavailableTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +61,7 @@ class Presence {
|
||||
* @returns {string} the presence state (see PRESENCE enum)
|
||||
*/
|
||||
public getState(): SetPresence | null {
|
||||
return this.state ?? null;
|
||||
return this.state;
|
||||
}
|
||||
|
||||
private onAction = (payload: ActionPayload): void => {
|
||||
|
||||
@@ -38,7 +38,7 @@ export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
|
||||
public static contextType = MatrixClientContext;
|
||||
public declare context: React.ContextType<typeof MatrixClientContext>;
|
||||
private unmounted = false;
|
||||
private dispatcherRef?: string;
|
||||
private dispatcherRef: string | null = null;
|
||||
|
||||
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
|
||||
super(props, context);
|
||||
@@ -100,7 +100,7 @@ export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
this.unmounted = true;
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef !== null) dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
private onAction = (payload: ActionPayload): void => {
|
||||
|
||||
@@ -228,9 +228,9 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
this._matrixClient.removeListener(ClientEvent.Sync, this.onSync);
|
||||
this._matrixClient.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||
OwnProfileStore.instance.off(UPDATE_EVENT, this.refreshBackgroundImage);
|
||||
SettingsStore.unwatchSetting(this.layoutWatcherRef);
|
||||
SettingsStore.unwatchSetting(this.compactLayoutWatcherRef);
|
||||
SettingsStore.unwatchSetting(this.backgroundImageWatcherRef);
|
||||
if (this.layoutWatcherRef) SettingsStore.unwatchSetting(this.layoutWatcherRef);
|
||||
if (this.compactLayoutWatcherRef) SettingsStore.unwatchSetting(this.compactLayoutWatcherRef);
|
||||
if (this.backgroundImageWatcherRef) SettingsStore.unwatchSetting(this.backgroundImageWatcherRef);
|
||||
this.timezoneProfileUpdateRef?.forEach((s) => SettingsStore.unwatchSetting(s));
|
||||
this.resizer?.detach();
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public declare context: React.ContextType<typeof RoomContext>;
|
||||
|
||||
private dispatcherRef?: string;
|
||||
private dispatcherRef: string | null = null;
|
||||
private readonly layoutWatcherRef: string;
|
||||
private timelinePanel = createRef<TimelinePanel>();
|
||||
private card = createRef<HTMLDivElement>();
|
||||
@@ -118,7 +118,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
|
||||
const roomId = this.props.mxEvent.getRoomId();
|
||||
SettingsStore.unwatchSetting(this.layoutWatcherRef);
|
||||
|
||||
|
||||
@@ -121,9 +121,9 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
SettingsStore.unwatchSetting(this.themeWatcherRef);
|
||||
SettingsStore.unwatchSetting(this.dndWatcherRef);
|
||||
defaultDispatcher.unregister(this.dispatcherRef);
|
||||
if (this.themeWatcherRef) SettingsStore.unwatchSetting(this.themeWatcherRef);
|
||||
if (this.dndWatcherRef) SettingsStore.unwatchSetting(this.dndWatcherRef);
|
||||
if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef);
|
||||
OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate);
|
||||
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate);
|
||||
this.context.voiceBroadcastRecordingsStore.off(
|
||||
|
||||
@@ -24,6 +24,10 @@ export enum Phase {
|
||||
WaitingForDevice,
|
||||
Verifying,
|
||||
Error,
|
||||
/**
|
||||
* @deprecated the MSC3906 implementation is deprecated in favour of MSC4108.
|
||||
*/
|
||||
LegacyConnected,
|
||||
}
|
||||
|
||||
export enum Click {
|
||||
|
||||
@@ -9,6 +9,11 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React from "react";
|
||||
import {
|
||||
ClientRendezvousFailureReason,
|
||||
LegacyRendezvousFailureReason,
|
||||
MSC3886SimpleHttpRendezvousTransport,
|
||||
MSC3903ECDHPayload,
|
||||
MSC3903ECDHv2RendezvousChannel,
|
||||
MSC3906Rendezvous,
|
||||
MSC4108FailureReason,
|
||||
MSC4108RendezvousSession,
|
||||
MSC4108SecureChannel,
|
||||
@@ -18,21 +23,29 @@ import {
|
||||
RendezvousIntent,
|
||||
} from "matrix-js-sdk/src/rendezvous";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { HTTPError, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { Click, Mode, Phase } from "./LoginWithQR-types";
|
||||
import LoginWithQRFlow from "./LoginWithQRFlow";
|
||||
import { wrapRequestWithDialog } from "../../../utils/UserInteractiveAuth";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
interface IProps {
|
||||
client: MatrixClient;
|
||||
mode: Mode;
|
||||
legacy: boolean;
|
||||
onFinished(...args: any): void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
phase: Phase;
|
||||
rendezvous?: MSC4108SignInWithQR;
|
||||
rendezvous?: MSC3906Rendezvous | MSC4108SignInWithQR;
|
||||
mediaPermissionError?: boolean;
|
||||
|
||||
// MSC3906
|
||||
confirmationDigits?: string;
|
||||
|
||||
// MSC4108
|
||||
verificationUri?: string;
|
||||
userCode?: string;
|
||||
checkCode?: string;
|
||||
@@ -41,18 +54,25 @@ interface IState {
|
||||
}
|
||||
|
||||
export enum LoginWithQRFailureReason {
|
||||
/**
|
||||
* @deprecated the MSC3906 implementation is deprecated in favour of MSC4108.
|
||||
*/
|
||||
RateLimited = "rate_limited",
|
||||
CheckCodeMismatch = "check_code_mismatch",
|
||||
}
|
||||
|
||||
export type FailureReason = RendezvousFailureReason | LoginWithQRFailureReason;
|
||||
|
||||
// n.b MSC3886/MSC3903/MSC3906 that this is based on are now closed.
|
||||
// However, we want to keep this implementation around for some time.
|
||||
// TODO: define an end-of-life date for this implementation.
|
||||
|
||||
/**
|
||||
* A component that allows sign in and E2EE set up with a QR code.
|
||||
*
|
||||
* It implements `login.reciprocate` capabilities and showing QR codes.
|
||||
*
|
||||
* This uses the unstable feature of MSC4108: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
|
||||
* This uses the unstable feature of MSC3906: https://github.com/matrix-org/matrix-spec-proposals/pull/3906
|
||||
*/
|
||||
export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
private finished = false;
|
||||
@@ -84,6 +104,9 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
if (this.state.rendezvous) {
|
||||
const rendezvous = this.state.rendezvous;
|
||||
rendezvous.onFailure = undefined;
|
||||
if (rendezvous instanceof MSC3906Rendezvous) {
|
||||
await rendezvous.cancel(LegacyRendezvousFailureReason.UserCancelled);
|
||||
}
|
||||
this.setState({ rendezvous: undefined });
|
||||
}
|
||||
if (mode === Mode.Show) {
|
||||
@@ -96,7 +119,60 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
// eslint-disable-next-line react/no-direct-mutation-state
|
||||
this.state.rendezvous.onFailure = undefined;
|
||||
// calling cancel will call close() as well to clean up the resources
|
||||
this.state.rendezvous.cancel(MSC4108FailureReason.UserCancelled);
|
||||
if (this.state.rendezvous instanceof MSC3906Rendezvous) {
|
||||
this.state.rendezvous.cancel(LegacyRendezvousFailureReason.UserCancelled);
|
||||
} else {
|
||||
this.state.rendezvous.cancel(MSC4108FailureReason.UserCancelled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async legacyApproveLogin(): Promise<void> {
|
||||
if (!(this.state.rendezvous instanceof MSC3906Rendezvous)) {
|
||||
throw new Error("Rendezvous not found");
|
||||
}
|
||||
if (!this.props.client) {
|
||||
throw new Error("No client to approve login with");
|
||||
}
|
||||
this.setState({ phase: Phase.Loading });
|
||||
|
||||
try {
|
||||
logger.info("Requesting login token");
|
||||
|
||||
const { login_token: loginToken } = await wrapRequestWithDialog(this.props.client.requestLoginToken, {
|
||||
matrixClient: this.props.client,
|
||||
title: _t("auth|qr_code_login|sign_in_new_device"),
|
||||
})();
|
||||
|
||||
this.setState({ phase: Phase.WaitingForDevice });
|
||||
|
||||
const newDeviceId = await this.state.rendezvous.approveLoginOnExistingDevice(loginToken);
|
||||
if (!newDeviceId) {
|
||||
// user denied
|
||||
return;
|
||||
}
|
||||
if (!this.props.client.getCrypto()) {
|
||||
// no E2EE to set up
|
||||
this.onFinished(true);
|
||||
return;
|
||||
}
|
||||
this.setState({ phase: Phase.Verifying });
|
||||
await this.state.rendezvous.verifyNewDeviceOnExistingDevice();
|
||||
// clean up our state:
|
||||
try {
|
||||
await this.state.rendezvous.close();
|
||||
} finally {
|
||||
this.setState({ rendezvous: undefined });
|
||||
}
|
||||
this.onFinished(true);
|
||||
} catch (e) {
|
||||
logger.error("Error whilst approving sign in", e);
|
||||
if (e instanceof HTTPError && e.httpStatus === 429) {
|
||||
// 429: rate limit
|
||||
this.setState({ phase: Phase.Error, failureReason: LoginWithQRFailureReason.RateLimited });
|
||||
return;
|
||||
}
|
||||
this.setState({ phase: Phase.Error, failureReason: ClientRendezvousFailureReason.Unknown });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,18 +182,28 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
private generateAndShowCode = async (): Promise<void> => {
|
||||
let rendezvous: MSC4108SignInWithQR;
|
||||
let rendezvous: MSC4108SignInWithQR | MSC3906Rendezvous;
|
||||
try {
|
||||
const fallbackRzServer = this.props.client?.getClientWellKnown()?.["io.element.rendezvous"]?.server;
|
||||
|
||||
const transport = new MSC4108RendezvousSession({
|
||||
onFailure: this.onFailure,
|
||||
client: this.props.client,
|
||||
fallbackRzServer,
|
||||
});
|
||||
await transport.send("");
|
||||
const channel = new MSC4108SecureChannel(transport, undefined, this.onFailure);
|
||||
rendezvous = new MSC4108SignInWithQR(channel, false, this.props.client, this.onFailure);
|
||||
if (this.props.legacy) {
|
||||
const transport = new MSC3886SimpleHttpRendezvousTransport<MSC3903ECDHPayload>({
|
||||
onFailure: this.onFailure,
|
||||
client: this.props.client,
|
||||
fallbackRzServer,
|
||||
});
|
||||
const channel = new MSC3903ECDHv2RendezvousChannel(transport, undefined, this.onFailure);
|
||||
rendezvous = new MSC3906Rendezvous(channel, this.props.client, this.onFailure);
|
||||
} else {
|
||||
const transport = new MSC4108RendezvousSession({
|
||||
onFailure: this.onFailure,
|
||||
client: this.props.client,
|
||||
fallbackRzServer,
|
||||
});
|
||||
await transport.send("");
|
||||
const channel = new MSC4108SecureChannel(transport, undefined, this.onFailure);
|
||||
rendezvous = new MSC4108SignInWithQR(channel, false, this.props.client, this.onFailure);
|
||||
}
|
||||
|
||||
await rendezvous.generateCode();
|
||||
this.setState({
|
||||
@@ -132,7 +218,10 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.ourIntent === RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE) {
|
||||
if (rendezvous instanceof MSC3906Rendezvous) {
|
||||
const confirmationDigits = await rendezvous.startAfterShowingCode();
|
||||
this.setState({ phase: Phase.LegacyConnected, confirmationDigits });
|
||||
} else if (this.ourIntent === RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE) {
|
||||
// MSC4108-Flow: NewScanned
|
||||
await rendezvous.negotiateProtocols();
|
||||
const { verificationUri } = await rendezvous.deviceAuthorizationGrant();
|
||||
@@ -145,9 +234,18 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
// we ask the user to confirm that the channel is secure
|
||||
} catch (e: RendezvousError | unknown) {
|
||||
logger.error("Error whilst approving login", e);
|
||||
await rendezvous?.cancel(
|
||||
e instanceof RendezvousError ? (e.code as MSC4108FailureReason) : ClientRendezvousFailureReason.Unknown,
|
||||
);
|
||||
if (rendezvous instanceof MSC3906Rendezvous) {
|
||||
// only set to error phase if it hasn't already been set by onFailure or similar
|
||||
if (this.state.phase !== Phase.Error) {
|
||||
this.setState({ phase: Phase.Error, failureReason: LegacyRendezvousFailureReason.Unknown });
|
||||
}
|
||||
} else {
|
||||
await rendezvous?.cancel(
|
||||
e instanceof RendezvousError
|
||||
? (e.code as MSC4108FailureReason)
|
||||
: ClientRendezvousFailureReason.Unknown,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -200,6 +298,7 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
public reset(): void {
|
||||
this.setState({
|
||||
rendezvous: undefined,
|
||||
confirmationDigits: undefined,
|
||||
verificationUri: undefined,
|
||||
failureReason: undefined,
|
||||
userCode: undefined,
|
||||
@@ -212,12 +311,16 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
private onClick = async (type: Click, checkCode?: string): Promise<void> => {
|
||||
switch (type) {
|
||||
case Click.Cancel:
|
||||
await this.state.rendezvous?.cancel(MSC4108FailureReason.UserCancelled);
|
||||
if (this.state.rendezvous instanceof MSC3906Rendezvous) {
|
||||
await this.state.rendezvous?.cancel(LegacyRendezvousFailureReason.UserCancelled);
|
||||
} else {
|
||||
await this.state.rendezvous?.cancel(MSC4108FailureReason.UserCancelled);
|
||||
}
|
||||
this.reset();
|
||||
this.onFinished(false);
|
||||
break;
|
||||
case Click.Approve:
|
||||
await this.approveLogin(checkCode);
|
||||
await (this.props.legacy ? this.legacyApproveLogin() : this.approveLogin(checkCode));
|
||||
break;
|
||||
case Click.Decline:
|
||||
await this.state.rendezvous?.declineLoginOnExistingDevice();
|
||||
@@ -225,7 +328,11 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
this.onFinished(false);
|
||||
break;
|
||||
case Click.Back:
|
||||
await this.state.rendezvous?.cancel(MSC4108FailureReason.UserCancelled);
|
||||
if (this.state.rendezvous instanceof MSC3906Rendezvous) {
|
||||
await this.state.rendezvous?.cancel(LegacyRendezvousFailureReason.UserCancelled);
|
||||
} else {
|
||||
await this.state.rendezvous?.cancel(MSC4108FailureReason.UserCancelled);
|
||||
}
|
||||
this.onFinished(false);
|
||||
break;
|
||||
case Click.ShowQr:
|
||||
@@ -235,6 +342,20 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
if (this.state.rendezvous instanceof MSC3906Rendezvous) {
|
||||
return (
|
||||
<LoginWithQRFlow
|
||||
onClick={this.onClick}
|
||||
phase={this.state.phase}
|
||||
code={this.state.phase === Phase.ShowingQR ? this.state.rendezvous?.code : undefined}
|
||||
confirmationDigits={
|
||||
this.state.phase === Phase.LegacyConnected ? this.state.confirmationDigits : undefined
|
||||
}
|
||||
failureReason={this.state.failureReason}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<LoginWithQRFlow
|
||||
onClick={this.onClick}
|
||||
|
||||
@@ -7,7 +7,11 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { createRef, ReactNode } from "react";
|
||||
import { ClientRendezvousFailureReason, MSC4108FailureReason } from "matrix-js-sdk/src/rendezvous";
|
||||
import {
|
||||
ClientRendezvousFailureReason,
|
||||
LegacyRendezvousFailureReason,
|
||||
MSC4108FailureReason,
|
||||
} from "matrix-js-sdk/src/rendezvous";
|
||||
import ChevronLeftIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-left";
|
||||
import CheckCircleSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/check-circle-solid";
|
||||
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
|
||||
@@ -19,11 +23,21 @@ import { _t } from "../../../languageHandler";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import QRCode from "../elements/QRCode";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import { Icon as InfoIcon } from "../../../../res/img/element-icons/i.svg";
|
||||
import { Click, Phase } from "./LoginWithQR-types";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { FailureReason, LoginWithQRFailureReason } from "./LoginWithQR";
|
||||
import { XOR } from "../../../@types/common";
|
||||
import { ErrorMessage } from "../../structures/ErrorMessage";
|
||||
|
||||
/**
|
||||
* @deprecated the MSC3906 implementation is deprecated in favour of MSC4108.
|
||||
*/
|
||||
interface MSC3906Props extends Pick<Props, "phase" | "onClick" | "failureReason"> {
|
||||
code?: string;
|
||||
confirmationDigits?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
phase: Phase;
|
||||
code?: Uint8Array;
|
||||
@@ -33,15 +47,19 @@ interface Props {
|
||||
checkCode?: string;
|
||||
}
|
||||
|
||||
// n.b MSC3886/MSC3903/MSC3906 that this is based on are now closed.
|
||||
// However, we want to keep this implementation around for some time.
|
||||
// TODO: define an end-of-life date for this implementation.
|
||||
|
||||
/**
|
||||
* A component that implements the UI for sign in and E2EE set up with a QR code.
|
||||
*
|
||||
* This supports the unstable features of MSC4108
|
||||
* This supports the unstable features of MSC3906 and MSC4108
|
||||
*/
|
||||
export default class LoginWithQRFlow extends React.Component<Props> {
|
||||
export default class LoginWithQRFlow extends React.Component<XOR<Props, MSC3906Props>> {
|
||||
private checkCodeInput = createRef<HTMLInputElement>();
|
||||
|
||||
public constructor(props: Props) {
|
||||
public constructor(props: XOR<Props, MSC3906Props>) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
@@ -86,17 +104,20 @@ export default class LoginWithQRFlow extends React.Component<Props> {
|
||||
|
||||
switch (this.props.failureReason) {
|
||||
case MSC4108FailureReason.UnsupportedProtocol:
|
||||
case LegacyRendezvousFailureReason.UnsupportedProtocol:
|
||||
title = _t("auth|qr_code_login|error_unsupported_protocol_title");
|
||||
message = _t("auth|qr_code_login|error_unsupported_protocol");
|
||||
break;
|
||||
|
||||
case MSC4108FailureReason.UserCancelled:
|
||||
case LegacyRendezvousFailureReason.UserCancelled:
|
||||
title = _t("auth|qr_code_login|error_user_cancelled_title");
|
||||
message = _t("auth|qr_code_login|error_user_cancelled");
|
||||
break;
|
||||
|
||||
case MSC4108FailureReason.AuthorizationExpired:
|
||||
case ClientRendezvousFailureReason.Expired:
|
||||
case LegacyRendezvousFailureReason.Expired:
|
||||
title = _t("auth|qr_code_login|error_expired_title");
|
||||
message = _t("auth|qr_code_login|error_expired");
|
||||
break;
|
||||
@@ -141,6 +162,7 @@ export default class LoginWithQRFlow extends React.Component<Props> {
|
||||
message = _t("auth|qr_code_login|error_etag_missing");
|
||||
break;
|
||||
|
||||
case LegacyRendezvousFailureReason.HomeserverLacksSupport:
|
||||
case ClientRendezvousFailureReason.HomeserverLacksSupport:
|
||||
success = null;
|
||||
Icon = QrCodeIcon;
|
||||
@@ -178,6 +200,40 @@ export default class LoginWithQRFlow extends React.Component<Props> {
|
||||
);
|
||||
break;
|
||||
}
|
||||
case Phase.LegacyConnected:
|
||||
backButton = false;
|
||||
main = (
|
||||
<>
|
||||
<p>{_t("auth|qr_code_login|confirm_code_match")}</p>
|
||||
<div className="mx_LoginWithQR_confirmationDigits">{this.props.confirmationDigits}</div>
|
||||
<div className="mx_LoginWithQR_confirmationAlert">
|
||||
<div>
|
||||
<InfoIcon />
|
||||
</div>
|
||||
<div>{_t("auth|qr_code_login|approve_access_warning")}</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
buttons = (
|
||||
<>
|
||||
<AccessibleButton
|
||||
data-testid="approve-login-button"
|
||||
kind="primary"
|
||||
onClick={this.handleClick(Click.Approve)}
|
||||
>
|
||||
{_t("action|approve")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
data-testid="decline-login-button"
|
||||
kind="primary_outline"
|
||||
onClick={this.handleClick(Click.Decline)}
|
||||
>
|
||||
{_t("action|cancel")}
|
||||
</AccessibleButton>
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case Phase.OutOfBandConfirmation:
|
||||
backButton = false;
|
||||
main = (
|
||||
@@ -232,7 +288,8 @@ export default class LoginWithQRFlow extends React.Component<Props> {
|
||||
break;
|
||||
case Phase.ShowingQR:
|
||||
if (this.props.code) {
|
||||
const data = this.props.code;
|
||||
const data =
|
||||
typeof this.props.code !== "string" ? this.props.code : Buffer.from(this.props.code ?? "");
|
||||
|
||||
main = (
|
||||
<>
|
||||
|
||||
@@ -80,7 +80,9 @@ class RoomSettingsDialog extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef) {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
MatrixClientPeg.get()?.removeListener(RoomEvent.Name, this.onRoomName);
|
||||
MatrixClientPeg.get()?.removeListener(RoomStateEvent.Events, this.onStateEvent);
|
||||
|
||||
@@ -340,13 +340,13 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
// Widget action listeners
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
|
||||
|
||||
if (this.props.room) {
|
||||
this.context.off(RoomEvent.MyMembership, this.onMyMembership);
|
||||
}
|
||||
|
||||
SettingsStore.unwatchSetting(this.allowedWidgetsWatchRef);
|
||||
if (this.allowedWidgetsWatchRef) SettingsStore.unwatchSetting(this.allowedWidgetsWatchRef);
|
||||
OwnProfileStore.instance.removeListener(UPDATE_EVENT, this.onUserReady);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ 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, { MutableRefObject, ReactNode, StrictMode } from "react";
|
||||
import React, { MutableRefObject, ReactNode } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
@@ -167,15 +167,13 @@ export default class PersistedElement extends React.Component<IProps> {
|
||||
|
||||
private renderApp(): void {
|
||||
const content = (
|
||||
<StrictMode>
|
||||
<MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>
|
||||
<TooltipProvider>
|
||||
<div ref={this.collectChild} style={this.props.style}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
</StrictMode>
|
||||
<MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>
|
||||
<TooltipProvider>
|
||||
<div ref={this.collectChild} style={this.props.style}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
|
||||
ReactDOM.render(content, getOrCreateContainer("mx_persistedElement_" + this.props.persistKey));
|
||||
|
||||
@@ -71,7 +71,7 @@ export default class DateSeparator extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
SettingsStore.unwatchSetting(this.settingWatcherRef);
|
||||
if (this.settingWatcherRef) SettingsStore.unwatchSetting(this.settingWatcherRef);
|
||||
}
|
||||
|
||||
private onContextMenuOpenClick = (e: ButtonEvent): void => {
|
||||
|
||||
@@ -367,7 +367,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||
this.unmounted = true;
|
||||
MatrixClientPeg.get()?.off(ClientEvent.Sync, this.reconnectedListener);
|
||||
this.clearBlurhashTimeout();
|
||||
SettingsStore.unwatchSetting(this.sizeWatcher);
|
||||
if (this.sizeWatcher) SettingsStore.unwatchSetting(this.sizeWatcher);
|
||||
if (this.state.isAnimated && this.state.thumbUrl) {
|
||||
URL.revokeObjectURL(this.state.thumbUrl);
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
SettingsStore.unwatchSetting(this.sizeWatcher);
|
||||
if (this.sizeWatcher) SettingsStore.unwatchSetting(this.sizeWatcher);
|
||||
}
|
||||
|
||||
private videoOnPlay = async (): Promise<void> => {
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import React, { createRef, SyntheticEvent, MouseEvent, StrictMode } from "react";
|
||||
import React, { createRef, SyntheticEvent, MouseEvent } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { MsgType } from "matrix-js-sdk/src/matrix";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
@@ -118,12 +118,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
||||
// Insert containing div in place of <pre> block
|
||||
pre.parentNode?.replaceChild(root, pre);
|
||||
|
||||
ReactDOM.render(
|
||||
<StrictMode>
|
||||
<CodeBlock onHeightChanged={this.props.onHeightChanged}>{pre}</CodeBlock>
|
||||
</StrictMode>,
|
||||
root,
|
||||
);
|
||||
ReactDOM.render(<CodeBlock onHeightChanged={this.props.onHeightChanged}>{pre}</CodeBlock>, root);
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Readonly<IBodyProps>): void {
|
||||
@@ -197,11 +192,9 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
||||
const reason = node.getAttribute("data-mx-spoiler") ?? undefined;
|
||||
node.removeAttribute("data-mx-spoiler"); // we don't want to recurse
|
||||
const spoiler = (
|
||||
<StrictMode>
|
||||
<TooltipProvider>
|
||||
<Spoiler reason={reason} contentHtml={node.outerHTML} />
|
||||
</TooltipProvider>
|
||||
</StrictMode>
|
||||
<TooltipProvider>
|
||||
<Spoiler reason={reason} contentHtml={node.outerHTML} />
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
||||
ReactDOM.render(spoiler, spoilerContainer);
|
||||
|
||||
@@ -100,10 +100,14 @@ export default class TimelineCard extends React.Component<IProps, IState> {
|
||||
public componentWillUnmount(): void {
|
||||
SdkContextClass.instance.roomViewStore.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
|
||||
SettingsStore.unwatchSetting(this.readReceiptsSettingWatcher);
|
||||
SettingsStore.unwatchSetting(this.layoutWatcherRef);
|
||||
if (this.readReceiptsSettingWatcher) {
|
||||
SettingsStore.unwatchSetting(this.readReceiptsSettingWatcher);
|
||||
}
|
||||
if (this.layoutWatcherRef) {
|
||||
SettingsStore.unwatchSetting(this.layoutWatcherRef);
|
||||
}
|
||||
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
private onRoomViewStoreUpdate = async (_initial?: boolean): Promise<void> => {
|
||||
|
||||
@@ -82,7 +82,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
|
||||
this.unmounted = true;
|
||||
ScalarMessaging.stopListening();
|
||||
WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(this.props.room), this.updateApps);
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
|
||||
if (this.resizeContainer) {
|
||||
this.resizer.detach();
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
VoiceRecordingStore.instance.off(UPDATE_EVENT, this.onVoiceStoreUpdate);
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
|
||||
UIStore.instance.stopTrackingElementDimensions(`MessageComposer${this.instanceId}`);
|
||||
UIStore.instance.removeListener(`MessageComposer${this.instanceId}`, this.onResize);
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import { useRoomMemberCount, useRoomMembers } from "../../../hooks/useRoomMember
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { Flex } from "../../utils/Flex";
|
||||
import { Box } from "../../utils/Box";
|
||||
import { getPlatformCallTypeChildren, getPlatformCallTypeLabel, useRoomCall } from "../../../hooks/room/useRoomCall";
|
||||
import { getPlatformCallTypeLabel, useRoomCall } from "../../../hooks/room/useRoomCall";
|
||||
import { useRoomThreadNotifications } from "../../../hooks/room/useRoomThreadNotifications";
|
||||
import { useGlobalNotificationState } from "../../../hooks/useGlobalNotificationState";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
@@ -172,8 +172,6 @@ export default function RoomHeader({
|
||||
key={option}
|
||||
label={getPlatformCallTypeLabel(option)}
|
||||
aria-label={getPlatformCallTypeLabel(option)}
|
||||
children={getPlatformCallTypeChildren(option)}
|
||||
className="mx_RoomHeader_videoCallOption"
|
||||
onClick={(ev) => videoCallClick(ev, option)}
|
||||
Icon={VideoCallIcon}
|
||||
onSelect={() => {} /* Dummy handler since we want the click event.*/}
|
||||
|
||||
@@ -446,7 +446,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||
public componentWillUnmount(): void {
|
||||
SpaceStore.instance.off(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms);
|
||||
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists);
|
||||
defaultDispatcher.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef);
|
||||
SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
}
|
||||
|
||||
|
||||
@@ -248,7 +248,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
defaultDispatcher.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef);
|
||||
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onListsUpdated);
|
||||
RoomListStore.instance.off(LISTS_LOADING_EVENT, this.onListsLoading);
|
||||
this.tilesRef.current?.removeEventListener("scroll", this.onScrollPrevent);
|
||||
|
||||
@@ -175,7 +175,7 @@ export class RoomTile extends React.PureComponent<ClassProps, State> {
|
||||
this.onRoomPreviewChanged,
|
||||
);
|
||||
this.props.room.off(RoomEvent.Name, this.onRoomNameUpdate);
|
||||
defaultDispatcher.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef);
|
||||
this.notificationState.off(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||
this.roomProps.off(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
|
||||
CallStore.instance.off(CallStoreEvent.Call, this.onCallChanged);
|
||||
|
||||
@@ -141,7 +141,9 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
|
||||
if (client) client.removeListener(ClientEvent.AccountData, this.updateWidget);
|
||||
RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||
window.removeEventListener("resize", this.onResize);
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef) {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidUpdate(): void {
|
||||
|
||||
@@ -79,7 +79,9 @@ export default class FontScalingPanel extends React.Component<IProps, IState> {
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
this.unmounted = true;
|
||||
SettingsStore.unwatchSetting(this.layoutWatcherRef);
|
||||
if (this.layoutWatcherRef) {
|
||||
SettingsStore.unwatchSetting(this.layoutWatcherRef);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -52,7 +52,7 @@ export default class IntegrationManager extends React.Component<IProps, IState>
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
|
||||
document.removeEventListener("keydown", this.onKeyDown);
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
private onAction = (payload: ActionPayload): void => {
|
||||
|
||||
@@ -8,7 +8,10 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
IGetLoginTokenCapability,
|
||||
IServerVersions,
|
||||
GET_LOGIN_TOKEN_CAPABILITY,
|
||||
Capabilities,
|
||||
IClientWellKnown,
|
||||
OidcClientConfig,
|
||||
MatrixClient,
|
||||
@@ -25,11 +28,27 @@ import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext
|
||||
interface IProps {
|
||||
onShowQr: () => void;
|
||||
versions?: IServerVersions;
|
||||
capabilities?: Capabilities;
|
||||
wellKnown?: IClientWellKnown;
|
||||
oidcClientConfig?: OidcClientConfig;
|
||||
isCrossSigningReady?: boolean;
|
||||
}
|
||||
|
||||
function shouldShowQrLegacy(
|
||||
versions?: IServerVersions,
|
||||
wellKnown?: IClientWellKnown,
|
||||
capabilities?: Capabilities,
|
||||
): boolean {
|
||||
// Needs server support for (get_login_token or OIDC Device Authorization Grant) and MSC3886:
|
||||
// in r0 of MSC3882 it is exposed as a feature flag, but in stable and unstable r1 it is a capability
|
||||
const loginTokenCapability = GET_LOGIN_TOKEN_CAPABILITY.findIn<IGetLoginTokenCapability>(capabilities);
|
||||
const getLoginTokenSupported =
|
||||
!!versions?.unstable_features?.["org.matrix.msc3882"] || !!loginTokenCapability?.enabled;
|
||||
const msc3886Supported =
|
||||
!!versions?.unstable_features?.["org.matrix.msc3886"] || !!wellKnown?.["io.element.rendezvous"]?.server;
|
||||
return getLoginTokenSupported && msc3886Supported;
|
||||
}
|
||||
|
||||
export function shouldShowQr(
|
||||
cli: MatrixClient,
|
||||
isCrossSigningReady: boolean,
|
||||
@@ -54,12 +73,15 @@ export function shouldShowQr(
|
||||
const LoginWithQRSection: React.FC<IProps> = ({
|
||||
onShowQr,
|
||||
versions,
|
||||
capabilities,
|
||||
wellKnown,
|
||||
oidcClientConfig,
|
||||
isCrossSigningReady,
|
||||
}) => {
|
||||
const cli = useMatrixClientContext();
|
||||
const offerShowQr = shouldShowQr(cli, !!isCrossSigningReady, oidcClientConfig, versions, wellKnown);
|
||||
const offerShowQr = oidcClientConfig
|
||||
? shouldShowQr(cli, !!isCrossSigningReady, oidcClientConfig, versions, wellKnown)
|
||||
: shouldShowQrLegacy(versions, wellKnown, capabilities);
|
||||
|
||||
return (
|
||||
<SettingsSubsection heading={_t("settings|sessions|sign_in_with_qr")}>
|
||||
|
||||
@@ -129,7 +129,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
|
||||
MatrixClientPeg.safeGet().removeListener(RoomEvent.MyMembership, this.onMyMembership);
|
||||
}
|
||||
|
||||
|
||||
@@ -181,6 +181,7 @@ const SessionManagerTab: React.FC<{
|
||||
const userId = matrixClient?.getUserId();
|
||||
const currentUserMember = (userId && matrixClient?.getUser(userId)) || undefined;
|
||||
const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]);
|
||||
const capabilities = useAsyncMemo(async () => matrixClient?.getCapabilities(), [matrixClient]);
|
||||
const wellKnown = useMemo(() => matrixClient?.getClientWellKnown(), [matrixClient]);
|
||||
const oidcClientConfig = useAsyncMemo(async () => {
|
||||
try {
|
||||
@@ -291,7 +292,12 @@ const SessionManagerTab: React.FC<{
|
||||
if (signInWithQrMode) {
|
||||
return (
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<LoginWithQR mode={signInWithQrMode} onFinished={onQrFinish} client={matrixClient} />
|
||||
<LoginWithQR
|
||||
mode={signInWithQrMode}
|
||||
onFinished={onQrFinish}
|
||||
client={matrixClient}
|
||||
legacy={!oidcClientConfig && !showMsc4108QrCode}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -302,6 +308,7 @@ const SessionManagerTab: React.FC<{
|
||||
<LoginWithQRSection
|
||||
onShowQr={onShowQrClicked}
|
||||
versions={clientVersions}
|
||||
capabilities={capabilities}
|
||||
wellKnown={wellKnown}
|
||||
oidcClientConfig={oidcClientConfig}
|
||||
isCrossSigningReady={isCrossSigningReady}
|
||||
|
||||
@@ -126,7 +126,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||
|
||||
document.removeEventListener("keydown", this.onNativeKeyDown);
|
||||
this.updateCallListeners(this.props.call, null);
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
public static getDerivedStateFromProps(props: IProps): Partial<IState> {
|
||||
|
||||
@@ -46,8 +46,7 @@ export class MatrixDispatcher {
|
||||
/**
|
||||
* Removes a callback based on its token.
|
||||
*/
|
||||
public unregister(id?: DispatchToken): void {
|
||||
if (!id) return;
|
||||
public unregister(id: DispatchToken): void {
|
||||
invariant(this.callbacks.has(id), `Dispatcher.unregister(...): '${id}' does not map to a registered callback.`);
|
||||
this.callbacks.delete(id);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
import React, { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { CallType } from "matrix-js-sdk/src/webrtc/call";
|
||||
|
||||
import { useFeatureEnabled } from "../useSettings";
|
||||
@@ -35,7 +35,6 @@ import { isVideoRoom } from "../../utils/video-rooms";
|
||||
import { useGuestAccessInformation } from "./useGuestAccessInformation";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import { UIFeature } from "../../settings/UIFeature";
|
||||
import { BetaPill } from "../../components/views/beta/BetaCard";
|
||||
|
||||
export enum PlatformCallType {
|
||||
ElementCall,
|
||||
@@ -52,14 +51,6 @@ export const getPlatformCallTypeLabel = (platformCallType: PlatformCallType): st
|
||||
return _t("voip|legacy_call");
|
||||
}
|
||||
};
|
||||
export const getPlatformCallTypeChildren = (platformCallType: PlatformCallType): ReactNode => {
|
||||
switch (platformCallType) {
|
||||
case PlatformCallType.ElementCall:
|
||||
return <BetaPill />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const enum State {
|
||||
NoCall,
|
||||
NoOneHere,
|
||||
@@ -247,12 +247,15 @@
|
||||
"phone_label": "Telefon",
|
||||
"phone_optional_label": "Telefonní číslo (nepovinné)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "Schválením přístupu tohoto zařízení získá zařízení plný přístup k vašemu účtu.",
|
||||
"completing_setup": "Dokončování nastavení nového zařízení",
|
||||
"confirm_code_match": "Zkontrolujte, zda se níže uvedený kód shoduje s vaším dalším zařízením:",
|
||||
"error_rate_limited": "Příliš mnoho pokusů v krátkém čase. Počkejte chvíli, než to zkusíte znovu.",
|
||||
"error_unexpected": "Došlo k neočekávané chybě.",
|
||||
"scan_code_instruction": "Níže uvedený QR kód naskenujte pomocí přihlašovaného zařízení.",
|
||||
"scan_qr_code": "Skenovat QR kód",
|
||||
"select_qr_code": "Vybrat '%(scanQRCode)s'",
|
||||
"sign_in_new_device": "Přihlásit nové zařízení",
|
||||
"waiting_for_device": "Čekání na přihlášení zařízení"
|
||||
},
|
||||
"register_action": "Vytvořit účet",
|
||||
|
||||
@@ -243,12 +243,15 @@
|
||||
"phone_label": "Telefon",
|
||||
"phone_optional_label": "Telefon (optional)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "Indem du den Zugriff dieses Gerätes bestätigst, erhält es vollen Zugang zu deinem Konto.",
|
||||
"completing_setup": "Schließe Anmeldung deines neuen Gerätes ab",
|
||||
"confirm_code_match": "Überprüfe, dass der unten angezeigte Code mit deinem anderen Gerät übereinstimmt:",
|
||||
"error_rate_limited": "Zu viele Versuche in zu kurzer Zeit. Warte ein wenig, bevor du es erneut versuchst.",
|
||||
"error_unexpected": "Ein unerwarteter Fehler ist aufgetreten.",
|
||||
"scan_code_instruction": "Lese den folgenden QR-Code mit deinem nicht angemeldeten Gerät ein.",
|
||||
"scan_qr_code": "QR-Code einlesen",
|
||||
"select_qr_code": "Wähle „%(scanQRCode)s“",
|
||||
"sign_in_new_device": "Neues Gerät anmelden",
|
||||
"waiting_for_device": "Warte auf Anmeldung des Gerätes"
|
||||
},
|
||||
"register_action": "Konto erstellen",
|
||||
|
||||
@@ -250,11 +250,13 @@
|
||||
"phone_label": "Phone",
|
||||
"phone_optional_label": "Phone (optional)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "By approving access for this device, it will have full access to your account.",
|
||||
"check_code_explainer": "This will verify that the connection to your other device is secure.",
|
||||
"check_code_heading": "Enter the number shown on your other device",
|
||||
"check_code_input_label": "2-digit code",
|
||||
"check_code_mismatch": "The numbers don't match",
|
||||
"completing_setup": "Completing set up of your new device",
|
||||
"confirm_code_match": "Check that the code below matches with your other device:",
|
||||
"error_etag_missing": "An unexpected error occurred. This may be due to a browser extension, proxy server, or server misconfiguration.",
|
||||
"error_expired": "Sign in expired. Please try again.",
|
||||
"error_expired_title": "The sign in was not completed in time",
|
||||
@@ -282,6 +284,7 @@
|
||||
"security_code": "Security code",
|
||||
"security_code_prompt": "If asked, enter the code below on your other device.",
|
||||
"select_qr_code": "Select \"%(scanQRCode)s\"",
|
||||
"sign_in_new_device": "Sign in new device",
|
||||
"unsupported_explainer": "Your account provider doesn't support signing into a new device with a QR code.",
|
||||
"unsupported_heading": "QR code not supported",
|
||||
"waiting_for_device": "Waiting for device to sign in"
|
||||
@@ -930,6 +933,7 @@
|
||||
},
|
||||
"unable_to_setup_keys_error": "Unable to set up keys",
|
||||
"unsupported": "This client does not support end-to-end encryption.",
|
||||
"upgrade_toast_title": "Encryption upgrade available",
|
||||
"verification": {
|
||||
"accepting": "Accepting…",
|
||||
"after_new_login": {
|
||||
|
||||
@@ -231,12 +231,15 @@
|
||||
"phone_label": "Teléfono",
|
||||
"phone_optional_label": "Teléfono (opcional)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "Si apruebas acceso a este dispositivo, tendrá acceso completo a tu cuenta.",
|
||||
"completing_setup": "Terminando de configurar tu nuevo dispositivo",
|
||||
"confirm_code_match": "Comprueba que el siguiente código también aparece en el otro dispositivo:",
|
||||
"error_rate_limited": "Demasiados intentos en poco tiempo. Espera un poco antes de volverlo a intentar.",
|
||||
"error_unexpected": "Ha ocurrido un error inesperado.",
|
||||
"scan_code_instruction": "Escanea el siguiente código QR con tu dispositivo.",
|
||||
"scan_qr_code": "Escanear código QR",
|
||||
"select_qr_code": "Selecciona «%(scanQRCode)s»",
|
||||
"sign_in_new_device": "Conectar nuevo dispositivo",
|
||||
"waiting_for_device": "Esperando a que el dispositivo inicie sesión"
|
||||
},
|
||||
"register_action": "Crear cuenta",
|
||||
|
||||
@@ -247,12 +247,15 @@
|
||||
"phone_label": "Telefon",
|
||||
"phone_optional_label": "Telefoninumber (kui soovid)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "Lubades ligipääsu sellele seadmele, annad talle ka täismahulise ligipääsu oma kasutajakontole.",
|
||||
"completing_setup": "Lõpetame uue seadme seadistamise",
|
||||
"confirm_code_match": "Kontrolli, et järgnev kood klapib teises seadmes kuvatava koodiga:",
|
||||
"error_rate_limited": "Liiga palju päringuid napis ajavahemikus. Enne uuesti proovimist palun oota veidi.",
|
||||
"error_unexpected": "Tekkis teadmata viga.",
|
||||
"scan_code_instruction": "Loe QR-koodi seadmega, kus sa oled Matrix'i võrgust välja loginud.",
|
||||
"scan_qr_code": "Loe QR-koodi",
|
||||
"select_qr_code": "Vali „%(scanQRCode)s“",
|
||||
"sign_in_new_device": "Logi sisse uus seade",
|
||||
"waiting_for_device": "Ootame, et teine seade logiks võrku"
|
||||
},
|
||||
"register_action": "Loo konto",
|
||||
|
||||
@@ -233,6 +233,7 @@
|
||||
"qr_code_login": {
|
||||
"error_rate_limited": "Liikaa yrityksiä lyhyessä ajassa. Odota hetki, ennen kuin yrität uudelleen.",
|
||||
"error_unexpected": "Tapahtui odottamaton virhe.",
|
||||
"sign_in_new_device": "Kirjaa sisään uusi laite",
|
||||
"waiting_for_device": "Odotetaan laitteen sisäänkirjautumista"
|
||||
},
|
||||
"register_action": "Luo tili",
|
||||
|
||||
@@ -249,11 +249,13 @@
|
||||
"phone_label": "Numéro de téléphone",
|
||||
"phone_optional_label": "Téléphone (facultatif)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "En autorisant l’accès pour cet appareil, il aura un accès complet à votre compte.",
|
||||
"check_code_explainer": "Cela vérifiera que la connexion à votre autre appareil est sécurisée.",
|
||||
"check_code_heading": "Entrez le numéro affiché sur votre autre appareil",
|
||||
"check_code_input_label": "code à 2 chiffres",
|
||||
"check_code_mismatch": "Les chiffres ne correspondent pas",
|
||||
"completing_setup": "Fin de la configuration de votre nouvel appareil",
|
||||
"confirm_code_match": "Vérifiez que le code ci-dessous correspond à celui sur votre autre appareil :",
|
||||
"error_etag_missing": "Une erreur inattendue s'est produite. Cela peut être dû à une extension de navigateur, à un serveur proxy ou à une mauvaise configuration du serveur.",
|
||||
"error_expired": "Connexion expirée. Veuillez réessayer.",
|
||||
"error_expired_title": "La connexion a pris trop de temps.",
|
||||
@@ -281,6 +283,7 @@
|
||||
"security_code": "Code de sécurité",
|
||||
"security_code_prompt": "Si vous y êtes invité, saisissez le code ci-dessous sur votre autre appareil.",
|
||||
"select_qr_code": "Sélectionnez « %(scanQRCode)s »",
|
||||
"sign_in_new_device": "Connecter le nouvel appareil",
|
||||
"waiting_for_device": "En attente de connexion de l’appareil"
|
||||
},
|
||||
"register_action": "Créer un compte",
|
||||
@@ -1595,7 +1598,6 @@
|
||||
"keyword": "Mot-clé",
|
||||
"keyword_new": "Nouveau mot-clé",
|
||||
"level_activity": "Activité",
|
||||
"level_highlight": "Surligner",
|
||||
"level_muted": "Muet",
|
||||
"level_none": "Aucun",
|
||||
"level_notification": "Notification",
|
||||
@@ -1807,23 +1809,10 @@
|
||||
},
|
||||
"right_panel": {
|
||||
"add_integrations": "Ajouter des widgets, passerelles et robots",
|
||||
"add_topic": "Ajouter un sujet",
|
||||
"files_button": "Fichiers",
|
||||
"pinned_messages": {
|
||||
"empty_title": "Épingler des messages importants afin qu'ils puissent être facilement découverts",
|
||||
"header": {
|
||||
"one": "1 Message épinglé",
|
||||
"other": "%(count)s Messages épinglés"
|
||||
},
|
||||
"limits": {
|
||||
"other": "Vous ne pouvez épingler que jusqu’à %(count)s widgets"
|
||||
},
|
||||
"menu": "Ouvrir le menu",
|
||||
"release_announcement": {
|
||||
"title": "Tous les nouveaux messages épinglés"
|
||||
},
|
||||
"unpin_all": {
|
||||
"button": "Désépingler tous les messages"
|
||||
}
|
||||
},
|
||||
"pinned_messages_button": "Épinglé",
|
||||
@@ -1923,13 +1912,8 @@
|
||||
"forget_room": "Oublier ce salon",
|
||||
"forget_space": "Oublier cet espace",
|
||||
"header": {
|
||||
"n_people_asking_to_join": {
|
||||
"one": "Demander à rejoindre",
|
||||
"other": "%(count)s personnes demandant à se joindre"
|
||||
},
|
||||
"room_is_public": "Ce salon est public"
|
||||
},
|
||||
"header_face_pile_tooltip": "Personnes",
|
||||
"header_untrusted_label": "Non fiable",
|
||||
"inaccessible": "Ce salon ou cet espace n’est pas accessible en ce moment.",
|
||||
"inaccessible_name": "%(roomName)s n’est pas joignable pour le moment.",
|
||||
@@ -1999,15 +1983,11 @@
|
||||
"not_found_title": "Ce salon ou cet espace n’existe pas.",
|
||||
"not_found_title_name": "%(roomName)s n’existe pas.",
|
||||
"peek_join_prompt": "Ceci est un aperçu de %(roomName)s. Voulez-vous rejoindre le salon ?",
|
||||
"pinned_message_banner": {
|
||||
"description": "Ce salon contient des messages épinglés. Cliquez pour les consulter."
|
||||
},
|
||||
"read_topic": "Cliquer pour lire le sujet",
|
||||
"rejecting": "Rejet de l’invitation…",
|
||||
"rejoin_button": "Revenir",
|
||||
"search": {
|
||||
"all_rooms_button": "Rechercher dans tous les salons",
|
||||
"placeholder": "Rechercher des messages…",
|
||||
"this_room_button": "Rechercher dans ce salon"
|
||||
},
|
||||
"status_bar": {
|
||||
@@ -2370,7 +2350,6 @@
|
||||
"brand_version": "Version de %(brand)s :",
|
||||
"clear_cache_reload": "Vider le cache et recharger",
|
||||
"crypto_version": "Version crypto :",
|
||||
"dialog_title": "<strong>Paramètres : </strong> Aide et À propos",
|
||||
"help_link": "Pour obtenir de l’aide sur l’utilisation de %(brand)s, cliquez <a>ici</a>.",
|
||||
"homeserver": "Le serveur d’accueil est <code>%(homeserverUrl)s</code>",
|
||||
"identity_server": "Le serveur d’identité est <code>%(identityServerUrl)s</code>",
|
||||
@@ -2390,9 +2369,7 @@
|
||||
"custom_font_size": "Utiliser une taille personnalisée",
|
||||
"custom_theme_error_downloading": "Erreur lors du téléchargement du thème",
|
||||
"custom_theme_invalid": "Schéma du thème invalide.",
|
||||
"dialog_title": "<strong>Paramètres : </strong> Apparence",
|
||||
"font_size": "Taille de la police",
|
||||
"font_size_default": "%(fontSize)s(par défaut)",
|
||||
"high_contrast": "Contraste élevé",
|
||||
"image_size_default": "Par défaut",
|
||||
"image_size_large": "Grande",
|
||||
@@ -2423,10 +2400,6 @@
|
||||
"add_msisdn_dialog_title": "Ajouter un numéro de téléphone",
|
||||
"add_msisdn_instructions": "Un SMS a été envoyé à +%(msisdn)s. Saisissez le code de vérification qu’il contient.",
|
||||
"add_msisdn_misconfigured": "L’ajout / liaison avec le flux MSISDN est mal configuré",
|
||||
"application_language_reload_hint": "L’application se rechargera après avoir sélectionné une autre langue",
|
||||
"avatar_remove_progress": "Suppression de l'image...",
|
||||
"avatar_save_progress": "Chargement de l'image...",
|
||||
"avatar_upload_error_text_generic": "Le format de fichier n'est peut-être pas pris en charge.",
|
||||
"confirm_adding_email_body": "Cliquez sur le bouton ci-dessous pour confirmer l’ajout de l’adresse e-mail.",
|
||||
"confirm_adding_email_title": "Confirmer l’ajout de l’adresse e-mail",
|
||||
"deactivate_confirm_body": "Voulez-vous vraiment désactiver votre compte ? Ceci est irréversible.",
|
||||
@@ -2446,8 +2419,6 @@
|
||||
"discovery_email_verification_instructions": "Vérifiez le lien dans votre boîte de réception",
|
||||
"discovery_msisdn_empty": "Les options de découverte apparaîtront quand vous aurez ajouté un numéro de téléphone ci-dessus.",
|
||||
"discovery_needs_terms": "Acceptez les conditions de service du serveur d’identité (%(serverName)s) pour vous permettre d’être découvrable par votre adresse e-mail ou votre numéro de téléphone.",
|
||||
"display_name": "Nom d'affichage",
|
||||
"display_name_error": "Impossible de définir le nom d'affichage",
|
||||
"email_address_in_use": "Cette adresse e-mail est déjà utilisée",
|
||||
"email_address_label": "Adresse e-mail",
|
||||
"email_not_verified": "Votre adresse e-mail n’a pas encore été vérifiée",
|
||||
@@ -2472,7 +2443,7 @@
|
||||
"error_share_msisdn_discovery": "Impossible de partager le numéro de téléphone",
|
||||
"identity_server_no_token": "Aucun jeton d’accès d’identité trouvé",
|
||||
"identity_server_not_set": "Serveur d'identité non défini",
|
||||
"language_section": "Langue",
|
||||
"language_section": "Langue et région",
|
||||
"msisdn_in_use": "Ce numéro de téléphone est déjà utilisé",
|
||||
"msisdn_label": "Numéro de téléphone",
|
||||
"msisdn_verification_field_label": "Code de vérification",
|
||||
@@ -2481,12 +2452,9 @@
|
||||
"oidc_manage_button": "Gérer le compte",
|
||||
"password_change_section": "Définir un nouveau mot de passe de compte…",
|
||||
"password_change_success": "Votre mot de passe a été mis à jour.",
|
||||
"personal_info": "Informations personnelles",
|
||||
"profile_subtitle_oidc": "Votre compte est géré séparément par un fournisseur d'identité et certaines de vos informations personnelles ne peuvent donc pas être modifiées ici.",
|
||||
"remove_email_prompt": "Supprimer %(email)s ?",
|
||||
"remove_msisdn_prompt": "Supprimer %(phone)s ?",
|
||||
"spell_check_locale_placeholder": "Choisir une langue",
|
||||
"username": "Nom d’utilisateur"
|
||||
"spell_check_locale_placeholder": "Choisir une langue"
|
||||
},
|
||||
"image_thumbnails": "Afficher les aperçus/vignettes pour les images",
|
||||
"inline_url_previews_default": "Activer l’aperçu des URL par défaut",
|
||||
@@ -2547,20 +2515,12 @@
|
||||
"phrase_strong_enough": "Super ! Cette phrase secrète a l’air assez robuste"
|
||||
},
|
||||
"keyboard": {
|
||||
"dialog_title": "<strong>Paramètres : </strong> Clavier",
|
||||
"title": "Clavier"
|
||||
},
|
||||
"labs": {
|
||||
"dialog_title": "<strong>Paramètres : </strong> Expérimental"
|
||||
},
|
||||
"labs_mjolnir": {
|
||||
"dialog_title": "<strong>Paramètres : </strong> Utilisateurs ignorés"
|
||||
},
|
||||
"notifications": {
|
||||
"default_setting_description": "Ce réglage sera appliqué par défaut à tous vos salons.",
|
||||
"default_setting_section": "Je veux être notifié pour (réglage par défaut)",
|
||||
"desktop_notification_message_preview": "Afficher l’aperçu du message dans la notification de bureau",
|
||||
"dialog_title": "<strong>Paramètres : </strong> Notifications",
|
||||
"email_description": "Recevoir un résumé par courriel des notifications manquées",
|
||||
"email_section": "Résumé en courriel",
|
||||
"email_select": "Sélectionner les adresses auxquelles envoyer les résumés. Gérer vos courriels dans <button>Général</button>.",
|
||||
@@ -2619,7 +2579,6 @@
|
||||
"code_blocks_heading": "Blocs de code",
|
||||
"compact_modern": "Utiliser une mise en page « moderne » plus compacte",
|
||||
"composer_heading": "Compositeur",
|
||||
"dialog_title": "<strong>Paramètres : </strong> Préférences",
|
||||
"enable_hardware_acceleration": "Activer l’accélération matérielle",
|
||||
"enable_tray_icon": "Afficher l’icône dans la barre d’état et minimiser la fenêtre lors de la fermeture",
|
||||
"keyboard_heading": "Raccourcis clavier",
|
||||
@@ -2665,11 +2624,8 @@
|
||||
"cross_signing_self_signing_private_key": "Clé privée d’auto-signature :",
|
||||
"cross_signing_user_signing_private_key": "Clé privée de signature de l’utilisateur :",
|
||||
"cryptography_section": "Chiffrement",
|
||||
"dehydrated_device_description": "La fonctionnalité d’appareil hors ligne vous permet de recevoir des messages chiffrés même lorsque vous n’êtes connecté à aucun appareil",
|
||||
"dehydrated_device_enabled": "Appareil hors ligne activé",
|
||||
"delete_backup": "Supprimer la sauvegarde",
|
||||
"delete_backup_confirm_description": "En êtes-vous sûr ? Vous perdrez vos messages chiffrés si vos clés ne sont pas sauvegardées correctement.",
|
||||
"dialog_title": "<strong>Paramètres : </strong> Sécurité et confidentialité",
|
||||
"e2ee_default_disabled_warning": "L’administrateur de votre serveur a désactivé le chiffrement de bout en bout par défaut dans les salons privés et les conversations privées.",
|
||||
"enable_message_search": "Activer la recherche de messages dans les salons chiffrés",
|
||||
"encryption_section": "Chiffrement",
|
||||
@@ -2747,7 +2703,6 @@
|
||||
"device_unverified_description_current": "Vérifiez cette session pour renforcer la sécurité de votre messagerie.",
|
||||
"device_verified_description": "Cette session est prête pour l’envoi de messages sécurisés.",
|
||||
"device_verified_description_current": "Votre session actuelle est prête pour une messagerie sécurisée.",
|
||||
"dialog_title": "<strong>Paramètres : </strong> Sessions",
|
||||
"error_pusher_state": "Échec lors de la définition de l’état push",
|
||||
"error_set_name": "Impossible d'enregistrer le nom de la session",
|
||||
"filter_all": "Tout",
|
||||
@@ -2789,7 +2744,7 @@
|
||||
"show_details": "Afficher les détails",
|
||||
"sign_in_with_qr": "Associer un nouvel appareil",
|
||||
"sign_in_with_qr_button": "Afficher le QR code",
|
||||
"sign_in_with_qr_description": "Utilisez un code QR pour vous connecter à un autre appareil et configurer votre messagerie sécurisée.",
|
||||
"sign_in_with_qr_description": "Vous pouvez utiliser cet appareil pour vous connecter sur un autre appareil avec un QR code. Vous devrez scanner le QR code affiché sur cet appareil avec votre autre appareil qui n’est pas connecté.",
|
||||
"sign_out": "Se déconnecter de cette session",
|
||||
"sign_out_all_other_sessions": "Déconnecter toutes les autres sessions (%(otherSessionsCount)s)",
|
||||
"sign_out_confirm_description": {
|
||||
@@ -2831,7 +2786,6 @@
|
||||
"show_typing_notifications": "Afficher les notifications de saisie",
|
||||
"showbold": "Afficher toute l'activité dans la liste des salons (points ou nombre de messages non lus)",
|
||||
"sidebar": {
|
||||
"dialog_title": "<strong>Paramètres : </strong> Barre latérale",
|
||||
"metaspaces_favourites_description": "Regroupez tous vos salons et personnes préférés au même endroit.",
|
||||
"metaspaces_home_all_rooms": "Afficher tous les salons",
|
||||
"metaspaces_home_all_rooms_description": "Affiche tous vos salons dans l’accueil, même s’ils font partis d’un espace.",
|
||||
@@ -2840,9 +2794,6 @@
|
||||
"metaspaces_orphans_description": "Regroupe tous les salons n’appartenant pas à un espace au même endroit.",
|
||||
"metaspaces_people_description": "Regrouper toutes vos connaissances au même endroit.",
|
||||
"metaspaces_subsection": "Espaces à afficher",
|
||||
"metaspaces_video_rooms": "Salons vidéo et conférences",
|
||||
"metaspaces_video_rooms_description": "Regroupez tous les salons vidéo et conférences.",
|
||||
"metaspaces_video_rooms_description_invite_extension": "Lors des conférences, vous pouvez inviter des personnes extérieures à Matrix.",
|
||||
"spaces_explainer": "Les espaces sont un nouveau moyen de regrouper les salons et les gens. En plus des espaces auxquels vous participez, vous pouvez également utiliser ceux qui sont prédéfinis.",
|
||||
"title": "Barre latérale"
|
||||
},
|
||||
@@ -2861,7 +2812,6 @@
|
||||
"audio_output_empty": "Aucune sortie audio détectée",
|
||||
"auto_gain_control": "Contrôle automatique du gain",
|
||||
"connection_section": "Connexion",
|
||||
"dialog_title": "<strong>Paramètres : </strong> Audio et vidéo",
|
||||
"echo_cancellation": "Annulation d’écho",
|
||||
"enable_fallback_ice_server": "Autoriser le serveur de secours d’assistance d’appel (%(server)s)",
|
||||
"enable_fallback_ice_server_description": "Concerne seulement les serveurs d’accueil qui n’en proposent pas. Votre adresse IP pourrait être diffusée pendant un appel.",
|
||||
@@ -2883,9 +2833,6 @@
|
||||
"link_title": "Lien vers le salon",
|
||||
"permalink_message": "Lien vers le message sélectionné",
|
||||
"permalink_most_recent": "Lien vers le message le plus récent",
|
||||
"share_call": "Lien d'invitation à la conférence",
|
||||
"share_call_subtitle": "Lien permettant aux utilisateurs externes de rejoindre l'appel sans compte Matrix :",
|
||||
"title_link": "Lien de partage",
|
||||
"title_message": "Partager le message du salon",
|
||||
"title_room": "Partager le salon",
|
||||
"title_user": "Partager l’utilisateur"
|
||||
@@ -2911,7 +2858,6 @@
|
||||
"devtools": "Ouvre la fenêtre des outils de développeur",
|
||||
"discardsession": "Force la session de groupe sortante actuelle dans un salon chiffré à être rejetée",
|
||||
"error_invalid_rendering_type": "Erreur de commande : Impossible de trouver le type de rendu (%(renderingType)s)",
|
||||
"error_invalid_room": "Échec de la commande : Impossible de trouver le salon (%(roomId)s)",
|
||||
"error_invalid_runfn": "Erreur de commande : Impossible de gérer la commande de barre oblique.",
|
||||
"error_invalid_user_in_room": "Impossible de trouver l’utilisateur dans le salon",
|
||||
"help": "Affiche la liste des commandes avec leurs utilisations et descriptions",
|
||||
@@ -3089,7 +3035,7 @@
|
||||
"keyboard_scroll_hint": "Utilisez <arrows/> pour faire défiler",
|
||||
"message_search_section_title": "Autres recherches",
|
||||
"other_rooms_in_space": "Autres salons dans %(spaceName)s",
|
||||
"public_rooms_label": "Salons publics",
|
||||
"public_rooms_label": "Salons public",
|
||||
"public_spaces_label": "Espaces publics",
|
||||
"recent_searches_section_title": "Recherches récentes",
|
||||
"recently_viewed_section_title": "Affiché récemment",
|
||||
@@ -3134,8 +3080,6 @@
|
||||
"one": "%(count)s réponse",
|
||||
"other": "%(count)s réponses"
|
||||
},
|
||||
"empty_description": "Utiliser \"%(replyInThread)s\" lorsque vous survolez un message.",
|
||||
"empty_title": "Les fils de discussion aident à garder vos conversations sur le sujet et à les suivre facilement.",
|
||||
"error_start_thread_existing_relation": "Impossible de créer un fil de discussion à partir d’un événement avec une relation existante",
|
||||
"mark_all_read": "Tout marquer comme lu",
|
||||
"my_threads": "Mes fils de discussion",
|
||||
@@ -3146,8 +3090,6 @@
|
||||
"threads_activity_centre": {
|
||||
"header": "Activité des fils de discussions",
|
||||
"no_rooms_with_threads_notifs": "Vous n’avez pas encore de salons avec des notifications de fil de discussion.",
|
||||
"no_rooms_with_unread_threads": "Vous n'avez pas encore de salons contenant des fils de discussion non lus.",
|
||||
"release_announcement_description": "Les notifications des fils de discussion ont été déplacées. À partir de maintenant, retrouvez-les ici.",
|
||||
"release_announcement_header": "Centre d'activité des fils de discussions"
|
||||
},
|
||||
"time": {
|
||||
@@ -3186,23 +3128,18 @@
|
||||
"report": "Signaler",
|
||||
"resent_unsent_reactions": "Renvoyer %(unsentCount)s réaction(s)",
|
||||
"show_url_preview": "Afficher l’aperçu",
|
||||
"view_related_event": "Voir l’événement associé",
|
||||
"view_related_event": "Afficher les événements liés",
|
||||
"view_source": "Afficher la source"
|
||||
},
|
||||
"creation_summary_dm": "%(creator)s a créé cette conversation privée.",
|
||||
"creation_summary_room": "%(creator)s a créé et configuré le salon.",
|
||||
"decryption_failure": {
|
||||
"blocked": "L'expéditeur vous a empêché de recevoir ce message car votre appareil n'est pas vérifié",
|
||||
"historical_event_no_key_backup": "L'historique des messages n'est pas disponible sur cet appareil",
|
||||
"historical_event_unverified_device": "Vous devez vérifier cet appareil pour accéder à l'historique des messages",
|
||||
"historical_event_user_not_joined": "Vous n'avez pas accès à ce message",
|
||||
"unable_to_decrypt": "Impossible de déchiffrer le message"
|
||||
"historical_event_user_not_joined": "Vous n'avez pas accès à ce message"
|
||||
},
|
||||
"disambiguated_profile": "%(displayName)s (%(matrixId)s)",
|
||||
"download_action_decrypting": "Déchiffrement",
|
||||
"download_action_downloading": "Téléchargement en cours",
|
||||
"download_failed": "Échec du téléchargement",
|
||||
"download_failed_description": "Une erreur s'est produite lors du téléchargement de ce fichier",
|
||||
"edits": {
|
||||
"tooltip_label": "Modifié le %(date)s. Cliquer pour voir les modifications.",
|
||||
"tooltip_sub": "Cliquez pour voir les modifications",
|
||||
@@ -3217,7 +3154,6 @@
|
||||
"you": "Vous avez terminé une <a>diffusion audio</a>"
|
||||
},
|
||||
"io.element.widgets.layout": "%(senderName)s a mis à jour la mise en page du salon",
|
||||
"late_event_separator": "Initialement envoyé%(dateTime)s",
|
||||
"load_error": {
|
||||
"no_permission": "Un instant donné du fil de discussion n’a pu être chargé car vous n’avez pas la permission de le visualiser.",
|
||||
"title": "Échec du chargement de la position dans le fil de discussion",
|
||||
@@ -3650,12 +3586,6 @@
|
||||
"toast_title": "Mettre à jour %(brand)s",
|
||||
"unavailable": "Indisponible"
|
||||
},
|
||||
"update_room_access_modal": {
|
||||
"description": "Pour créer un lien de partage, vous devez autoriser les invités à rejoindre ce salon. Cela peut rendre le salon moins sûr. Lorsque vous aurez terminé l'appel, vous pourrez redéfinir la confidentialité du salon.",
|
||||
"dont_change_description": "Vous pouvez également prendre l'appel dans un salon séparé.",
|
||||
"no_change": "Je ne souhaite pas modifier le niveau d'accès.",
|
||||
"title": "Modifier le niveau d'accès du salon"
|
||||
},
|
||||
"upload_failed_generic": "Le fichier « %(fileName)s » n’a pas pu être envoyé.",
|
||||
"upload_failed_size": "Le fichier « %(fileName)s » dépasse la taille limite autorisée par ce serveur pour les envois",
|
||||
"upload_failed_title": "Échec de l’envoi",
|
||||
@@ -3734,13 +3664,13 @@
|
||||
"no_recent_messages_description": "Essayez de faire défiler le fil de discussion vers le haut pour voir s’il y en a de plus anciens.",
|
||||
"no_recent_messages_title": "Aucun message récent de %(user)s n’a été trouvé"
|
||||
},
|
||||
"redact_button": "Supprimer des messages",
|
||||
"redact_button": "Supprimer les messages récents",
|
||||
"revoke_invite": "Révoquer l’invitation",
|
||||
"room_encrypted": "Les messages dans ce salon sont chiffrés de bout en bout.",
|
||||
"room_encrypted_detail": "Vos messages sont sécurisés et seuls vous et le destinataire avez les clés uniques pour les déchiffrer.",
|
||||
"room_unencrypted": "Les messages dans ce salon ne sont pas chiffrés de bout en bout.",
|
||||
"room_unencrypted_detail": "Dans les salons chiffrés, vos messages sont sécurisés et seuls vous et le destinataire avez les clés uniques pour les déchiffrer.",
|
||||
"share_button": "Partager le profil",
|
||||
"share_button": "Partager le lien vers l’utilisateur",
|
||||
"unban_button_room": "Révoquer le bannissement du salon",
|
||||
"unban_button_space": "Révoquer le bannissement de l’espace",
|
||||
"unban_room_confirm_title": "Annuler le bannissement de %(roomName)s",
|
||||
@@ -3751,7 +3681,6 @@
|
||||
"verify_explainer": "Pour une sécurité supplémentaire, vérifiez cet utilisateur en comparant un code à usage unique sur vos deux appareils."
|
||||
},
|
||||
"user_menu": {
|
||||
"link_new_device": "Associer un nouvel appareil",
|
||||
"settings": "Tous les paramètres",
|
||||
"switch_theme_dark": "Passer au mode sombre",
|
||||
"switch_theme_light": "Passer au mode clair"
|
||||
@@ -3808,7 +3737,6 @@
|
||||
"camera_enabled": "Votre caméra est toujours allumée",
|
||||
"cannot_call_yourself_description": "Vous ne pouvez pas passer d’appel avec vous-même.",
|
||||
"change_input_device": "Change de périphérique d’entrée",
|
||||
"close_lobby": "Fermer la salle d'attente",
|
||||
"connecting": "Connexion",
|
||||
"connection_lost": "La connexion au serveur a été perdue",
|
||||
"connection_lost_description": "Vous ne pouvez pas passer d’appels sans connexion au serveur.",
|
||||
@@ -3822,24 +3750,18 @@
|
||||
"disabled_no_perms_start_video_call": "Vous n’avez pas la permission de démarrer un appel vidéo",
|
||||
"disabled_no_perms_start_voice_call": "Vous n’avez pas la permission de démarrer un appel audio",
|
||||
"disabled_ongoing_call": "Appel en cours",
|
||||
"element_call": "Element Call",
|
||||
"enable_camera": "Activer la caméra",
|
||||
"enable_microphone": "Activer le microphone",
|
||||
"expand": "Revenir à l’appel",
|
||||
"failed_call_live_broadcast_description": "Vous ne pouvez pas démarrer un appel car vous êtes en train d’enregistrer une diffusion en direct. Veuillez terminer cette diffusion pour démarrer un appel.",
|
||||
"failed_call_live_broadcast_title": "Impossible de démarrer un appel",
|
||||
"get_call_link": "Partager le lien de l'appel",
|
||||
"hangup": "Raccrocher",
|
||||
"hide_sidebar_button": "Masquer la barre latérale",
|
||||
"input_devices": "Périphériques d’entrée",
|
||||
"jitsi_call": "Conférence Jitsi",
|
||||
"join_button_tooltip_call_full": "Désolé — Cet appel est actuellement complet",
|
||||
"join_button_tooltip_connecting": "Connexion",
|
||||
"maximise": "Remplir l’écran",
|
||||
"maximise_call": "Plein écran",
|
||||
"metaspace_video_rooms": {
|
||||
"conference_room_section": "Conférences"
|
||||
},
|
||||
"minimise_call": "Quitter le mode plein écran",
|
||||
"misconfigured_server": "L’appel a échoué à cause d’un serveur mal configuré",
|
||||
"misconfigured_server_description": "Demandez à l’administrateur de votre serveur d’accueil (<code>%(homeserverDomain)s</code>) de configurer un serveur TURN afin que les appels fonctionnent de manière fiable.",
|
||||
@@ -3888,7 +3810,6 @@
|
||||
"user_is_presenting": "%(sharerName)s est à l’écran",
|
||||
"video_call": "Appel vidéo",
|
||||
"video_call_started": "Appel vidéo commencé",
|
||||
"video_call_using": "Appel vidéo utilisant :",
|
||||
"voice_call": "Appel audio",
|
||||
"you_are_presenting": "Vous êtes à l’écran"
|
||||
},
|
||||
|
||||
@@ -241,11 +241,14 @@
|
||||
"phone_label": "Telefon",
|
||||
"phone_optional_label": "Telefonszám (nem kötelező)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "Ennek az eszköznek a hozzáférés engedélyezése után az eszköznek teljes hozzáférése lesz a fiókjához.",
|
||||
"completing_setup": "Új eszköz beállításának elvégzése",
|
||||
"confirm_code_match": "Ellenőrizze, hogy az alábbi kód megegyezik a másik eszközödön lévővel:",
|
||||
"error_unexpected": "Nemvárt hiba történt.",
|
||||
"scan_code_instruction": "A kijelentkezett eszközzel olvasd be a QR kódot alább.",
|
||||
"scan_qr_code": "QR kód beolvasása",
|
||||
"select_qr_code": "Kiválasztás „%(scanQRCode)s”",
|
||||
"sign_in_new_device": "Új eszköz bejelentkeztetése",
|
||||
"waiting_for_device": "Várakozás a másik eszköz bejelentkezésére"
|
||||
},
|
||||
"register_action": "Fiók létrehozása",
|
||||
|
||||
@@ -241,11 +241,14 @@
|
||||
"phone_label": "Ponsel",
|
||||
"phone_optional_label": "Nomor telepon (opsional)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "Dengan menerima akses untuk perangkat ini, itu akan memiliki akses penuh ke akun Anda.",
|
||||
"completing_setup": "Menyelesaikan penyiapan perangkat baru Anda",
|
||||
"confirm_code_match": "Periksa bahwa kode di bawah cocok dengan perangkat Anda yang lain:",
|
||||
"error_unexpected": "Sebuah kesalahan terjadi secara tidak terduga.",
|
||||
"scan_code_instruction": "Pindai kode QR di bawah dengan perangkat Anda yang sudah keluar dari akun.",
|
||||
"scan_qr_code": "Pindai kode QR",
|
||||
"select_qr_code": "Pilih '%(scanQRCode)s'",
|
||||
"sign_in_new_device": "Masuk perangkat baru",
|
||||
"waiting_for_device": "Menunggu perangkat untuk masuk"
|
||||
},
|
||||
"register_action": "Buat Akun",
|
||||
|
||||
@@ -220,6 +220,7 @@
|
||||
"phone_label": "Sími",
|
||||
"phone_optional_label": "Sími (valfrjálst)",
|
||||
"qr_code_login": {
|
||||
"sign_in_new_device": "Skrá inn nýtt tæki",
|
||||
"waiting_for_device": "Bíð eftir að tækið skráist inn"
|
||||
},
|
||||
"register_action": "Búa til notandaaðgang",
|
||||
|
||||
@@ -247,12 +247,15 @@
|
||||
"phone_label": "Telefono",
|
||||
"phone_optional_label": "Telefono (facoltativo)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "Approvando l'accesso per questo dispositivo, avrà accesso completo al tuo account.",
|
||||
"completing_setup": "Completamento configurazione nuovo dispositivo",
|
||||
"confirm_code_match": "Controlla che il codice sottostante corrisponda nell'altro dispositivo:",
|
||||
"error_rate_limited": "Troppi tentativi in poco tempo. Attendi un po' prima di riprovare.",
|
||||
"error_unexpected": "Si è verificato un errore imprevisto.",
|
||||
"scan_code_instruction": "Scansiona il codice QR sottostante con il dispositivo che è disconnesso.",
|
||||
"scan_qr_code": "Scansiona codice QR",
|
||||
"select_qr_code": "Seleziona '%(scanQRCode)s'",
|
||||
"sign_in_new_device": "Accedi nel nuovo dispositivo",
|
||||
"waiting_for_device": "In attesa che il dispositivo acceda"
|
||||
},
|
||||
"register_action": "Crea account",
|
||||
|
||||
@@ -231,12 +231,15 @@
|
||||
"phone_label": "電話",
|
||||
"phone_optional_label": "電話番号(任意)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "この端末へのアクセスを許可すると、あなたのアカウントに完全にアクセスできるようになります。",
|
||||
"completing_setup": "新しい端末の設定を完了しています",
|
||||
"confirm_code_match": "以下のコードが他の端末と一致していることを確認してください:",
|
||||
"error_rate_limited": "再試行の数が多すぎます。少し待ってから再度試してください。",
|
||||
"error_unexpected": "予期しないエラーが発生しました。",
|
||||
"scan_code_instruction": "サインアウトした端末で以下のQRコードをスキャンしてください。",
|
||||
"scan_qr_code": "QRコードをスキャン",
|
||||
"select_qr_code": "「%(scanQRCode)s」を選択",
|
||||
"sign_in_new_device": "新しい端末でサインイン",
|
||||
"waiting_for_device": "端末のサインインを待機しています"
|
||||
},
|
||||
"register_action": "アカウントを作成",
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
},
|
||||
"a11y_jump_first_unread_room": "Ga naar het eerste ongelezen kamer.",
|
||||
"action": {
|
||||
"accept": "Accepteren",
|
||||
"accept": "Aannemen",
|
||||
"add": "Toevoegen",
|
||||
"add_existing_room": "Bestaande kamers toevoegen",
|
||||
"add_people": "Personen toevoegen",
|
||||
"approve": "Goedkeuren",
|
||||
"back": "Terug",
|
||||
"call": "Bellen",
|
||||
"cancel": "Annuleer",
|
||||
"cancel": "Annuleren",
|
||||
"change": "Wijzigen",
|
||||
"clear": "Wis",
|
||||
"click": "Klik",
|
||||
@@ -86,7 +86,6 @@
|
||||
"refresh": "Herladen",
|
||||
"register": "Registreren",
|
||||
"reject": "Weigeren",
|
||||
"reload": "Herladen",
|
||||
"remove": "Verwijderen",
|
||||
"rename": "Hernoemen",
|
||||
"reply": "Beantwoorden",
|
||||
@@ -111,7 +110,6 @@
|
||||
"skip": "Overslaan",
|
||||
"start_chat": "Gesprek beginnen",
|
||||
"start_new_chat": "Nieuwe chat beginnen",
|
||||
"stop": "Stop",
|
||||
"submit": "Bevestigen",
|
||||
"subscribe": "Abonneren",
|
||||
"transfer": "Doorschakelen",
|
||||
@@ -222,9 +220,12 @@
|
||||
"phone_label": "Telefoonnummer",
|
||||
"phone_optional_label": "Telefoonnummer (optioneel)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "Door de toegang voor dit apparaat goed te keuren, heeft het volledige toegang tot jouw account.",
|
||||
"completing_setup": "De configuratie van je nieuwe apparaat voltooien",
|
||||
"confirm_code_match": "Controleer of de onderstaande code overeenkomt met je andere apparaat:",
|
||||
"error_unexpected": "Er is een onverwachte fout opgetreden.",
|
||||
"scan_code_instruction": "Scan de onderstaande QR-code met je apparaat dat is uitgelogd.",
|
||||
"sign_in_new_device": "Aanmelden nieuw apparaat",
|
||||
"waiting_for_device": "Wachten op apparaat om in te loggen"
|
||||
},
|
||||
"register_action": "Registreren",
|
||||
|
||||
@@ -250,11 +250,13 @@
|
||||
"phone_label": "Telefon",
|
||||
"phone_optional_label": "Telefon (opcjonalny)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "Akceptując dostęp temu urządzeniu, będzie miał on pełny dostęp do Twojego konta.",
|
||||
"check_code_explainer": "Bezpieczeństwo połączenia z urządzeniem zostanie sprawdzone.",
|
||||
"check_code_heading": "Wprowadź numer wyświetlany na drugim urządzeniu",
|
||||
"check_code_input_label": "2-cyfrowy kod",
|
||||
"check_code_mismatch": "Liczby się nie zgadzają",
|
||||
"completing_setup": "Kończenie konfiguracji nowego urządzenia",
|
||||
"confirm_code_match": "Potwierdź, że kod poniżej pasuje z Twoim drugim urządzeniem:",
|
||||
"error_etag_missing": "Wystąpił nieoczekiwany błąd. Może to być spowodowane rozszerzeniem przeglądarki, serwerem proxy lub błędną konfiguracją serwera.",
|
||||
"error_expired": "Logowanie wygasło. Spróbuj ponownie.",
|
||||
"error_expired_title": "Logowanie nie zostało zakończone na czas",
|
||||
@@ -282,8 +284,7 @@
|
||||
"security_code": "Kod bezpieczeństwa",
|
||||
"security_code_prompt": "Jeśli zostaniesz poproszony, wprowadź poniższy kod na drugim urządzeniu.",
|
||||
"select_qr_code": "Wybierz \"%(scanQRCode)s\"",
|
||||
"unsupported_explainer": "Twój dostawca konta nie obsługuje logowania nowego urządzenia za pomocą kodu QR.",
|
||||
"unsupported_heading": "Kod QR nie jest wspierany",
|
||||
"sign_in_new_device": "Zaloguj nowe urządzenie",
|
||||
"waiting_for_device": "Oczekiwanie na logowanie urządzenia"
|
||||
},
|
||||
"register_action": "Utwórz konto",
|
||||
@@ -451,9 +452,8 @@
|
||||
"all_rooms": "Wszystkie pokoje",
|
||||
"analytics": "Analityka",
|
||||
"and_n_others": {
|
||||
"one": "i jeden inny...",
|
||||
"few": "i %(count)s innych...",
|
||||
"many": "i %(count)s innych..."
|
||||
"other": "i %(count)s innych...",
|
||||
"one": "i jeden inny..."
|
||||
},
|
||||
"android": "Android",
|
||||
"appearance": "Wygląd",
|
||||
@@ -744,7 +744,7 @@
|
||||
"developer_tools": "Narzędzia programistyczne",
|
||||
"edit_setting": "Edytuj ustawienie",
|
||||
"edit_values": "Edytuj wartości",
|
||||
"empty_string": "<empty string>",
|
||||
"empty_string": "<empty•string>",
|
||||
"event_content": "Zawartość wydarzenia",
|
||||
"event_id": "ID wydarzenia: %(eventId)s",
|
||||
"event_sent": "Wydarzenie wysłane!",
|
||||
@@ -800,8 +800,7 @@
|
||||
"show_hidden_events": "Pokaż ukryte wydarzenia na linii czasowej",
|
||||
"spaces": {
|
||||
"one": "<space>",
|
||||
"few": "<%(count)s spacje>",
|
||||
"many": "<%(count)s spacji>"
|
||||
"other": "<%(count)s spacji>"
|
||||
},
|
||||
"state_key": "Klucz stanu",
|
||||
"thread_root_id": "ID Root Wątku:%(threadRootId)s",
|
||||
@@ -1132,24 +1131,20 @@
|
||||
"export_successful": "Eksport zakończony pomyślnie!",
|
||||
"exported_n_events_in_time": {
|
||||
"one": "Wyeksportowano %(count)s wydarzenie w %(seconds)s sekund",
|
||||
"few": "Wyeksportowano %(count)s wydarzenia w %(seconds)s sekund",
|
||||
"many": "Wyeksportowano %(count)s wydarzeń w %(seconds)s sekund"
|
||||
"other": "Wyeksportowano %(count)s wydarzeń w %(seconds)s sekund"
|
||||
},
|
||||
"exporting_your_data": "Eksportowanie Twoich danych",
|
||||
"fetched_n_events": {
|
||||
"one": "Pobrano %(count)s wydarzenie do tej pory",
|
||||
"few": "Pobrano %(count)s wydarzenia do tej pory",
|
||||
"many": "Pobrano %(count)s wydarzeń do tej pory"
|
||||
"one": "Pobrano %(count)s wydarzenie",
|
||||
"other": "Pobrano %(count)s wydarzeń"
|
||||
},
|
||||
"fetched_n_events_in_time": {
|
||||
"one": "Pobrano %(count)s wydarzenie w %(seconds)ss",
|
||||
"few": "Pobrano %(count)s wydarzenia w %(seconds)ss",
|
||||
"many": "Pobrano %(count)s wydarzeń w %(seconds)ss"
|
||||
"other": "Pobrano %(count)s wydarzeń w %(seconds)ss"
|
||||
},
|
||||
"fetched_n_events_with_total": {
|
||||
"one": "Pobrano %(count)s wydarzenie z %(total)s",
|
||||
"few": "Pobrano %(count)s wydarzenia z %(total)s",
|
||||
"many": "Pobrano %(count)s wydarzeń z %(total)s"
|
||||
"other": "Pobrano %(count)s wydarzeń z %(total)s"
|
||||
},
|
||||
"fetching_events": "Pobieranie wydarzeń…",
|
||||
"file_attached": "Plik załączony",
|
||||
@@ -1239,9 +1234,8 @@
|
||||
"in_space": "W przestrzeni %(spaceName)s.",
|
||||
"in_space1_and_space2": "W przestrzeniach %(space1Name)s i %(space2Name)s.",
|
||||
"in_space_and_n_other_spaces": {
|
||||
"one": "W %(spaceName)s i jednej innej przestrzeni.",
|
||||
"few": "W %(spaceName)s i %(count)s innych przestrzeniach.",
|
||||
"many": "W %(spaceName)s i %(count)s innych przestrzeniach."
|
||||
"one": "W %(spaceName)s i %(count)s innej przestrzeni.",
|
||||
"other": "W %(spaceName)s i %(count)s innych przestrzeniach."
|
||||
},
|
||||
"incompatible_browser": {
|
||||
"continue": "Kontynuuj mimo to",
|
||||
@@ -1249,14 +1243,11 @@
|
||||
"detail_can_continue": "Jeśli kontynuujesz, niektóre funkcje mogą przestać działać, jak i istnieje ryzyko utraty danych w przyszłości.",
|
||||
"detail_no_continue": "Zaktualizuj przeglądarkę, jeśli jeszcze tego nie zrobiłeś i spróbuj ponownie.",
|
||||
"learn_more": "Dowiedz się więcej",
|
||||
"linux": "Linux",
|
||||
"macos": "Mac",
|
||||
"supported_browsers": "Dla najlepszego doświadczenia korzystaj z <Chrome>Chrome</Chrome>, <Firefox>Firefox</Firefox>, <Edge>Edge</Edge> lub <Safari>Safari</Safari>.",
|
||||
"title": "%(brand)s nie wspiera tej przeglądarki",
|
||||
"use_desktop_heading": "Zamiast tego użyj %(brand)s Desktop",
|
||||
"use_mobile_heading": "Zamiast tego użyj %(brand)s Mobile",
|
||||
"use_mobile_heading_after_desktop": "lub skorzystaj z naszej aplikacji mobilnej",
|
||||
"windows": "Windows (%(bits)s-bity)"
|
||||
"use_mobile_heading_after_desktop": "lub skorzystaj z naszej aplikacji mobilnej"
|
||||
},
|
||||
"info_tooltip_title": "Informacje",
|
||||
"integration_manager": {
|
||||
@@ -1331,9 +1322,8 @@
|
||||
},
|
||||
"inviting_user1_and_user2": "Zapraszanie %(user1)s i %(user2)s",
|
||||
"inviting_user_and_n_others": {
|
||||
"one": "Zapraszanie %(user)s i jedną inną osobę",
|
||||
"few": "Zapraszanie %(user)s i %(count)s inne",
|
||||
"many": "Zapraszanie %(user)s i %(count)s innych"
|
||||
"one": "Zapraszanie %(user)s i 1 więcej",
|
||||
"other": "Zapraszanie %(user)s i %(count)s innych"
|
||||
},
|
||||
"items_and_n_others": {
|
||||
"other": "<Items/> i %(count)s innych",
|
||||
@@ -1527,7 +1517,7 @@
|
||||
"rules_title": "Zbanuj listę zasad - %(roomName)s",
|
||||
"rules_user": "Zasady użytkownika",
|
||||
"something_went_wrong": "Coś poszło nie tak. Spróbuj ponownie lub sprawdź konsolę przeglądarki dla wskazówek.",
|
||||
"title": "Ignorowani użytkownicy",
|
||||
"title": "Zignorowani użytkownicy",
|
||||
"view_rules": "Zobacz zasady"
|
||||
},
|
||||
"language_dropdown_label": "Rozwiń języki",
|
||||
@@ -1677,8 +1667,7 @@
|
||||
"no_avatar_label": "Dodaj zdjęcie, aby inni mogli Cię rozpoznać.",
|
||||
"only_n_steps_to_go": {
|
||||
"one": "Jeszcze tylko %(count)s krok",
|
||||
"few": "Jeszcze tylko %(count)s kroki",
|
||||
"many": "Jeszcze tylko %(count)s kroków"
|
||||
"other": "Jeszcze tylko %(count)s kroki"
|
||||
},
|
||||
"personal_messaging_action": "Zacznij swoją pierwszą rozmowę",
|
||||
"personal_messaging_title": "Bezpieczna komunikacja dla znajomych i rodziny",
|
||||
@@ -2765,7 +2754,7 @@
|
||||
"error_loading_key_backup_status": "Nie można załadować stanu kopii zapasowej klucza",
|
||||
"export_megolm_keys": "Eksportuj klucze E2E pokojów",
|
||||
"ignore_users_empty": "Nie posiadasz ignorowanych użytkowników.",
|
||||
"ignore_users_section": "Ignorowani użytkownicy",
|
||||
"ignore_users_section": "Zignorowani użytkownicy",
|
||||
"import_megolm_keys": "Importuj klucze pokoju E2E",
|
||||
"key_backup_active": "Ta sesja tworzy kopię zapasową kluczy.",
|
||||
"key_backup_active_version": "Aktywna wersja kopii zapasowej:",
|
||||
@@ -2840,7 +2829,7 @@
|
||||
"error_pusher_state": "Nie udało się ustawić stanu pushera",
|
||||
"error_set_name": "Nie udało się ustawić nazwy sesji",
|
||||
"filter_all": "Wszystkie",
|
||||
"filter_inactive": "Nieaktywne",
|
||||
"filter_inactive": "Nieaktywny",
|
||||
"filter_inactive_description": "Nieaktywne przez %(inactiveAgeDays)s dni lub dłużej",
|
||||
"filter_label": "Filtruj urządzenia",
|
||||
"filter_unverified_description": "Nieprzygotowane do bezpiecznej komunikacji",
|
||||
@@ -2849,7 +2838,7 @@
|
||||
"inactive_days": "Nieaktywne przez %(inactiveAgeDays)s+ dni",
|
||||
"inactive_sessions": "Sesje nieaktywne",
|
||||
"inactive_sessions_explainer_1": "Sesje nieaktywne to sesje, które nie były używane przez dłuższy czas, ale wciąż otrzymują klucze szyfrujące.",
|
||||
"inactive_sessions_explainer_2": "Regularne usuwanie sesji nieaktywnych poprawia bezpieczeństwo, wydajność i upraszcza Tobie wykrywanie podejrzanych sesji.",
|
||||
"inactive_sessions_explainer_2": "Regularne usuwanie sesji nieaktywnych poprawia bezpieczeństwo, wydajność i upraszcza Tobie detekcje podejrzanych sesji.",
|
||||
"inactive_sessions_list_description": "Rozważ wylogowanie się ze starych sesji (%(inactiveAgeDays)s dni lub starsze), jeśli już z nich nie korzystasz.",
|
||||
"ip": "Adres IP",
|
||||
"last_activity": "Ostatnia aktywność",
|
||||
@@ -2879,7 +2868,6 @@
|
||||
"sign_in_with_qr": "Połącz nowe urządzenie",
|
||||
"sign_in_with_qr_button": "Pokaż kod QR",
|
||||
"sign_in_with_qr_description": "Użyj kodu QR, aby zalogować się na innym urządzeniu i skonfigurować bezpieczne przesyłanie wiadomości.",
|
||||
"sign_in_with_qr_unsupported": "Nieobsługiwane przez dostawcę konta",
|
||||
"sign_out": "Wyloguj się z tej sesji",
|
||||
"sign_out_all_other_sessions": "Wyloguj się z wszystkich pozostałych sesji (%(otherSessionsCount)s)",
|
||||
"sign_out_confirm_description": {
|
||||
@@ -2925,7 +2913,7 @@
|
||||
"metaspaces_favourites_description": "Pogrupuj wszystkie swoje ulubione pokoje i osoby w jednym miejscu.",
|
||||
"metaspaces_home_all_rooms": "Pokaż wszystkie pokoje",
|
||||
"metaspaces_home_all_rooms_description": "Pokaż wszystkie swoje pokoje na głównej, nawet jeśli znajdują się w przestrzeni.",
|
||||
"metaspaces_home_description": "Główna to przydatne miejsce, gdzie znajdziesz przegląd wszystkich pokoi.",
|
||||
"metaspaces_home_description": "Główna to przydatne miejsce, gdzie znajdziesz przegląd wszystkiego.",
|
||||
"metaspaces_orphans": "Pokoje poza przestrzenią",
|
||||
"metaspaces_orphans_description": "Pogrupuj wszystkie pokoje, które nie są częścią przestrzeni, w jednym miejscu.",
|
||||
"metaspaces_people_description": "Pogrupuj wszystkie osoby w jednym miejscu.",
|
||||
@@ -3353,7 +3341,7 @@
|
||||
},
|
||||
"m.file": {
|
||||
"error_decrypting": "Błąd odszyfrowywania załącznika",
|
||||
"error_invalid": "Nieprawidłowy plik"
|
||||
"error_invalid": "Nieprawidłowy plik %(extra)s"
|
||||
},
|
||||
"m.image": {
|
||||
"error": "Nie można pokazać zdjęcia z powodu błędu",
|
||||
@@ -3628,9 +3616,8 @@
|
||||
"other": "%(severalUsers)s dołączyło i wyszło %(count)s razy"
|
||||
},
|
||||
"joined_multiple": {
|
||||
"one": "%(severalUsers)s dołączył",
|
||||
"few": "%(severalUsers)s dołączyli %(count)s razy",
|
||||
"many": "%(severalUsers)s dołączyło %(count)s razy"
|
||||
"one": "%(severalUsers)sdołączyło",
|
||||
"other": "%(severalUsers)s dołączyło %(count)s razy"
|
||||
},
|
||||
"kicked": {
|
||||
"one": "zostało usunięte",
|
||||
@@ -3708,9 +3695,8 @@
|
||||
"thread_info_basic": "Z wątku",
|
||||
"typing_indicator": {
|
||||
"more_users": {
|
||||
"one": "%(names)s i jedna inna pisze ...",
|
||||
"few": "%(names)s i %(count)s inne piszą ...",
|
||||
"many": "%(names)s i %(count)s innych pisze ..."
|
||||
"other": "%(names)s i %(count)s innych piszą…",
|
||||
"one": "%(names)s i jedna osoba pisze…"
|
||||
},
|
||||
"one_user": "%(displayName)s pisze…",
|
||||
"two_users": "%(names)s i %(lastPerson)s piszą…"
|
||||
|
||||
@@ -242,11 +242,14 @@
|
||||
"phone_label": "Телефон",
|
||||
"phone_optional_label": "Телефон (не обязательно)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "Разрешив доступ к этому устройству, оно получит полный доступ к вашей учетной записи.",
|
||||
"completing_setup": "Завершение настройки нового устройства",
|
||||
"confirm_code_match": "Проверьте, чтобы код ниже совпадал с тем, что показан на другом устройстве:",
|
||||
"error_unexpected": "Произошла неожиданная ошибка.",
|
||||
"scan_code_instruction": "Отсканируйте приведенный ниже QR-код на устройстве, которое вышло из системы.",
|
||||
"scan_qr_code": "Сканировать QR-код",
|
||||
"select_qr_code": "Выберите '%(scanQRCode)s'",
|
||||
"sign_in_new_device": "Войдите в систему c нового устройства",
|
||||
"waiting_for_device": "Ожидание входа устройства в систему"
|
||||
},
|
||||
"register_action": "Создать учётную запись",
|
||||
|
||||
@@ -242,11 +242,14 @@
|
||||
"phone_label": "Telefón",
|
||||
"phone_optional_label": "Telefón (nepovinné)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "Schválením prístupu pre toto zariadenie bude mať plný prístup k vášmu účtu.",
|
||||
"completing_setup": "Dokončenie nastavenia nového zariadenia",
|
||||
"confirm_code_match": "Skontrolujte, či sa nižšie uvedený kód zhoduje s vaším druhým zariadením:",
|
||||
"error_unexpected": "Vyskytla sa neočakávaná chyba.",
|
||||
"scan_code_instruction": "Naskenujte nižšie uvedený QR kód pomocou zariadenia, ktoré je odhlásené.",
|
||||
"scan_qr_code": "Skenovať QR kód",
|
||||
"select_qr_code": "Vyberte '%(scanQRCode)s'",
|
||||
"sign_in_new_device": "Prihlásiť nové zariadenie",
|
||||
"waiting_for_device": "Čaká sa na prihlásenie zariadenia"
|
||||
},
|
||||
"register_action": "Vytvoriť účet",
|
||||
|
||||
@@ -233,12 +233,15 @@
|
||||
"phone_label": "Telefon",
|
||||
"phone_optional_label": "Telefoni (në daçi)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "Duke miratuar hyrje për këtë pajisje, ajo do të ketë hyrje të plotë në llogarinë tuaj.",
|
||||
"completing_setup": "Po plotësohet ujdisja e pajisjes tuaj të re",
|
||||
"confirm_code_match": "Kontrolloni se kodi më poshtë përkon me atë në pajisjen tuaj tjetër:",
|
||||
"error_rate_limited": "Shumë përpjekje në një kohë të shkurtër. Prisni ca, para se të riprovoni.",
|
||||
"error_unexpected": "Ndodhi një gabim të papritur.",
|
||||
"scan_code_instruction": "Skanoni kodin QR më poshtë me pajisjen ku është bërë dalja.",
|
||||
"scan_qr_code": "Skanoni kodin QR",
|
||||
"select_qr_code": "Përzgjidhni “%(scanQRCode)s”",
|
||||
"sign_in_new_device": "Hyni në pajisje të re",
|
||||
"waiting_for_device": "Po pritet që të bëhet hyrja te pajisja"
|
||||
},
|
||||
"register_action": "Krijoni Llogari",
|
||||
|
||||
@@ -247,12 +247,15 @@
|
||||
"phone_label": "Telefon",
|
||||
"phone_optional_label": "Telefon (valfritt)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "Genom att godkänna åtkomst för den här enheten så får den full åtkomst till ditt konto.",
|
||||
"completing_setup": "Slutför inställning av din nya enhet",
|
||||
"confirm_code_match": "Kolla att koden nedan matchar din andra enhet:",
|
||||
"error_rate_limited": "För många försök under för kort tid. Vänta ett tag innan du försöker igen.",
|
||||
"error_unexpected": "Ett oväntade fel inträffade.",
|
||||
"scan_code_instruction": "Skanna QR-koden nedan med din andra enhet som är utloggad.",
|
||||
"scan_qr_code": "Skanna QR-kod",
|
||||
"select_qr_code": "Välj '%(scanQRCode)s'",
|
||||
"sign_in_new_device": "Logga in ny enhet",
|
||||
"waiting_for_device": "Väntar på att enheter loggar in"
|
||||
},
|
||||
"register_action": "Skapa konto",
|
||||
|
||||
@@ -239,12 +239,15 @@
|
||||
"phone_label": "Телефон",
|
||||
"phone_optional_label": "Телефон (не обов'язково)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "Затвердивши доступ для цього пристрою, ви надасте йому повний доступ до вашого облікового запису.",
|
||||
"completing_setup": "Завершення налаштування нового пристрою",
|
||||
"confirm_code_match": "Перевірте, чи збігається наведений внизу код з кодом на вашому іншому пристрої:",
|
||||
"error_rate_limited": "Забагато спроб за короткий час. Зачекайте трохи, перш ніж повторити спробу.",
|
||||
"error_unexpected": "Виникла непередбачувана помилка.",
|
||||
"scan_code_instruction": "Скануйте QR-код знизу своїм пристроєм, на якому ви вийшли.",
|
||||
"scan_qr_code": "Скануйте QR-код",
|
||||
"select_qr_code": "Виберіть «%(scanQRCode)s»",
|
||||
"sign_in_new_device": "Увійти на новому пристрої",
|
||||
"waiting_for_device": "Очікування входу з пристрою"
|
||||
},
|
||||
"register_action": "Створити обліковий запис",
|
||||
|
||||
@@ -242,7 +242,9 @@
|
||||
"phone_label": "电话",
|
||||
"phone_optional_label": "电话号码(可选)",
|
||||
"qr_code_login": {
|
||||
"completing_setup": "完成新设备的设置"
|
||||
"approve_access_warning": "为此设备批准访问权限后,它对你的帐户有完全的访问权限。",
|
||||
"completing_setup": "完成新设备的设置",
|
||||
"confirm_code_match": "检查以下代码是否与你的其他设备匹配:"
|
||||
},
|
||||
"register_action": "创建账户",
|
||||
"registration": {
|
||||
|
||||
@@ -239,12 +239,15 @@
|
||||
"phone_label": "電話",
|
||||
"phone_optional_label": "電話(選擇性)",
|
||||
"qr_code_login": {
|
||||
"approve_access_warning": "透過批准此裝置的存取權限,其將對您的帳號有完全的存取權限。",
|
||||
"completing_setup": "完成您新裝置的設定",
|
||||
"confirm_code_match": "請確認下列代碼與您另一台裝置上的代碼相符:",
|
||||
"error_rate_limited": "短時間內嘗試太多次,請稍待一段時間後再嘗試。",
|
||||
"error_unexpected": "發生預料之外的錯誤。",
|
||||
"scan_code_instruction": "請用您已登出的裝置掃描下列 QR Code。",
|
||||
"scan_qr_code": "掃描 QR Code",
|
||||
"select_qr_code": "選取「%(scanQRCode)s」",
|
||||
"sign_in_new_device": "登入新裝置",
|
||||
"waiting_for_device": "正在等待裝置登入"
|
||||
},
|
||||
"register_action": "建立帳號",
|
||||
|
||||
@@ -22,12 +22,12 @@ import { Action } from "../dispatcher/actions";
|
||||
// TODO: Move this and related files to the js-sdk or something once finalized.
|
||||
|
||||
export class Mjolnir {
|
||||
private static instance?: Mjolnir;
|
||||
private static instance: Mjolnir | null = null;
|
||||
|
||||
private _lists: BanList[] = []; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
private _roomIds: string[] = []; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
private mjolnirWatchRef?: string;
|
||||
private dispatcherRef?: string;
|
||||
private mjolnirWatchRef: string | null = null;
|
||||
private dispatcherRef: string | null = null;
|
||||
|
||||
public get roomIds(): string[] {
|
||||
return this._roomIds;
|
||||
@@ -61,11 +61,15 @@ export class Mjolnir {
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
SettingsStore.unwatchSetting(this.mjolnirWatchRef);
|
||||
this.mjolnirWatchRef = undefined;
|
||||
if (this.mjolnirWatchRef) {
|
||||
SettingsStore.unwatchSetting(this.mjolnirWatchRef);
|
||||
this.mjolnirWatchRef = null;
|
||||
}
|
||||
|
||||
dis.unregister(this.dispatcherRef);
|
||||
this.dispatcherRef = undefined;
|
||||
if (this.dispatcherRef) {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
this.dispatcherRef = null;
|
||||
}
|
||||
|
||||
MatrixClientPeg.get()?.removeListener(RoomStateEvent.Events, this.onEvent);
|
||||
}
|
||||
|
||||
@@ -643,8 +643,8 @@ export class ElementCall extends Call {
|
||||
public static readonly MEMBER_EVENT_TYPE = new NamespacedValue(null, EventType.GroupCallMemberPrefix);
|
||||
public readonly STUCK_DEVICE_TIMEOUT_MS = 1000 * 60 * 60; // 1 hour
|
||||
|
||||
private settingsStoreCallEncryptionWatcher?: string;
|
||||
private terminationTimer?: number;
|
||||
private settingsStoreCallEncryptionWatcher: string | null = null;
|
||||
private terminationTimer: number | null = null;
|
||||
private _layout = Layout.Tile;
|
||||
public get layout(): Layout {
|
||||
return this._layout;
|
||||
@@ -938,9 +938,13 @@ export class ElementCall extends Call {
|
||||
this.session.off(MatrixRTCSessionEvent.MembershipsChanged, this.onMembershipChanged);
|
||||
this.client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionEnded, this.onRTCSessionEnded);
|
||||
|
||||
SettingsStore.unwatchSetting(this.settingsStoreCallEncryptionWatcher);
|
||||
clearTimeout(this.terminationTimer);
|
||||
this.terminationTimer = undefined;
|
||||
if (this.settingsStoreCallEncryptionWatcher) {
|
||||
SettingsStore.unwatchSetting(this.settingsStoreCallEncryptionWatcher);
|
||||
}
|
||||
if (this.terminationTimer !== null) {
|
||||
clearTimeout(this.terminationTimer);
|
||||
this.terminationTimer = null;
|
||||
}
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
@@ -195,8 +195,7 @@ export default class SettingsStore {
|
||||
* @param {string} watcherReference The watcher reference (received from #watchSetting)
|
||||
* to cancel.
|
||||
*/
|
||||
public static unwatchSetting(watcherReference?: string): void {
|
||||
if (!watcherReference) return;
|
||||
public static unwatchSetting(watcherReference: string): void {
|
||||
if (!SettingsStore.watchers.has(watcherReference)) {
|
||||
logger.warn(`Ending non-existent watcher ID ${watcherReference}`);
|
||||
return;
|
||||
|
||||
@@ -28,7 +28,11 @@ export class FontWatcher implements IWatcher {
|
||||
*/
|
||||
public static readonly DEFAULT_DELTA = 0;
|
||||
|
||||
private dispatcherRef?: string;
|
||||
private dispatcherRef: string | null;
|
||||
|
||||
public constructor() {
|
||||
this.dispatcherRef = null;
|
||||
}
|
||||
|
||||
public async start(): Promise<void> {
|
||||
this.updateFont();
|
||||
@@ -144,6 +148,7 @@ export class FontWatcher implements IWatcher {
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
if (!this.dispatcherRef) return;
|
||||
dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ import { ActionPayload } from "../../dispatcher/payloads";
|
||||
import { SettingLevel } from "../SettingLevel";
|
||||
|
||||
export default class ThemeWatcher {
|
||||
private themeWatchRef?: string;
|
||||
private systemThemeWatchRef?: string;
|
||||
private dispatcherRef?: string;
|
||||
private themeWatchRef: string | null;
|
||||
private systemThemeWatchRef: string | null;
|
||||
private dispatcherRef: string | null;
|
||||
|
||||
private preferDark: MediaQueryList;
|
||||
private preferLight: MediaQueryList;
|
||||
@@ -29,6 +29,10 @@ export default class ThemeWatcher {
|
||||
private currentTheme: string;
|
||||
|
||||
public constructor() {
|
||||
this.themeWatchRef = null;
|
||||
this.systemThemeWatchRef = null;
|
||||
this.dispatcherRef = null;
|
||||
|
||||
// we have both here as each may either match or not match, so by having both
|
||||
// we can get the tristate of dark/light/unsupported
|
||||
this.preferDark = (<any>global).matchMedia("(prefers-color-scheme: dark)");
|
||||
@@ -51,9 +55,9 @@ export default class ThemeWatcher {
|
||||
this.preferDark.removeEventListener("change", this.onChange);
|
||||
this.preferLight.removeEventListener("change", this.onChange);
|
||||
this.preferHighContrast.removeEventListener("change", this.onChange);
|
||||
SettingsStore.unwatchSetting(this.systemThemeWatchRef);
|
||||
SettingsStore.unwatchSetting(this.themeWatchRef);
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.systemThemeWatchRef) SettingsStore.unwatchSetting(this.systemThemeWatchRef);
|
||||
if (this.themeWatchRef) SettingsStore.unwatchSetting(this.themeWatchRef);
|
||||
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
private onChange = (): void => {
|
||||
|
||||
@@ -65,7 +65,7 @@ export abstract class AsyncStore<T extends object> extends EventEmitter {
|
||||
* Stops the store's listening functions, such as the listener to the dispatcher.
|
||||
*/
|
||||
protected stop(): void {
|
||||
this.dispatcher.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef) this.dispatcher.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,7 @@ export abstract class AsyncStoreWithClient<T extends object> extends AsyncStore<
|
||||
const asyncStore = this; // eslint-disable-line @typescript-eslint/no-this-alias
|
||||
this.readyStore = new (class extends ReadyWatchingStore {
|
||||
public get mxClient(): MatrixClient | null {
|
||||
return this.matrixClient ?? null;
|
||||
return this.matrixClient;
|
||||
}
|
||||
|
||||
protected async onReady(): Promise<any> {
|
||||
|
||||
@@ -142,7 +142,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||
this.matrixClient.removeListener(BeaconEvent.Destroy, this.onDestroyBeacon);
|
||||
this.matrixClient.removeListener(RoomStateEvent.Members, this.onRoomStateMembers);
|
||||
}
|
||||
SettingsStore.unwatchSetting(this.dynamicWatcherRef);
|
||||
SettingsStore.unwatchSetting(this.dynamicWatcherRef ?? "");
|
||||
|
||||
this.clearBeacons();
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ import { Action } from "../dispatcher/actions";
|
||||
import { MatrixDispatcher } from "../dispatcher/dispatcher";
|
||||
|
||||
export abstract class ReadyWatchingStore extends EventEmitter implements IDestroyable {
|
||||
protected matrixClient?: MatrixClient;
|
||||
private dispatcherRef?: string;
|
||||
protected matrixClient: MatrixClient | null = null;
|
||||
private dispatcherRef: string | null = null;
|
||||
|
||||
public constructor(protected readonly dispatcher: MatrixDispatcher) {
|
||||
super();
|
||||
@@ -35,7 +35,7 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro
|
||||
}
|
||||
|
||||
public get mxClient(): MatrixClient | null {
|
||||
return this.matrixClient ?? null; // for external readonly access
|
||||
return this.matrixClient; // for external readonly access
|
||||
}
|
||||
|
||||
public useUnitTestClient(cli: MatrixClient): void {
|
||||
@@ -43,7 +43,7 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.dispatcher.unregister(this.dispatcherRef);
|
||||
if (this.dispatcherRef !== null) this.dispatcher.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
protected async onReady(): Promise<void> {
|
||||
@@ -80,7 +80,7 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro
|
||||
} else if (payload.action === "on_client_not_viable" || payload.action === Action.OnLoggedOut) {
|
||||
if (this.matrixClient) {
|
||||
await this.onNotReady();
|
||||
this.matrixClient = undefined;
|
||||
this.matrixClient = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -414,58 +414,6 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
||||
await client._unstable_updateDelayedEvent(delayId, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@link WidgetDriver#sendToDevice}
|
||||
*/
|
||||
public async sendToDevice(
|
||||
eventType: string,
|
||||
encrypted: boolean,
|
||||
contentMap: { [userId: string]: { [deviceId: string]: object } },
|
||||
): Promise<void> {
|
||||
const client = MatrixClientPeg.safeGet();
|
||||
|
||||
if (encrypted) {
|
||||
const crypto = client.getCrypto();
|
||||
if (!crypto) throw new Error("E2EE not enabled");
|
||||
|
||||
// attempt to re-batch these up into a single request
|
||||
const invertedContentMap: { [content: string]: { userId: string; deviceId: string }[] } = {};
|
||||
|
||||
for (const userId of Object.keys(contentMap)) {
|
||||
const userContentMap = contentMap[userId];
|
||||
for (const deviceId of Object.keys(userContentMap)) {
|
||||
const content = userContentMap[deviceId];
|
||||
const stringifiedContent = JSON.stringify(content);
|
||||
invertedContentMap[stringifiedContent] = invertedContentMap[stringifiedContent] || [];
|
||||
invertedContentMap[stringifiedContent].push({ userId, deviceId });
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
Object.entries(invertedContentMap).map(async ([stringifiedContent, recipients]) => {
|
||||
const batch = await crypto.encryptToDeviceMessages(
|
||||
eventType,
|
||||
recipients,
|
||||
JSON.parse(stringifiedContent),
|
||||
);
|
||||
|
||||
await client.queueToDevice(batch);
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
await client.queueToDevice({
|
||||
eventType,
|
||||
batch: Object.entries(contentMap).flatMap(([userId, userContentMap]) =>
|
||||
Object.entries(userContentMap).map(([deviceId, content]) => ({
|
||||
userId,
|
||||
deviceId,
|
||||
payload: content,
|
||||
})),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private pickRooms(roomIds?: (string | Symbols.AnyRoom)[]): Room[] {
|
||||
const client = MatrixClientPeg.get();
|
||||
if (!client) throw new Error("Not attached to a client");
|
||||
|
||||
@@ -91,9 +91,9 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
||||
this.byRoom = new MapWithDefault(() => new Map());
|
||||
|
||||
this.matrixClient?.off(RoomStateEvent.Events, this.updateRoomFromState);
|
||||
SettingsStore.unwatchSetting(this.pinnedRef);
|
||||
SettingsStore.unwatchSetting(this.layoutRef);
|
||||
SettingsStore.unwatchSetting(this.dynamicRef);
|
||||
if (this.pinnedRef) SettingsStore.unwatchSetting(this.pinnedRef);
|
||||
if (this.layoutRef) SettingsStore.unwatchSetting(this.layoutRef);
|
||||
if (this.dynamicRef) SettingsStore.unwatchSetting(this.dynamicRef);
|
||||
WidgetStore.instance.off(UPDATE_EVENT, this.updateFromWidgetStore);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ const getTitle = (kind: Kind): string => {
|
||||
switch (kind) {
|
||||
case Kind.SET_UP_ENCRYPTION:
|
||||
return _t("encryption|set_up_toast_title");
|
||||
case Kind.UPGRADE_ENCRYPTION:
|
||||
return _t("encryption|upgrade_toast_title");
|
||||
case Kind.VERIFY_THIS_SESSION:
|
||||
return _t("encryption|verify_toast_title");
|
||||
}
|
||||
@@ -31,6 +33,7 @@ const getTitle = (kind: Kind): string => {
|
||||
const getIcon = (kind: Kind): string => {
|
||||
switch (kind) {
|
||||
case Kind.SET_UP_ENCRYPTION:
|
||||
case Kind.UPGRADE_ENCRYPTION:
|
||||
return "secure_backup";
|
||||
case Kind.VERIFY_THIS_SESSION:
|
||||
return "verification_warning";
|
||||
@@ -41,6 +44,8 @@ const getSetupCaption = (kind: Kind): string => {
|
||||
switch (kind) {
|
||||
case Kind.SET_UP_ENCRYPTION:
|
||||
return _t("action|continue");
|
||||
case Kind.UPGRADE_ENCRYPTION:
|
||||
return _t("action|upgrade");
|
||||
case Kind.VERIFY_THIS_SESSION:
|
||||
return _t("action|verify");
|
||||
}
|
||||
@@ -49,6 +54,7 @@ const getSetupCaption = (kind: Kind): string => {
|
||||
const getDescription = (kind: Kind): string => {
|
||||
switch (kind) {
|
||||
case Kind.SET_UP_ENCRYPTION:
|
||||
case Kind.UPGRADE_ENCRYPTION:
|
||||
return _t("encryption|set_up_toast_description");
|
||||
case Kind.VERIFY_THIS_SESSION:
|
||||
return _t("encryption|verify_toast_description");
|
||||
@@ -57,6 +63,7 @@ const getDescription = (kind: Kind): string => {
|
||||
|
||||
export enum Kind {
|
||||
SET_UP_ENCRYPTION = "set_up_encryption",
|
||||
UPGRADE_ENCRYPTION = "upgrade_encryption",
|
||||
VERIFY_THIS_SESSION = "verify_this_session",
|
||||
}
|
||||
|
||||
|
||||
47
src/utils/UserInteractiveAuth.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 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 { AuthDict } from "matrix-js-sdk/src/interactive-auth";
|
||||
import { UIAResponse } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import Modal from "../Modal";
|
||||
import InteractiveAuthDialog, { InteractiveAuthDialogProps } from "../components/views/dialogs/InteractiveAuthDialog";
|
||||
|
||||
type FunctionWithUIA<R, A> = (auth?: AuthDict, ...args: A[]) => Promise<UIAResponse<R>>;
|
||||
|
||||
export function wrapRequestWithDialog<R, A = any>(
|
||||
requestFunction: FunctionWithUIA<R, A>,
|
||||
opts: Omit<InteractiveAuthDialogProps<R>, "makeRequest" | "onFinished">,
|
||||
): (...args: A[]) => Promise<R> {
|
||||
return async function (...args): Promise<R> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const boundFunction = requestFunction.bind(opts.matrixClient) as FunctionWithUIA<R, A>;
|
||||
boundFunction(undefined, ...args)
|
||||
.then((res) => resolve(res as R))
|
||||
.catch((error) => {
|
||||
if (error.httpStatus !== 401 || !error.data?.flows) {
|
||||
// doesn't look like an interactive-auth failure
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
Modal.createDialog(InteractiveAuthDialog, {
|
||||
...opts,
|
||||
authData: error.data,
|
||||
makeRequest: (authData: AuthDict) => boundFunction(authData, ...args),
|
||||
onFinished: (success, result) => {
|
||||
if (success) {
|
||||
resolve(result as R);
|
||||
} else {
|
||||
reject(result);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -161,19 +161,24 @@ export default class HTMLExporter extends Exporter {
|
||||
<div class="mx_MatrixChat_wrapper" aria-hidden="false">
|
||||
<div class="mx_MatrixChat">
|
||||
<main class="mx_RoomView">
|
||||
<div class="mx_Flex mx_RoomHeader light-panel">
|
||||
${roomAvatar}
|
||||
<div class="mx_RoomHeader_infoWrapper">
|
||||
<div
|
||||
dir="auto"
|
||||
class="mx_RoomHeader_info"
|
||||
title="${safeRoomName}"
|
||||
>
|
||||
<span class="mx_RoomHeader_truncated mx_lineClamp">
|
||||
${safeRoomName}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mx_LegacyRoomHeader light-panel">
|
||||
<div class="mx_LegacyRoomHeader_wrapper" aria-owns="mx_RightPanel">
|
||||
<div class="mx_LegacyRoomHeader_avatar">
|
||||
<div class="mx_DecoratedRoomAvatar">
|
||||
${roomAvatar}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx_LegacyRoomHeader_name">
|
||||
<div
|
||||
dir="auto"
|
||||
class="mx_LegacyRoomHeader_nametext"
|
||||
title="${safeRoomName}"
|
||||
>
|
||||
${safeRoomName}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx_LegacyRoomHeader_topic" dir="auto"> ${safeTopic} </div>
|
||||
</div>
|
||||
</div>
|
||||
${previousMessagesLink}
|
||||
<div class="mx_MainSplit">
|
||||
|
||||
@@ -130,14 +130,6 @@ a.mx_reply_anchor:hover {
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomHeader {
|
||||
--mx-flex-display: flex;
|
||||
--mx-flex-direction: row;
|
||||
--mx-flex-align: center;
|
||||
--mx-flex-justify: start;
|
||||
--mx-flex-gap: var(--cpd-space-3x);
|
||||
}
|
||||
|
||||
.mx_ReplyChain_Export {
|
||||
margin-top: 0;
|
||||
margin-bottom: 5px;
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import React, { StrictMode } from "react";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
|
||||
import { MatrixClient, MatrixEvent, RuleId } from "matrix-js-sdk/src/matrix";
|
||||
@@ -76,11 +76,9 @@ export function pillifyLinks(
|
||||
const pillContainer = document.createElement("span");
|
||||
|
||||
const pill = (
|
||||
<StrictMode>
|
||||
<TooltipProvider>
|
||||
<Pill url={href} inMessage={true} room={room} shouldShowPillAvatar={shouldShowPillAvatar} />
|
||||
</TooltipProvider>
|
||||
</StrictMode>
|
||||
<TooltipProvider>
|
||||
<Pill url={href} inMessage={true} room={room} shouldShowPillAvatar={shouldShowPillAvatar} />
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
||||
ReactDOM.render(pill, pillContainer);
|
||||
@@ -135,16 +133,14 @@ export function pillifyLinks(
|
||||
|
||||
const pillContainer = document.createElement("span");
|
||||
const pill = (
|
||||
<StrictMode>
|
||||
<TooltipProvider>
|
||||
<Pill
|
||||
type={PillType.AtRoomMention}
|
||||
inMessage={true}
|
||||
room={room}
|
||||
shouldShowPillAvatar={shouldShowPillAvatar}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
</StrictMode>
|
||||
<TooltipProvider>
|
||||
<Pill
|
||||
type={PillType.AtRoomMention}
|
||||
inMessage={true}
|
||||
room={room}
|
||||
shouldShowPillAvatar={shouldShowPillAvatar}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
||||
ReactDOM.render(pill, pillContainer);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import React, { StrictMode } from "react";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
|
||||
@@ -53,13 +53,11 @@ export function tooltipifyLinks(rootNodes: ArrayLike<Element>, ignoredNodes: Ele
|
||||
// wrapping the link with the LinkWithTooltip component, keeping the same children. Ideally we'd do this
|
||||
// without the superfluous span but this is not something React trivially supports at this time.
|
||||
const tooltip = (
|
||||
<StrictMode>
|
||||
<TooltipProvider>
|
||||
<LinkWithTooltip tooltip={href}>
|
||||
<span dangerouslySetInnerHTML={{ __html: node.innerHTML }} />
|
||||
</LinkWithTooltip>
|
||||
</TooltipProvider>
|
||||
</StrictMode>
|
||||
<TooltipProvider>
|
||||
<LinkWithTooltip tooltip={href}>
|
||||
<span dangerouslySetInnerHTML={{ __html: node.innerHTML }} />
|
||||
</LinkWithTooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
||||
ReactDOM.render(tooltip, node);
|
||||
|
||||
@@ -12,7 +12,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
// To ensure we load the browser-matrix version first
|
||||
import "matrix-js-sdk/src/browser-index";
|
||||
import React, { ReactElement, StrictMode } from "react";
|
||||
import React, { ReactElement } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { createClient, AutoDiscovery, ClientConfig } from "matrix-js-sdk/src/matrix";
|
||||
import { WrapperLifecycle, WrapperOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WrapperLifecycle";
|
||||
@@ -111,19 +111,17 @@ export async function loadApp(fragParams: {}, matrixChatRef: React.Ref<MatrixCha
|
||||
|
||||
return (
|
||||
<wrapperOpts.Wrapper>
|
||||
<StrictMode>
|
||||
<MatrixChat
|
||||
ref={matrixChatRef}
|
||||
onNewScreen={onNewScreen}
|
||||
config={config}
|
||||
realQueryParams={params}
|
||||
startingFragmentQueryParams={fragParams}
|
||||
enableGuest={!config.disable_guests}
|
||||
onTokenLoginCompleted={onTokenLoginCompleted}
|
||||
initialScreenAfterLogin={initialScreenAfterLogin}
|
||||
defaultDeviceDisplayName={defaultDeviceName}
|
||||
/>
|
||||
</StrictMode>
|
||||
<MatrixChat
|
||||
ref={matrixChatRef}
|
||||
onNewScreen={onNewScreen}
|
||||
config={config}
|
||||
realQueryParams={params}
|
||||
startingFragmentQueryParams={fragParams}
|
||||
enableGuest={!config.disable_guests}
|
||||
onTokenLoginCompleted={onTokenLoginCompleted}
|
||||
initialScreenAfterLogin={initialScreenAfterLogin}
|
||||
defaultDeviceDisplayName={defaultDeviceName}
|
||||
/>
|
||||
</wrapperOpts.Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import * as ReactDOM from "react-dom";
|
||||
import React, { StrictMode } from "react";
|
||||
import * as React from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import * as languageHandler from "../languageHandler";
|
||||
@@ -105,9 +105,7 @@ export async function showError(title: string, messages?: string[]): Promise<voi
|
||||
"../async-components/structures/ErrorView"
|
||||
);
|
||||
window.matrixChat = ReactDOM.render(
|
||||
<StrictMode>
|
||||
<ErrorView title={title} messages={messages} />
|
||||
</StrictMode>,
|
||||
<ErrorView title={title} messages={messages} />,
|
||||
document.getElementById("matrixchat"),
|
||||
);
|
||||
}
|
||||
@@ -118,9 +116,7 @@ export async function showIncompatibleBrowser(onAccept: () => void): Promise<voi
|
||||
"../async-components/structures/ErrorView"
|
||||
);
|
||||
window.matrixChat = ReactDOM.render(
|
||||
<StrictMode>
|
||||
<UnsupportedBrowserView onAccept={onAccept} />
|
||||
</StrictMode>,
|
||||
<UnsupportedBrowserView onAccept={onAccept} />,
|
||||
document.getElementById("matrixchat"),
|
||||
);
|
||||
}
|
||||
|
||||