diff --git a/.github/workflows/layered-build.yaml b/.github/workflows/layered-build.yaml index 7235b4020f..c9d7e89a75 100644 --- a/.github/workflows/layered-build.yaml +++ b/.github/workflows/layered-build.yaml @@ -16,4 +16,16 @@ jobs: path: element-web/webapp # We'll only use this in a triggered job, then we're done with it retention-days: 1 + - uses: actions/github-script@v3.1.0 + with: + script: | + var fs = require('fs'); + fs.writeFileSync('${{github.workspace}}/pr.json', JSON.stringify(context.payload.pull_request)); + - name: Upload PR Info + uses: actions/upload-artifact@v2 + with: + name: pr.json + path: pr.json + # We'll only use this in a triggered job, then we're done with it + retention-days: 1 diff --git a/.github/workflows/netflify.yaml b/.github/workflows/netflify.yaml index 9d65dd5926..a6a408bdbd 100644 --- a/.github/workflows/netflify.yaml +++ b/.github/workflows/netflify.yaml @@ -33,18 +33,48 @@ jobs: }); var fs = require('fs'); fs.writeFileSync('${{github.workspace}}/previewbuild.zip', Buffer.from(download.data)); - - run: unzip previewbuild.zip && rm previewbuild.zip + + var prInfoArtifact = artifacts.data.artifacts.filter((artifact) => { + return artifact.name == "pr.json" + })[0]; + var download = await github.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: prInfoArtifact.id, + archive_format: 'zip', + }); + var fs = require('fs'); + fs.writeFileSync('${{github.workspace}}/pr.json.zip', Buffer.from(download.data)); + - name: Extract Artifacts + run: unzip -d webapp previewbuild.zip && rm previewbuild.zip && unzip pr.json.zip && rm pr.json.zip + - name: 'Read PR Info' + id: readctx + uses: actions/github-script@v3.1.0 + with: + script: | + var fs = require('fs'); + var pr = JSON.parse(fs.readFileSync('${{github.workspace}}/pr.json')); + console.log(`::set-output name=prnumber::${pr.number}`); - name: Deploy to Netlify + id: netlify uses: nwtgck/actions-netlify@v1.2 with: - publish-dir: . - github-token: ${{ secrets.GITHUB_TOKEN }} + publish-dir: webapp deploy-message: "Deploy from GitHub Actions" - enable-pull-request-comment: true + # These don't work because we're in workflow_run + enable-pull-request-comment: false enable-commit-comment: false - overwrites-pull-request-comment: true env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} timeout-minutes: 1 + - name: Edit PR Description + uses: velas/pr-description@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + pull-request-number: ${{ steps.readctx.outputs.prnumber }} + description-message: | + Preview: ${{ steps.netlify.outputs.deploy-url }} + ⚠️ Do you trust the author of this PR? Maybe this build will steal your keys or give you malware. Exercise caution. Use test accounts. diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d65a524d1..6f71c1414c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,76 @@ -Changes in [3.27.0](https://github.com/vector-im/element-desktop/releases/tag/v3.27.0) (2021-07-02) +Changes in [3.28.1](https://github.com/vector-im/element-desktop/releases/tag/v3.28.1) (2021-08-17) +=================================================================================================== + +## 🐛 Bug Fixes + * Fix multiple VoIP regressions ([matrix-org/matrix-js-sdk#1860](https://github.com/matrix-org/matrix-js-sdk/pull/1860)). + +Changes in [3.28.0](https://github.com/vector-im/element-desktop/releases/tag/v3.28.0) (2021-08-16) +=================================================================================================== + +## ✨ Features + * Show how long a call was on call tiles ([\#6570](https://github.com/matrix-org/matrix-react-sdk/pull/6570)). Fixes vector-im/element-web#18405. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Add regional indicators to emoji picker ([\#6490](https://github.com/matrix-org/matrix-react-sdk/pull/6490)). Fixes vector-im/element-web#14963. Contributed by [robintown](https://github.com/robintown). + * Make call control buttons accessible to screen reader users ([\#6181](https://github.com/matrix-org/matrix-react-sdk/pull/6181)). Fixes vector-im/element-web#18358. Contributed by [pvagner](https://github.com/pvagner). + * Skip sending a thumbnail if it is not a sufficient saving over the original ([\#6559](https://github.com/matrix-org/matrix-react-sdk/pull/6559)). Fixes vector-im/element-web#17906. + * Increase PiP snapping speed ([\#6539](https://github.com/matrix-org/matrix-react-sdk/pull/6539)). Fixes vector-im/element-web#18371. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Improve and move the incoming call toast ([\#6470](https://github.com/matrix-org/matrix-react-sdk/pull/6470)). Fixes vector-im/element-web#17912. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Allow all of the URL schemes that Firefox allows ([\#6457](https://github.com/matrix-org/matrix-react-sdk/pull/6457)). Contributed by [aaronraimist](https://github.com/aaronraimist). + * Improve bubble layout colors ([\#6452](https://github.com/matrix-org/matrix-react-sdk/pull/6452)). Fixes vector-im/element-web#18081. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Spaces let users switch between Home and All Rooms behaviours ([\#6497](https://github.com/matrix-org/matrix-react-sdk/pull/6497)). Fixes vector-im/element-web#18093. + * Support for MSC2285 (hidden read receipts) ([\#6390](https://github.com/matrix-org/matrix-react-sdk/pull/6390)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Group pinned message events with MELS ([\#6349](https://github.com/matrix-org/matrix-react-sdk/pull/6349)). Fixes vector-im/element-web#17938. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Make version copiable ([\#6227](https://github.com/matrix-org/matrix-react-sdk/pull/6227)). Fixes vector-im/element-web#17603 and vector-im/element-web#18329. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Improve voice messages uploading state ([\#6530](https://github.com/matrix-org/matrix-react-sdk/pull/6530)). Fixes vector-im/element-web#18226 and vector-im/element-web#18224. + * Add surround with feature ([\#5510](https://github.com/matrix-org/matrix-react-sdk/pull/5510)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Improve call event tile wording ([\#6545](https://github.com/matrix-org/matrix-react-sdk/pull/6545)). Fixes vector-im/element-web#18376. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Show an avatar/a turned off microphone icon for muted users ([\#6486](https://github.com/matrix-org/matrix-react-sdk/pull/6486)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Prompt user to leave rooms/subspaces in a space when leaving space ([\#6424](https://github.com/matrix-org/matrix-react-sdk/pull/6424)). Fixes vector-im/element-web#18071. + * Add customisation point to override widget variables ([\#6455](https://github.com/matrix-org/matrix-react-sdk/pull/6455)). Fixes vector-im/element-web#18035. + * Add support for screen sharing in 1:1 calls ([\#5992](https://github.com/matrix-org/matrix-react-sdk/pull/5992)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + +## 🐛 Bug Fixes + * [Release] Fix glare related regressions ([\#6622](https://github.com/matrix-org/matrix-react-sdk/pull/6622)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * [Release] Fix PiP of held calls ([\#6612](https://github.com/matrix-org/matrix-react-sdk/pull/6612)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * [Release] Fix toast colors ([\#6607](https://github.com/matrix-org/matrix-react-sdk/pull/6607)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix [object Object] in Widget Permissions ([\#6560](https://github.com/matrix-org/matrix-react-sdk/pull/6560)). Fixes vector-im/element-web#18384. Contributed by [Palid](https://github.com/Palid). + * Fix right margin for events on IRC layout ([\#6542](https://github.com/matrix-org/matrix-react-sdk/pull/6542)). Fixes vector-im/element-web#18354. + * Mirror only usermedia feeds ([\#6512](https://github.com/matrix-org/matrix-react-sdk/pull/6512)). Fixes vector-im/element-web#5633. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix LogoutDialog warning + TypeScript migration ([\#6533](https://github.com/matrix-org/matrix-react-sdk/pull/6533)). + * Fix the wrong font being used in the room topic field ([\#6527](https://github.com/matrix-org/matrix-react-sdk/pull/6527)). Fixes vector-im/element-web#18339. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix inconsistent styling for links on hover ([\#6513](https://github.com/matrix-org/matrix-react-sdk/pull/6513)). Contributed by [janogarcia](https://github.com/janogarcia). + * Fix incorrect height for encoded placeholder images ([\#6514](https://github.com/matrix-org/matrix-react-sdk/pull/6514)). Contributed by [Palid](https://github.com/Palid). + * Fix call events layout for message bubble ([\#6465](https://github.com/matrix-org/matrix-react-sdk/pull/6465)). Fixes vector-im/element-web#18144. + * Improve subspaces and some utilities around room/space creation ([\#6458](https://github.com/matrix-org/matrix-react-sdk/pull/6458)). Fixes vector-im/element-web#18090 vector-im/element-web#18091 and vector-im/element-web#17256. + * Restore pointer cursor for SenderProfile in message bubbles ([\#6501](https://github.com/matrix-org/matrix-react-sdk/pull/6501)). Fixes vector-im/element-web#18249. + * Fix issues with the Call View ([\#6472](https://github.com/matrix-org/matrix-react-sdk/pull/6472)). Fixes vector-im/element-web#18221. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Align event list summary read receipts when using message bubbles ([\#6500](https://github.com/matrix-org/matrix-react-sdk/pull/6500)). Fixes vector-im/element-web#18143. + * Better positioning for unbubbled events in timeline ([\#6477](https://github.com/matrix-org/matrix-react-sdk/pull/6477)). Fixes vector-im/element-web#18132. + * Realign reactions row with messages in modern layout ([\#6491](https://github.com/matrix-org/matrix-react-sdk/pull/6491)). Fixes vector-im/element-web#18118. Contributed by [robintown](https://github.com/robintown). + * Fix CreateRoomDialog exploding when making public room outside of a space ([\#6492](https://github.com/matrix-org/matrix-react-sdk/pull/6492)). Fixes vector-im/element-web#18275. + * Fix call crashing because `element` was undefined ([\#6488](https://github.com/matrix-org/matrix-react-sdk/pull/6488)). Fixes vector-im/element-web#18270. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Upscale thumbnails to the container size ([\#6589](https://github.com/matrix-org/matrix-react-sdk/pull/6589)). Fixes vector-im/element-web#18307. + * Fix create room dialog in spaces no longer adding to the space ([\#6587](https://github.com/matrix-org/matrix-react-sdk/pull/6587)). Fixes vector-im/element-web#18465. + * Don't show a modal on call reject/user hangup ([\#6580](https://github.com/matrix-org/matrix-react-sdk/pull/6580)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fade Call View Buttons after `componentDidMount` ([\#6581](https://github.com/matrix-org/matrix-react-sdk/pull/6581)). Fixes vector-im/element-web#18439. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix missing expand button on codeblocks ([\#6565](https://github.com/matrix-org/matrix-react-sdk/pull/6565)). Fixes vector-im/element-web#18388. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * allow customizing the bubble layout colors ([\#6568](https://github.com/matrix-org/matrix-react-sdk/pull/6568)). Fixes vector-im/element-web#18408. Contributed by [benneti](https://github.com/benneti). + * Don't flash "Missed call" when accepting a call ([\#6567](https://github.com/matrix-org/matrix-react-sdk/pull/6567)). Fixes vector-im/element-web#18404. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix clicking whitespaces on replies ([\#6571](https://github.com/matrix-org/matrix-react-sdk/pull/6571)). Fixes vector-im/element-web#18327. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix disabled state for voice messages + send button tooltip ([\#6562](https://github.com/matrix-org/matrix-react-sdk/pull/6562)). Fixes vector-im/element-web#18413. + * Fix voice feed being cut-off ([\#6550](https://github.com/matrix-org/matrix-react-sdk/pull/6550)). Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix sizing issues of the screen picker ([\#6498](https://github.com/matrix-org/matrix-react-sdk/pull/6498)). Fixes vector-im/element-web#18281. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Stop voice messages that are playing when starting a recording ([\#6563](https://github.com/matrix-org/matrix-react-sdk/pull/6563)). Fixes vector-im/element-web#18410. + * Properly set style attribute on shared usercontent iframe ([\#6561](https://github.com/matrix-org/matrix-react-sdk/pull/6561)). Fixes vector-im/element-web#18414. + * Null guard space inviter to prevent the app exploding ([\#6558](https://github.com/matrix-org/matrix-react-sdk/pull/6558)). + * Make the ringing sound mutable/disablable ([\#6534](https://github.com/matrix-org/matrix-react-sdk/pull/6534)). Fixes vector-im/element-web#15591. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix wrong cursor being used in PiP ([\#6551](https://github.com/matrix-org/matrix-react-sdk/pull/6551)). Fixes vector-im/element-web#18383. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Re-pin Jitsi if the widget already exists ([\#6226](https://github.com/matrix-org/matrix-react-sdk/pull/6226)). Fixes vector-im/element-web#17679. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix broken call notification regression ([\#6526](https://github.com/matrix-org/matrix-react-sdk/pull/6526)). Fixes vector-im/element-web#18335. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * createRoom, only send join rule event if we have a join rule to put in it ([\#6516](https://github.com/matrix-org/matrix-react-sdk/pull/6516)). Fixes vector-im/element-web#18301. + * Fix clicking pills inside replies ([\#6508](https://github.com/matrix-org/matrix-react-sdk/pull/6508)). Fixes vector-im/element-web#18283. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix grecaptcha regression ([\#6503](https://github.com/matrix-org/matrix-react-sdk/pull/6503)). Fixes vector-im/element-web#18284. Contributed by [Palid](https://github.com/Palid). + +Changes in [3.27.0](https://github.com/vector-im/element-desktop/releases/tag/v3.27.0) (2021-08-02) =================================================================================================== ## 🔒 SECURITY FIXES diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f7c8c8b1c5..f0ca3eb8a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ Contributing code to The React SDK ================================== -matrix-react-sdk follows the same pattern as https://github.com/matrix-org/matrix-js-sdk/blob/master/CONTRIBUTING.rst +matrix-react-sdk follows the same pattern as https://github.com/matrix-org/matrix-js-sdk/blob/master/CONTRIBUTING.md diff --git a/package.json b/package.json index 2445e3c973..985a4210c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.27.0", + "version": "3.28.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -80,7 +80,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "12.2.0", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "matrix-widget-api": "^0.1.0-beta.15", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", @@ -149,7 +149,7 @@ "@typescript-eslint/eslint-plugin": "^4.17.0", "@typescript-eslint/parser": "^4.17.0", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1", - "allchange": "github:matrix-org/allchange", + "allchange": "^1.0.0", "babel-jest": "^26.6.3", "chokidar": "^3.5.1", "concurrently": "^5.3.0", diff --git a/res/css/_animations.scss b/res/css/_animations.scss new file mode 100644 index 0000000000..4d3ad97141 --- /dev/null +++ b/res/css/_animations.scss @@ -0,0 +1,55 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/** + * React Transition Group animations are prefixed with 'mx_rtg--' so that we + * know they should not be used anywhere outside of React Transition Groups. +*/ + +.mx_rtg--fade-enter { + opacity: 0; +} +.mx_rtg--fade-enter-active { + opacity: 1; + transition: opacity 300ms ease; +} +.mx_rtg--fade-exit { + opacity: 1; +} +.mx_rtg--fade-exit-active { + opacity: 0; + transition: opacity 300ms ease; +} + + +@keyframes mx--anim-pulse { + 0% { opacity: 1; } + 50% { opacity: 0.7; } + 100% { opacity: 1; } +} + + +@media (prefers-reduced-motion) { + @keyframes mx--anim-pulse { + // Override all keyframes in reduced-motion + } + .mx_rtg--fade-enter-active { + transition: none; + } + .mx_rtg--fade-exit-active { + transition: none; + } +} diff --git a/res/css/_common.scss b/res/css/_common.scss index 6b4e109b3a..fa925eba5b 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -18,6 +18,7 @@ limitations under the License. @import "./_font-sizes.scss"; @import "./_font-weights.scss"; +@import "./_animations.scss"; $hover-transition: 0.08s cubic-bezier(.46, .03, .52, .96); // quadratic diff --git a/res/css/_components.scss b/res/css/_components.scss index af161c92c6..035caec36a 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -75,6 +75,7 @@ @import "./views/dialogs/_CreateCommunityPrototypeDialog.scss"; @import "./views/dialogs/_CreateGroupDialog.scss"; @import "./views/dialogs/_CreateRoomDialog.scss"; +@import "./views/dialogs/_CreateSpaceFromCommunityDialog.scss"; @import "./views/dialogs/_CreateSubspaceDialog.scss"; @import "./views/dialogs/_DeactivateAccountDialog.scss"; @import "./views/dialogs/_DevtoolsDialog.scss"; @@ -270,6 +271,7 @@ @import "./views/toasts/_IncomingCallToast.scss"; @import "./views/toasts/_NonUrgentEchoFailureToast.scss"; @import "./views/verification/_VerificationShowSas.scss"; +@import "./views/voip/CallView/_CallViewButtons.scss"; @import "./views/voip/_CallContainer.scss"; @import "./views/voip/_CallPreview.scss"; @import "./views/voip/_CallView.scss"; diff --git a/res/css/structures/_GroupView.scss b/res/css/structures/_GroupView.scss index 60f9ebdd08..fb660f4194 100644 --- a/res/css/structures/_GroupView.scss +++ b/res/css/structures/_GroupView.scss @@ -368,6 +368,65 @@ limitations under the License. padding: 40px 20px; } +.mx_GroupView_spaceUpgradePrompt { + padding: 16px 50px; + background-color: $header-panel-bg-color; + border-radius: 8px; + max-width: 632px; + font-size: $font-15px; + line-height: $font-24px; + margin-top: 24px; + position: relative; + + > h2 { + font-size: inherit; + font-weight: $font-semi-bold; + } + + > p, h2 { + margin: 0; + } + + &::before { + content: ""; + position: absolute; + height: $font-24px; + width: 20px; + left: 18px; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); + background-color: $secondary-fg-color; + } + + .mx_AccessibleButton_kind_link { + padding: 0; + } + + .mx_GroupView_spaceUpgradePrompt_close { + width: 16px; + height: 16px; + border-radius: 8px; + background-color: $input-darker-bg-color; + position: absolute; + top: 16px; + right: 16px; + + &::before { + content: ""; + position: absolute; + width: inherit; + height: inherit; + mask-repeat: no-repeat; + mask-position: center; + mask-size: 8px; + mask-image: url('$(res)/img/image-view/close.svg'); + background-color: $secondary-fg-color; + } + } +} + .mx_GroupView .mx_MemberInfo .mx_AutoHideScrollbar > :not(.mx_MemberInfo_avatar) { padding-left: 16px; padding-right: 16px; diff --git a/res/css/structures/_SpaceRoomDirectory.scss b/res/css/structures/_SpaceRoomDirectory.scss index cb91aa3c7d..88e6a3f494 100644 --- a/res/css/structures/_SpaceRoomDirectory.scss +++ b/res/css/structures/_SpaceRoomDirectory.scss @@ -269,7 +269,7 @@ limitations under the License. } } - &:hover { + &:hover, &:focus-within { background-color: $groupFilterPanel-bg-color; .mx_AccessibleButton { @@ -278,6 +278,10 @@ limitations under the License. } } + li.mx_SpaceRoomDirectory_roomTileWrapper { + list-style: none; + } + .mx_SpaceRoomDirectory_roomTile, .mx_SpaceRoomDirectory_subspace_children { &::before { diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index 58a4b426c2..945de01eba 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -180,6 +180,18 @@ $SpaceRoomViewInnerWidth: 428px; } } + .mx_SpaceRoomView_preview_migratedCommunity { + margin-bottom: 16px; + padding: 8px 12px; + border-radius: 8px; + border: 1px solid $input-border-color; + width: max-content; + + .mx_BaseAvatar { + margin-right: 4px; + } + } + .mx_SpaceRoomView_preview_inviter { display: flex; align-items: center; @@ -342,7 +354,7 @@ $SpaceRoomViewInnerWidth: 428px; .mx_SpaceFeedbackPrompt { padding: 7px; // 8px - 1px border - border: 1px solid $menu-border-color; + border: 1px solid rgba($primary-fg-color, .1); border-radius: 8px; width: max-content; margin: 0 0 -40px auto; // collapse its own height to not push other components down diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss index 2c3f1c705c..5cd938f1ce 100644 --- a/res/css/structures/_ToastContainer.scss +++ b/res/css/structures/_ToastContainer.scss @@ -28,7 +28,7 @@ limitations under the License. margin: 0 4px; grid-row: 2 / 4; grid-column: 1; - background-color: $toast-bg-color; + background-color: $system; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); border-radius: 8px; } @@ -37,7 +37,7 @@ limitations under the License. grid-row: 1 / 3; grid-column: 1; color: $primary-fg-color; - background-color: $toast-bg-color; + background-color: $system; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5); border-radius: 8px; overflow: hidden; diff --git a/res/css/views/context_menus/_TagTileContextMenu.scss b/res/css/views/context_menus/_TagTileContextMenu.scss index d707f4ce7c..14f5ec817e 100644 --- a/res/css/views/context_menus/_TagTileContextMenu.scss +++ b/res/css/views/context_menus/_TagTileContextMenu.scss @@ -51,6 +51,10 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/hide.svg'); } +.mx_TagTileContextMenu_createSpace::before { + mask-image: url('$(res)/img/element-icons/message/fwd.svg'); +} + .mx_TagTileContextMenu_separator { margin-top: 0; margin-bottom: 0; diff --git a/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss b/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss new file mode 100644 index 0000000000..afa722e05e --- /dev/null +++ b/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss @@ -0,0 +1,187 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_CreateSpaceFromCommunityDialog_wrapper { + .mx_Dialog { + display: flex; + flex-direction: column; + } +} + +.mx_CreateSpaceFromCommunityDialog { + width: 480px; + color: $primary-fg-color; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + min-height: 0; + + .mx_CreateSpaceFromCommunityDialog_content { + > p { + font-size: $font-15px; + line-height: $font-24px; + + &:first-of-type { + margin-top: 0; + } + + &.mx_CreateSpaceFromCommunityDialog_flairNotice { + font-size: $font-12px; + line-height: $font-15px; + } + } + + .mx_SpaceBasicSettings { + > p { + font-size: $font-12px; + line-height: $font-15px; + margin: 16px 0; + } + + .mx_Field_textarea { + margin-bottom: 0; + } + } + + .mx_JoinRuleDropdown .mx_Dropdown_menu { + width: auto !important; // override fixed width + } + + .mx_CreateSpaceFromCommunityDialog_nonPublicSpacer { + height: 63px; // balance the height of the missing room alias field to prevent modal bouncing + } + } + + .mx_CreateSpaceFromCommunityDialog_footer { + display: flex; + margin-top: 20px; + + > span { + flex-grow: 1; + font-size: $font-12px; + line-height: $font-15px; + color: $secondary-fg-color; + + .mx_ProgressBar { + height: 8px; + width: 100%; + + @mixin ProgressBarBorderRadius 8px; + } + + .mx_CreateSpaceFromCommunityDialog_progressText { + margin-top: 8px; + font-size: $font-15px; + line-height: $font-24px; + color: $primary-fg-color; + } + + > * { + vertical-align: middle; + } + } + + .mx_CreateSpaceFromCommunityDialog_error { + padding-left: 12px; + + > img { + align-self: center; + } + + .mx_CreateSpaceFromCommunityDialog_errorHeading { + font-weight: $font-semi-bold; + font-size: $font-15px; + line-height: $font-18px; + color: $notice-primary-color; + } + + .mx_CreateSpaceFromCommunityDialog_errorCaption { + margin-top: 4px; + font-size: $font-12px; + line-height: $font-15px; + color: $primary-fg-color; + } + } + + .mx_AccessibleButton { + display: inline-block; + align-self: center; + } + + .mx_AccessibleButton_kind_primary { + padding: 8px 36px; + margin-left: 24px; + } + + .mx_AccessibleButton_kind_primary_outline { + margin-left: auto; + } + + .mx_CreateSpaceFromCommunityDialog_retryButton { + margin-left: 12px; + padding-left: 24px; + position: relative; + + &::before { + content: ''; + position: absolute; + background-color: $primary-fg-color; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + mask-image: url('$(res)/img/element-icons/retry.svg'); + width: 18px; + height: 18px; + left: 0; + } + } + + .mx_AccessibleButton_kind_link { + padding: 0; + } + } +} + +.mx_CreateSpaceFromCommunityDialog_SuccessInfoDialog { + .mx_InfoDialog { + max-width: 500px; + } + + .mx_AccessibleButton_kind_link { + padding: 0; + } + + .mx_CreateSpaceFromCommunityDialog_SuccessInfoDialog_checkmark { + position: relative; + border-radius: 50%; + border: 3px solid $accent-color; + width: 68px; + height: 68px; + margin: 12px auto 32px; + + &::before { + width: inherit; + height: inherit; + content: ''; + position: absolute; + background-color: $accent-color; + mask-repeat: no-repeat; + mask-position: center; + mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg'); + mask-size: 48px; + } + } +} diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss index cae81dcc97..50cd14c4da 100644 --- a/res/css/views/elements/_Field.scss +++ b/res/css/views/elements/_Field.scss @@ -38,6 +38,7 @@ limitations under the License. .mx_Field input, .mx_Field select, .mx_Field textarea { + font-family: inherit; font-weight: normal; font-size: $font-14px; border: none; diff --git a/res/css/views/messages/_CallEvent.scss b/res/css/views/messages/_CallEvent.scss index 0c1b41ca38..2d9caf1569 100644 --- a/res/css/views/messages/_CallEvent.scss +++ b/res/css/views/messages/_CallEvent.scss @@ -14,126 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_CallEvent { +.mx_CallEvent_wrapper { display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; + width: 100%; - background-color: $dark-panel-bg-color; - border-radius: 8px; - margin: 10px auto; - width: 75%; - box-sizing: border-box; - height: 60px; - - &.mx_CallEvent_voice { - .mx_CallEvent_type_icon::before, - .mx_CallEvent_content_button_callBack span::before, - .mx_CallEvent_content_button_answer span::before { - mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); - } - } - - &.mx_CallEvent_video { - .mx_CallEvent_type_icon::before, - .mx_CallEvent_content_button_callBack span::before, - .mx_CallEvent_content_button_answer span::before { - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); - } - } - - &.mx_CallEvent_voice.mx_CallEvent_missed .mx_CallEvent_type_icon::before { - mask-image: url('$(res)/img/voip/missed-voice.svg'); - } - - &.mx_CallEvent_video.mx_CallEvent_missed .mx_CallEvent_type_icon::before { - mask-image: url('$(res)/img/voip/missed-video.svg'); - } - - .mx_CallEvent_info { + .mx_CallEvent { + position: relative; display: flex; flex-direction: row; align-items: center; - margin-left: 12px; + justify-content: space-between; - .mx_CallEvent_info_basic { - display: flex; - flex-direction: column; - margin-left: 10px; // To match mx_CallEvent - - .mx_CallEvent_sender { - font-weight: 600; - font-size: 1.5rem; - line-height: 1.8rem; - margin-bottom: 3px; - } - - .mx_CallEvent_type { - font-weight: 400; - color: $secondary-fg-color; - font-size: 1.2rem; - line-height: $font-13px; - display: flex; - align-items: center; - - .mx_CallEvent_type_icon { - height: 13px; - width: 13px; - margin-right: 5px; - - &::before { - content: ''; - position: absolute; - height: 13px; - width: 13px; - background-color: $tertiary-fg-color; - mask-repeat: no-repeat; - mask-size: contain; - } - } - } - } - } - - .mx_CallEvent_content { - display: flex; - flex-direction: row; - align-items: center; - color: $secondary-fg-color; - margin-right: 16px; - - .mx_CallEvent_content_button { - height: 24px; - padding: 0px 12px; - margin-left: 8px; - - span { - padding: 8px 0; - display: flex; - align-items: center; - - &::before { - content: ''; - display: inline-block; - background-color: $button-fg-color; - mask-position: center; - mask-repeat: no-repeat; - mask-size: 16px; - width: 16px; - height: 16px; - margin-right: 8px; - } - } - } - - .mx_CallEvent_content_button_reject span::before { - mask-image: url('$(res)/img/element-icons/call/hangup.svg'); - } - - .mx_CallEvent_content_tooltip { - margin-right: 5px; - } + background-color: $dark-panel-bg-color; + border-radius: 8px; + width: 65%; + box-sizing: border-box; + height: 60px; + margin: 4px 0; .mx_CallEvent_iconButton { display: inline-flex; @@ -158,5 +55,157 @@ limitations under the License. .mx_CallEvent_unSilence::before { mask-image: url('$(res)/img/voip/un-silence.svg'); } + + &.mx_CallEvent_voice { + .mx_CallEvent_type_icon::before, + .mx_CallEvent_content_button_callBack span::before, + .mx_CallEvent_content_button_answer span::before { + mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + } + } + + &.mx_CallEvent_video { + .mx_CallEvent_type_icon::before, + .mx_CallEvent_content_button_callBack span::before, + .mx_CallEvent_content_button_answer span::before { + mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + } + } + + &.mx_CallEvent_voice.mx_CallEvent_missed .mx_CallEvent_type_icon::before { + mask-image: url('$(res)/img/voip/missed-voice.svg'); + } + + &.mx_CallEvent_video.mx_CallEvent_missed .mx_CallEvent_type_icon::before { + mask-image: url('$(res)/img/voip/missed-video.svg'); + } + + &.mx_CallEvent_voice.mx_CallEvent_rejected .mx_CallEvent_type_icon::before, + &.mx_CallEvent_voice.mx_CallEvent_noAnswer .mx_CallEvent_type_icon::before { + mask-image: url('$(res)/img/voip/declined-voice.svg'); + } + + &.mx_CallEvent_video.mx_CallEvent_rejected .mx_CallEvent_type_icon::before, + &.mx_CallEvent_video.mx_CallEvent_noAnswer .mx_CallEvent_type_icon::before { + mask-image: url('$(res)/img/voip/declined-video.svg'); + } + + .mx_CallEvent_info { + display: flex; + flex-direction: row; + align-items: center; + margin-left: 12px; + + .mx_CallEvent_info_basic { + display: flex; + flex-direction: column; + margin-left: 10px; // To match mx_CallEvent + + .mx_CallEvent_sender { + font-weight: 600; + font-size: 1.5rem; + line-height: 1.8rem; + margin-bottom: 3px; + } + + .mx_CallEvent_type { + font-weight: 400; + color: $secondary-fg-color; + font-size: 1.2rem; + line-height: $font-13px; + display: flex; + align-items: center; + + .mx_CallEvent_type_icon { + height: 13px; + width: 13px; + margin-right: 5px; + + &::before { + content: ''; + position: absolute; + height: 13px; + width: 13px; + background-color: $tertiary-fg-color; + mask-repeat: no-repeat; + mask-size: contain; + } + } + } + } + } + + .mx_CallEvent_content { + display: flex; + flex-direction: row; + align-items: center; + color: $secondary-fg-color; + margin-right: 16px; + gap: 8px; + + .mx_CallEvent_content_button { + height: 24px; + padding: 0px 12px; + + span { + padding: 8px 0; + display: flex; + align-items: center; + + &::before { + content: ''; + display: inline-block; + background-color: $button-fg-color; + mask-position: center; + mask-repeat: no-repeat; + mask-size: 16px; + width: 16px; + height: 16px; + margin-right: 8px; + } + } + } + + .mx_CallEvent_content_button_reject span::before { + mask-image: url('$(res)/img/element-icons/call/hangup.svg'); + } + + .mx_CallEvent_content_tooltip { + margin-right: 5px; + } + } + + &.mx_CallEvent_narrow { + height: unset; + width: 290px; + flex-direction: column; + align-items: unset; + gap: 16px; + + .mx_CallEvent_iconButton { + position: absolute; + margin-right: 0; + top: 12px; + right: 12px; + height: 16px; + width: 16px; + display: flex; + } + + .mx_CallEvent_info { + align-items: unset; + margin-top: 12px; + margin-right: 12px; + + .mx_CallEvent_sender { + margin-bottom: 8px; + } + } + + .mx_CallEvent_content { + margin-left: 54px; // mx_CallEvent margin (12px) + avatar (32px) + mx_CallEvent_info_basic margin (10px) + margin-bottom: 16px; + } + } } } diff --git a/res/css/views/messages/_MImageBody.scss b/res/css/views/messages/_MImageBody.scss index a748435cd8..765c74a36d 100644 --- a/res/css/views/messages/_MImageBody.scss +++ b/res/css/views/messages/_MImageBody.scss @@ -16,6 +16,12 @@ limitations under the License. $timelineImageBorderRadius: 4px; +.mx_MImageBody_thumbnail--blurhash { + position: absolute; + left: 0; + top: 0; +} + .mx_MImageBody_thumbnail { object-fit: contain; border-radius: $timelineImageBorderRadius; @@ -23,8 +29,11 @@ $timelineImageBorderRadius: 4px; display: flex; justify-content: center; align-items: center; + height: 100%; + width: 100%; - > div > canvas { + .mx_Blurhash > canvas { + animation: mx--anim-pulse 1.75s infinite cubic-bezier(.4, 0, .6, 1); border-radius: $timelineImageBorderRadius; } } diff --git a/res/css/views/rooms/_BasicMessageComposer.scss b/res/css/views/rooms/_BasicMessageComposer.scss index e1ba468204..544a96daba 100644 --- a/res/css/views/rooms/_BasicMessageComposer.scss +++ b/res/css/views/rooms/_BasicMessageComposer.scss @@ -65,6 +65,14 @@ limitations under the License. font-size: $font-10-4px; } } + + span.mx_UserPill { + cursor: pointer; + } + + span.mx_RoomPill { + cursor: default; + } } &.mx_BasicMessageComposer_input_disabled { diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 1adfc0cde3..4dd91eb7f2 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -489,6 +489,10 @@ $hover-select-border: 4px; // https://github.com/vector-im/vector-web/issues/754 overflow-x: overlay; overflow-y: visible; + + &::-webkit-scrollbar-corner { + background: transparent; + } } } diff --git a/res/css/views/settings/tabs/_SettingsTab.scss b/res/css/views/settings/tabs/_SettingsTab.scss index 9f40372690..3290a998ab 100644 --- a/res/css/views/settings/tabs/_SettingsTab.scss +++ b/res/css/views/settings/tabs/_SettingsTab.scss @@ -50,15 +50,21 @@ limitations under the License. } .mx_SettingsTab_section { + $right-gutter: 80px; + margin-bottom: 24px; .mx_SettingsFlag { - margin-right: 80px; + margin-right: $right-gutter; margin-bottom: 10px; } + > p { + margin-right: $right-gutter; + } + &.mx_SettingsTab_subsectionText .mx_SettingsFlag { - margin-right: 0px !important; + margin-right: 0 !important; } } diff --git a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss index be0af9123b..4cdfa0b40f 100644 --- a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss @@ -22,4 +22,25 @@ limitations under the License. .mx_SettingsTab_section { margin-bottom: 30px; } + + .mx_PreferencesUserSettingsTab_CommunityMigrator { + margin-right: 200px; + + > div { + font-weight: $font-semi-bold; + font-size: $font-15px; + line-height: $font-18px; + color: $primary-fg-color; + margin: 16px 0; + + .mx_BaseAvatar { + margin-right: 12px; + vertical-align: middle; + } + + .mx_AccessibleButton { + float: right; + } + } + } } diff --git a/res/css/views/spaces/_SpaceCreateMenu.scss b/res/css/views/spaces/_SpaceCreateMenu.scss index 097b2b648e..41536bc8b1 100644 --- a/res/css/views/spaces/_SpaceCreateMenu.scss +++ b/res/css/views/spaces/_SpaceCreateMenu.scss @@ -41,7 +41,6 @@ $spacePanelWidth: 71px; > p { font-size: $font-15px; color: $secondary-fg-color; - margin: 0; } .mx_SpaceFeedbackPrompt { @@ -51,13 +50,6 @@ $spacePanelWidth: 71px; } } - // XXX remove this when spaces leaves Beta - .mx_BetaCard_betaPill { - position: absolute; - top: 24px; - right: 24px; - } - .mx_SpaceCreateMenuType { @mixin SpacePillButton; } @@ -100,6 +92,11 @@ $spacePanelWidth: 71px; width: min-content; } + .mx_AccessibleButton_kind_link { + padding: 0; + font-size: inherit; + } + .mx_AccessibleButton_disabled { cursor: not-allowed; } diff --git a/res/css/views/toasts/_IncomingCallToast.scss b/res/css/views/toasts/_IncomingCallToast.scss index 975628f948..eb80f2d5cf 100644 --- a/res/css/views/toasts/_IncomingCallToast.scss +++ b/res/css/views/toasts/_IncomingCallToast.scss @@ -30,7 +30,14 @@ limitations under the License. font-size: $font-15px; line-height: $font-18px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-top: 2px; + margin-right: 6px; + + max-width: 200px; } .mx_CallEvent_type { diff --git a/res/css/views/voip/CallView/_CallViewButtons.scss b/res/css/views/voip/CallView/_CallViewButtons.scss new file mode 100644 index 0000000000..8e343f0ff3 --- /dev/null +++ b/res/css/views/voip/CallView/_CallViewButtons.scss @@ -0,0 +1,102 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2020 - 2021 The Matrix.org Foundation C.I.C. +Copyright 2021 Šimon Brandner + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_CallViewButtons { + position: absolute; + display: flex; + justify-content: center; + bottom: 5px; + opacity: 1; + transition: opacity 0.5s; + z-index: 200; // To be above _all_ feeds + + &.mx_CallViewButtons_hidden { + opacity: 0.001; // opacity 0 can cause a re-layout + pointer-events: none; + } + + .mx_CallViewButtons_button { + cursor: pointer; + margin-left: 2px; + margin-right: 2px; + + + &::before { + content: ''; + display: inline-block; + + height: 48px; + width: 48px; + + background-repeat: no-repeat; + background-size: contain; + background-position: center; + } + + + &.mx_CallViewButtons_dialpad::before { + background-image: url('$(res)/img/voip/dialpad.svg'); + } + + &.mx_CallViewButtons_button_micOn::before { + background-image: url('$(res)/img/voip/mic-on.svg'); + } + + &.mx_CallViewButtons_button_micOff::before { + background-image: url('$(res)/img/voip/mic-off.svg'); + } + + &.mx_CallViewButtons_button_vidOn::before { + background-image: url('$(res)/img/voip/vid-on.svg'); + } + + &.mx_CallViewButtons_button_vidOff::before { + background-image: url('$(res)/img/voip/vid-off.svg'); + } + + &.mx_CallViewButtons_button_screensharingOn::before { + background-image: url('$(res)/img/voip/screensharing-on.svg'); + } + + &.mx_CallViewButtons_button_screensharingOff::before { + background-image: url('$(res)/img/voip/screensharing-off.svg'); + } + + &.mx_CallViewButtons_button_sidebarOn::before { + background-image: url('$(res)/img/voip/sidebar-on.svg'); + } + + &.mx_CallViewButtons_button_sidebarOff::before { + background-image: url('$(res)/img/voip/sidebar-off.svg'); + } + + &.mx_CallViewButtons_button_hangup::before { + background-image: url('$(res)/img/voip/hangup.svg'); + } + + &.mx_CallViewButtons_button_more::before { + background-image: url('$(res)/img/voip/more.svg'); + } + + &.mx_CallViewButtons_button_invisible { + visibility: hidden; + pointer-events: none; + position: absolute; + } + } +} diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 7752edddfa..63ca91267f 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -39,19 +39,20 @@ limitations under the License. .mx_CallView_pip { width: 320px; padding-bottom: 8px; - background-color: $toast-bg-color; + background-color: $system; box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.20); border-radius: 8px; + .mx_CallView_video_hold, .mx_CallView_voice { height: 180px; } - .mx_CallView_callControls { + .mx_CallViewButtons { bottom: 0px; } - .mx_CallView_callControls_button { + .mx_CallViewButtons_button { &::before { width: 36px; height: 36px; @@ -199,20 +200,6 @@ limitations under the License. } } -.mx_CallView_callControls { - position: absolute; - display: flex; - justify-content: center; - bottom: 5px; - opacity: 1; - transition: opacity 0.5s; - z-index: 200; // To be above _all_ feeds -} - -.mx_CallView_callControls_hidden { - opacity: 0.001; // opacity 0 can cause a re-layout - pointer-events: none; -} .mx_CallView_presenting { opacity: 1; @@ -232,94 +219,3 @@ limitations under the License. opacity: 0.001; // opacity 0 can cause a re-layout pointer-events: none; } - -.mx_CallView_callControls_button { - cursor: pointer; - margin-left: 2px; - margin-right: 2px; - - - &::before { - content: ''; - display: inline-block; - - height: 48px; - width: 48px; - - background-repeat: no-repeat; - background-size: contain; - background-position: center; - } -} - -.mx_CallView_callControls_dialpad { - &::before { - background-image: url('$(res)/img/voip/dialpad.svg'); - } -} - -.mx_CallView_callControls_button_micOn { - &::before { - background-image: url('$(res)/img/voip/mic-on.svg'); - } -} - -.mx_CallView_callControls_button_micOff { - &::before { - background-image: url('$(res)/img/voip/mic-off.svg'); - } -} - -.mx_CallView_callControls_button_vidOn { - &::before { - background-image: url('$(res)/img/voip/vid-on.svg'); - } -} - -.mx_CallView_callControls_button_vidOff { - &::before { - background-image: url('$(res)/img/voip/vid-off.svg'); - } -} - -.mx_CallView_callControls_button_screensharingOn { - &::before { - background-image: url('$(res)/img/voip/screensharing-on.svg'); - } -} - -.mx_CallView_callControls_button_screensharingOff { - &::before { - background-image: url('$(res)/img/voip/screensharing-off.svg'); - } -} - -.mx_CallView_callControls_button_sidebarOn { - &::before { - background-image: url('$(res)/img/voip/sidebar-on.svg'); - } -} - -.mx_CallView_callControls_button_sidebarOff { - &::before { - background-image: url('$(res)/img/voip/sidebar-off.svg'); - } -} - -.mx_CallView_callControls_button_hangup { - &::before { - background-image: url('$(res)/img/voip/hangup.svg'); - } -} - -.mx_CallView_callControls_button_more { - &::before { - background-image: url('$(res)/img/voip/more.svg'); - } -} - -.mx_CallView_callControls_button_invisible { - visibility: hidden; - pointer-events: none; - position: absolute; -} diff --git a/res/img/voip/declined-video.svg b/res/img/voip/declined-video.svg new file mode 100644 index 0000000000..509ffa8fd1 --- /dev/null +++ b/res/img/voip/declined-video.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/voip/declined-voice.svg b/res/img/voip/declined-voice.svg new file mode 100644 index 0000000000..78e8d90cdf --- /dev/null +++ b/res/img/voip/declined-voice.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index e4ea2bb57e..8c305b9828 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -1,18 +1,35 @@ -// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0 -$system-dark: #21262C; +// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A741 +$accent: #0DBD8B; +$alert: #FF5B55; +$links: #0086e6; +$primary-content: #ffffff; +$secondary-content: #A9B2BC; +$tertiary-content: #8E99A4; +$quaternary-content: #6F7882; +$quinary-content: #394049; +$system: #21262C; +$background: #15191E; +$panels: rgba($system, 0.9); +$panel-base: #8D97A5; // This color is not intended for use in the app +$panel-selected: rgba($panel-base, 0.3); +$panel-hover: rgba($panel-base, 0.1); +$panel-actions: rgba($panel-base, 0.2); +$space-nav: rgba($panel-base, 0.1); + +// TODO: Move userId colors here // unified palette // try to use these colors when possible -$bg-color: #15191E; +$bg-color: $background; $base-color: $bg-color; -$base-text-color: #ffffff; +$base-text-color: $primary-content; $header-panel-bg-color: #20252B; $header-panel-border-color: #000000; $header-panel-text-primary-color: #B9BEC6; $header-panel-text-secondary-color: #c8c8cd; -$text-primary-color: #ffffff; +$text-primary-color: $primary-content; $text-secondary-color: #B9BEC6; -$quaternary-fg-color: #6F7882; +$quaternary-fg-color: $quaternary-content; $search-bg-color: #181b21; $search-placeholder-color: #61708b; $room-highlight-color: #343a46; @@ -23,8 +40,8 @@ $primary-bg-color: $bg-color; $muted-fg-color: $header-panel-text-primary-color; // additional text colors -$secondary-fg-color: #A9B2BC; -$tertiary-fg-color: #8E99A4; +$secondary-fg-color: $secondary-content; +$tertiary-fg-color: $tertiary-content; // used for dialog box text $light-fg-color: $header-panel-text-secondary-color; @@ -50,7 +67,7 @@ $inverted-bg-color: $base-color; $selected-color: $room-highlight-color; // selected for hoverover & selected event tiles -$event-selected-color: $system-dark; +$event-selected-color: $system; // used for the hairline dividers in RoomView $primary-hairline-color: transparent; @@ -94,7 +111,7 @@ $lightbox-background-bg-color: #000; $lightbox-background-bg-opacity: 0.85; $settings-grey-fg-color: #a2a2a2; -$settings-profile-placeholder-bg-color: $system-dark; +$settings-profile-placeholder-bg-color: $system; $settings-profile-overlay-placeholder-fg-color: #454545; $settings-profile-button-bg-color: #e7e7e7; $settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; @@ -108,20 +125,17 @@ $roomheader-addroom-fg-color: $text-primary-color; $groupFilterPanel-button-color: $header-panel-text-primary-color; $groupheader-button-color: $header-panel-text-primary-color; $rightpanel-button-color: $header-panel-text-primary-color; -$icon-button-color: #8E99A4; +$icon-button-color: $tertiary-content; $roomtopic-color: $text-secondary-color; $eventtile-meta-color: $roomtopic-color; $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; -$quinary-content-color: #394049; -$toast-bg-color: $quinary-content-color; - // ******************** $theme-button-bg-color: #e3e8f0; -$dialpad-button-bg-color: #394049; +$dialpad-button-bg-color: $quinary-content; $roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons $roomlist-filter-active-bg-color: $bg-color; @@ -164,12 +178,12 @@ $tab-label-icon-bg-color: $text-primary-color; $tab-label-active-icon-bg-color: $text-primary-color; // Buttons -$button-primary-fg-color: #ffffff; +$button-primary-fg-color: $primary-content; $button-primary-bg-color: $accent-color; $button-secondary-bg-color: transparent; -$button-danger-fg-color: #ffffff; +$button-danger-fg-color: $primary-content; $button-danger-bg-color: $notice-primary-color; -$button-danger-disabled-fg-color: #ffffff; +$button-danger-disabled-fg-color: $primary-content; $button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color $button-link-fg-color: $accent-color; $button-link-bg-color: transparent; @@ -178,7 +192,7 @@ $button-link-bg-color: transparent; $togglesw-off-color: $room-highlight-color; $progressbar-fg-color: $accent-color; -$progressbar-bg-color: $system-dark; +$progressbar-bg-color: $system; $visual-bell-bg-color: #800; @@ -201,19 +215,19 @@ $reaction-row-button-selected-border-color: $accent-color; $kbd-border-color: #000000; $tooltip-timeline-bg-color: $groupFilterPanel-bg-color; -$tooltip-timeline-fg-color: #ffffff; +$tooltip-timeline-fg-color: $primary-content; $interactive-tooltip-bg-color: $base-color; -$interactive-tooltip-fg-color: #ffffff; +$interactive-tooltip-fg-color: $primary-content; $breadcrumb-placeholder-bg-color: #272c35; $user-tile-hover-bg-color: $header-panel-bg-color; $message-body-panel-fg-color: $secondary-fg-color; -$message-body-panel-bg-color: #394049; // "Dark Tile" +$message-body-panel-bg-color: $quinary-content; $message-body-panel-icon-fg-color: $secondary-fg-color; -$message-body-panel-icon-bg-color: $system-dark; // "System Dark" +$message-body-panel-icon-bg-color: $system; // "System Dark" $voice-record-stop-border-color: $quaternary-fg-color; $voice-record-waveform-incomplete-fg-color: $quaternary-fg-color; diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index b9429318ac..3e3412c6c1 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -1,3 +1,6 @@ +// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A741 +$system: #21262C; + // unified palette // try to use these colors when possible $bg-color: #181b21; @@ -111,9 +114,6 @@ $eventtile-meta-color: $roomtopic-color; $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; -$quinary-content-color: #394049; -$toast-bg-color: $quinary-content-color; - // ******************** $theme-button-bg-color: #e3e8f0; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 1a63c9bd07..3f722bcb30 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -13,7 +13,7 @@ $font-family: 'Nunito', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial $monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', monospace, 'Noto Color Emoji'; // Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0 -$system-light: #F4F6FA; +$system: #F4F6FA; // unified palette // try to use these colors when possible @@ -181,8 +181,7 @@ $eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #91a1c0; $header-divider-color: #91a1c0; -$toast-bg-color: $system-light; -$voipcall-plinth-color: $system-light; +$voipcall-plinth-color: $system; // ******************** @@ -334,7 +333,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color; $message-body-panel-fg-color: $secondary-fg-color; $message-body-panel-bg-color: #E3E8F0; $message-body-panel-icon-fg-color: $secondary-fg-color; -$message-body-panel-icon-bg-color: $system-light; +$message-body-panel-icon-bg-color: $system; // See non-legacy _light for variable information $voice-record-stop-symbol-color: #ff4b55; @@ -352,7 +351,7 @@ $composer-shadow-color: tranparent; // Bubble tiles $eventbubble-self-bg: #F0FBF8; -$eventbubble-others-bg: $system-light; +$eventbubble-others-bg: $system; $eventbubble-bg-hover: #FAFBFD; $eventbubble-avatar-outline: #fff; $eventbubble-reply-color: #C1C6CD; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index eff9abe5af..e64fe12d3b 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -12,23 +12,39 @@ $font-family: 'Inter', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Arial' $monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Courier', monospace, 'Noto Color Emoji'; -// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=557%3A0 -$system-light: #F4F6FA; +// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A120 +$accent: #0DBD8B; +$alert: #FF5B55; +$links: #0086e6; +$primary-content: #17191C; +$secondary-content: #737D8C; +$tertiary-content: #8D97A5; +$quaternary-content: #c1c6cd; +$quinary-content: #E3E8F0; +$system: #F4F6FA; +$background: #ffffff; +$panels: rgba($system, 0.9); +$panel-selected: rgba($tertiary-content, 0.3); +$panel-hover: rgba($tertiary-content, 0.1); +$panel-actions: rgba($tertiary-content, 0.2); +$space-nav: rgba($tertiary-content, 0.15); + +// TODO: Move userId colors here // unified palette // try to use these colors when possible -$accent-color: #0DBD8B; +$accent-color: $accent; $accent-bg-color: rgba(3, 179, 129, 0.16); $notice-primary-color: #ff4b55; $notice-primary-bg-color: rgba(255, 75, 85, 0.16); $primary-fg-color: #2e2f32; -$secondary-fg-color: #737D8C; +$secondary-fg-color: $secondary-content; $tertiary-fg-color: #8D99A5; -$quaternary-fg-color: #C1C6CD; +$quaternary-fg-color: $quaternary-content; $header-panel-bg-color: #f3f8fd; // typical text (dark-on-white in light skin) -$primary-bg-color: #ffffff; +$primary-bg-color: $background; $muted-fg-color: #61708b; // Commonly used in headings and relevant alt text // used for dialog box text @@ -38,7 +54,7 @@ $light-fg-color: #747474; $focus-bg-color: #dddddd; // button UI (white-on-green in light skin) -$accent-fg-color: #ffffff; +$accent-fg-color: $background; $accent-color-50pct: rgba($accent-color, 0.5); $accent-color-darker: #92caad; $accent-color-alt: #238CF5; @@ -82,7 +98,7 @@ $primary-hairline-color: transparent; // used for the border of input text fields $input-border-color: #e7e7e7; -$input-darker-bg-color: #e3e8f0; +$input-darker-bg-color: $quinary-content; $input-darker-fg-color: #9fa9ba; $input-lighter-bg-color: #f2f5f8; $input-lighter-fg-color: $input-darker-fg-color; @@ -90,7 +106,7 @@ $input-focused-border-color: #238cf5; $input-valid-border-color: $accent-color; $input-invalid-border-color: $warning-color; -$field-focused-label-bg-color: #ffffff; +$field-focused-label-bg-color: $background; $button-bg-color: $accent-color; $button-fg-color: white; @@ -112,8 +128,8 @@ $menu-bg-color: #fff; $menu-box-shadow-color: rgba(118, 131, 156, 0.6); $menu-selected-color: #f5f8fa; -$avatar-initial-color: #ffffff; -$avatar-bg-color: #ffffff; +$avatar-initial-color: $background; +$avatar-bg-color: $background; $h3-color: #3d3b39; @@ -141,7 +157,7 @@ $blockquote-bar-color: #ddd; $blockquote-fg-color: #777; $settings-grey-fg-color: #a2a2a2; -$settings-profile-placeholder-bg-color: $system-light; +$settings-profile-placeholder-bg-color: $system; $settings-profile-overlay-placeholder-fg-color: #2e2f32; $settings-profile-button-bg-color: #e7e7e7; $settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; @@ -163,24 +179,23 @@ $roomheader-addroom-fg-color: #5c6470; $groupFilterPanel-button-color: #91A1C0; $groupheader-button-color: #91A1C0; $rightpanel-button-color: #91A1C0; -$icon-button-color: #C1C6CD; +$icon-button-color: $quaternary-content; $roomtopic-color: #9e9e9e; $eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #91A1C0; $header-divider-color: #91A1C0; -$toast-bg-color: $system-light; -$voipcall-plinth-color: $system-light; +$voipcall-plinth-color: $system; // ******************** -$theme-button-bg-color: #e3e8f0; -$dialpad-button-bg-color: #e3e8f0; +$theme-button-bg-color: $quinary-content; +$dialpad-button-bg-color: $quinary-content; $roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons -$roomlist-filter-active-bg-color: #ffffff; +$roomlist-filter-active-bg-color: $background; $roomlist-bg-color: rgba(245, 245, 245, 0.90); $roomlist-header-color: $tertiary-fg-color; $roomsublist-divider-color: $primary-fg-color; @@ -194,7 +209,7 @@ $roomtile-selected-bg-color: #FFF; $presence-online: $accent-color; $presence-away: #d9b072; -$presence-offline: #E3E8F0; +$presence-offline: $quinary-content; // ******************** @@ -257,7 +272,7 @@ $lightbox-border-color: #ffffff; // Tabbed views $tab-label-fg-color: #45474a; -$tab-label-active-fg-color: #ffffff; +$tab-label-active-fg-color: $background; $tab-label-bg-color: transparent; $tab-label-active-bg-color: $accent-color; $tab-label-icon-bg-color: #454545; @@ -267,9 +282,9 @@ $tab-label-active-icon-bg-color: $tab-label-active-fg-color; $button-primary-fg-color: #ffffff; $button-primary-bg-color: $accent-color; $button-secondary-bg-color: $accent-fg-color; -$button-danger-fg-color: #ffffff; +$button-danger-fg-color: $background; $button-danger-bg-color: $notice-primary-color; -$button-danger-disabled-fg-color: #ffffff; +$button-danger-disabled-fg-color: $background; $button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color $button-link-fg-color: $accent-color; $button-link-bg-color: transparent; @@ -294,7 +309,7 @@ $memberstatus-placeholder-color: $muted-fg-color; $authpage-bg-color: #2e3649; $authpage-modal-bg-color: rgba(245, 245, 245, 0.90); -$authpage-body-bg-color: #ffffff; +$authpage-body-bg-color: $background; $authpage-focus-bg-color: #dddddd; $authpage-lang-color: #4e5054; $authpage-primary-color: #232f32; @@ -318,26 +333,26 @@ $kbd-border-color: $reaction-row-button-border-color; $inverted-bg-color: #27303a; $tooltip-timeline-bg-color: $inverted-bg-color; -$tooltip-timeline-fg-color: #ffffff; +$tooltip-timeline-fg-color: $background; $interactive-tooltip-bg-color: #27303a; -$interactive-tooltip-fg-color: #ffffff; +$interactive-tooltip-fg-color: $background; $breadcrumb-placeholder-bg-color: #e8eef5; $user-tile-hover-bg-color: $header-panel-bg-color; $message-body-panel-fg-color: $secondary-fg-color; -$message-body-panel-bg-color: #E3E8F0; // "Separator" +$message-body-panel-bg-color: $quinary-content; $message-body-panel-icon-fg-color: $secondary-fg-color; -$message-body-panel-icon-bg-color: $system-light; +$message-body-panel-icon-bg-color: $system; // These two don't change between themes. They are the $warning-color, but we don't // want custom themes to affect them by accident. $voice-record-stop-symbol-color: #ff4b55; $voice-record-live-circle-color: #ff4b55; -$voice-record-stop-border-color: #E3E8F0; // "Separator" +$voice-record-stop-border-color: $quinary-content; $voice-record-waveform-incomplete-fg-color: $quaternary-fg-color; $voice-record-icon-color: $tertiary-fg-color; $voice-playback-button-bg-color: $message-body-panel-icon-bg-color; @@ -354,10 +369,10 @@ $composer-shadow-color: rgba(0, 0, 0, 0.04); // Bubble tiles $eventbubble-self-bg: #F0FBF8; -$eventbubble-others-bg: $system-light; +$eventbubble-others-bg: $system; $eventbubble-bg-hover: #FAFBFD; $eventbubble-avatar-outline: $primary-bg-color; -$eventbubble-reply-color: #C1C6CD; +$eventbubble-reply-color: $quaternary-content; // ***** Mixins! ***** diff --git a/src/ActiveRoomObserver.ts b/src/ActiveRoomObserver.ts index 0be49a24ea..c7423fab8f 100644 --- a/src/ActiveRoomObserver.ts +++ b/src/ActiveRoomObserver.ts @@ -14,8 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import RoomViewStore from './stores/RoomViewStore'; import { EventSubscription } from 'fbemitter'; +import RoomViewStore from './stores/RoomViewStore'; type Listener = (isActive: boolean) => void; diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 41571666c3..f2142f56f4 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -86,7 +86,7 @@ import { randomUppercaseString, randomLowercaseString } from "matrix-js-sdk/src/ import EventEmitter from 'events'; import SdkConfig from './SdkConfig'; import { ensureDMExists, findDMForUser } from './createRoom'; -import { IPushRule, RuleId, TweakName, Tweaks } from "matrix-js-sdk/src/@types/PushRules"; +import { RuleId, TweakName, Tweaks } from "matrix-js-sdk/src/@types/PushRules"; import { PushProcessor } from 'matrix-js-sdk/src/pushprocessor'; import { WidgetLayoutStore, Container } from './stores/widgets/WidgetLayoutStore'; import { getIncomingCallToastKey } from './toasts/IncomingCallToast'; @@ -484,7 +484,7 @@ export default class CallHandler extends EventEmitter { switch (newState) { case CallState.Ringing: { const incomingCallPushRule = ( - new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) as IPushRule + new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) ); const pushRuleEnabled = incomingCallPushRule?.enabled; const tweakSetToRing = incomingCallPushRule?.actions.some((action: Tweaks) => ( diff --git a/src/KeyBindingsDefaults.ts b/src/KeyBindingsDefaults.ts index b2f70abff7..6169f431f4 100644 --- a/src/KeyBindingsDefaults.ts +++ b/src/KeyBindingsDefaults.ts @@ -161,31 +161,29 @@ const messageComposerBindings = (): KeyBinding[] => { const autocompleteBindings = (): KeyBinding[] => { return [ { - action: AutocompleteAction.CompleteOrNextSelection, + action: AutocompleteAction.ForceComplete, keyCombo: { key: Key.TAB, }, }, { - action: AutocompleteAction.CompleteOrNextSelection, + action: AutocompleteAction.ForceComplete, keyCombo: { key: Key.TAB, ctrlKey: true, }, }, { - action: AutocompleteAction.CompleteOrPrevSelection, + action: AutocompleteAction.Complete, keyCombo: { - key: Key.TAB, - shiftKey: true, + key: Key.ENTER, }, }, { - action: AutocompleteAction.CompleteOrPrevSelection, + action: AutocompleteAction.Complete, keyCombo: { - key: Key.TAB, + key: Key.ENTER, ctrlKey: true, - shiftKey: true, }, }, { diff --git a/src/KeyBindingsManager.ts b/src/KeyBindingsManager.ts index 4225d2f449..3a893e2ec8 100644 --- a/src/KeyBindingsManager.ts +++ b/src/KeyBindingsManager.ts @@ -52,13 +52,11 @@ export enum MessageComposerAction { /** Actions for text editing autocompletion */ export enum AutocompleteAction { - /** - * Select previous selection or, if the autocompletion window is not shown, open the window and select the first - * selection. - */ - CompleteOrPrevSelection = 'ApplySelection', - /** Select next selection or, if the autocompletion window is not shown, open it and select the first selection */ - CompleteOrNextSelection = 'CompleteOrNextSelection', + /** Accepts chosen autocomplete selection */ + Complete = 'Complete', + /** Accepts chosen autocomplete selection or, + * if the autocompletion window is not shown, open the window and select the first selection */ + ForceComplete = 'ForceComplete', /** Move to the previous autocomplete selection */ PrevSelection = 'PrevSelection', /** Move to the next autocomplete selection */ diff --git a/src/accessibility/RovingTabIndex.tsx b/src/accessibility/RovingTabIndex.tsx index 87f525bdfc..68e10049fd 100644 --- a/src/accessibility/RovingTabIndex.tsx +++ b/src/accessibility/RovingTabIndex.tsx @@ -150,13 +150,14 @@ const reducer = (state: IState, action: IAction) => { interface IProps { handleHomeEnd?: boolean; + handleUpDown?: boolean; children(renderProps: { onKeyDownHandler(ev: React.KeyboardEvent); }); onKeyDown?(ev: React.KeyboardEvent, state: IState); } -export const RovingTabIndexProvider: React.FC = ({ children, handleHomeEnd, onKeyDown }) => { +export const RovingTabIndexProvider: React.FC = ({ children, handleHomeEnd, handleUpDown, onKeyDown }) => { const [state, dispatch] = useReducer>(reducer, { activeRef: null, refs: [], @@ -167,21 +168,50 @@ export const RovingTabIndexProvider: React.FC = ({ children, handleHomeE const onKeyDownHandler = useCallback((ev) => { let handled = false; // Don't interfere with input default keydown behaviour - if (handleHomeEnd && ev.target.tagName !== "INPUT" && ev.target.tagName !== "TEXTAREA") { + if (ev.target.tagName !== "INPUT" && ev.target.tagName !== "TEXTAREA") { // check if we actually have any items switch (ev.key) { case Key.HOME: - handled = true; - // move focus to first item - if (context.state.refs.length > 0) { - context.state.refs[0].current.focus(); + if (handleHomeEnd) { + handled = true; + // move focus to first item + if (context.state.refs.length > 0) { + context.state.refs[0].current.focus(); + } } break; + case Key.END: - handled = true; - // move focus to last item - if (context.state.refs.length > 0) { - context.state.refs[context.state.refs.length - 1].current.focus(); + if (handleHomeEnd) { + handled = true; + // move focus to last item + if (context.state.refs.length > 0) { + context.state.refs[context.state.refs.length - 1].current.focus(); + } + } + break; + + case Key.ARROW_UP: + if (handleUpDown) { + handled = true; + if (context.state.refs.length > 0) { + const idx = context.state.refs.indexOf(context.state.activeRef); + if (idx > 0) { + context.state.refs[idx - 1].current.focus(); + } + } + } + break; + + case Key.ARROW_DOWN: + if (handleUpDown) { + handled = true; + if (context.state.refs.length > 0) { + const idx = context.state.refs.indexOf(context.state.activeRef); + if (idx < context.state.refs.length - 1) { + context.state.refs[idx + 1].current.focus(); + } + } } break; } @@ -193,7 +223,7 @@ export const RovingTabIndexProvider: React.FC = ({ children, handleHomeE } else if (onKeyDown) { return onKeyDown(ev, context.state); } - }, [context.state, onKeyDown, handleHomeEnd]); + }, [context.state, onKeyDown, handleHomeEnd, handleUpDown]); return { children({ onKeyDownHandler }) } diff --git a/src/autocomplete/AutocompleteProvider.tsx b/src/autocomplete/AutocompleteProvider.tsx index 51ab2e2cf7..2d82a9f591 100644 --- a/src/autocomplete/AutocompleteProvider.tsx +++ b/src/autocomplete/AutocompleteProvider.tsx @@ -27,11 +27,11 @@ export interface ICommand { }; } -export default class AutocompleteProvider { +export default abstract class AutocompleteProvider { commandRegex: RegExp; forcedCommandRegex: RegExp; - constructor(commandRegex?: RegExp, forcedCommandRegex?: RegExp) { + protected constructor(commandRegex?: RegExp, forcedCommandRegex?: RegExp) { if (commandRegex) { if (!commandRegex.global) { throw new Error('commandRegex must have global flag set'); @@ -93,23 +93,16 @@ export default class AutocompleteProvider { }; } - async getCompletions( + abstract getCompletions( query: string, selection: ISelectionRange, - force = false, - limit = -1, - ): Promise { - return []; - } + force: boolean, + limit: number, + ): Promise; - getName(): string { - return 'Default Provider'; - } + abstract getName(): string; - renderCompletions(completions: React.ReactNode[]): React.ReactNode | null { - console.error('stub; should be implemented in subclasses'); - return null; - } + abstract renderCompletions(completions: React.ReactNode[]): React.ReactNode | null; // Whether we should provide completions even if triggered forcefully, without a sigil. shouldForceComplete(): boolean { diff --git a/src/autocomplete/CommandProvider.tsx b/src/autocomplete/CommandProvider.tsx index e9a7742dee..d56adc026c 100644 --- a/src/autocomplete/CommandProvider.tsx +++ b/src/autocomplete/CommandProvider.tsx @@ -96,7 +96,7 @@ export default class CommandProvider extends AutocompleteProvider { return (
{ completions } diff --git a/src/autocomplete/CommunityProvider.tsx b/src/autocomplete/CommunityProvider.tsx index de99675b4b..4b42f4c64e 100644 --- a/src/autocomplete/CommunityProvider.tsx +++ b/src/autocomplete/CommunityProvider.tsx @@ -116,7 +116,7 @@ export default class CommunityProvider extends AutocompleteProvider { return (
{ completions } diff --git a/src/autocomplete/DuckDuckGoProvider.tsx b/src/autocomplete/DuckDuckGoProvider.tsx index 08750493d3..c41a91b97f 100644 --- a/src/autocomplete/DuckDuckGoProvider.tsx +++ b/src/autocomplete/DuckDuckGoProvider.tsx @@ -105,7 +105,7 @@ export default class DuckDuckGoProvider extends AutocompleteProvider { return (
{ completions } diff --git a/src/autocomplete/EmojiProvider.tsx b/src/autocomplete/EmojiProvider.tsx index d3175edbdb..0aae8c6372 100644 --- a/src/autocomplete/EmojiProvider.tsx +++ b/src/autocomplete/EmojiProvider.tsx @@ -139,7 +139,7 @@ export default class EmojiProvider extends AutocompleteProvider { return (
{ completions } diff --git a/src/autocomplete/NotifProvider.tsx b/src/autocomplete/NotifProvider.tsx index 31b834ccfe..aa4f1174dc 100644 --- a/src/autocomplete/NotifProvider.tsx +++ b/src/autocomplete/NotifProvider.tsx @@ -70,7 +70,7 @@ export default class NotifProvider extends AutocompleteProvider { return (
{ completions } diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx index 37ddf2c387..00bfe6be5c 100644 --- a/src/autocomplete/RoomProvider.tsx +++ b/src/autocomplete/RoomProvider.tsx @@ -134,7 +134,7 @@ export default class RoomProvider extends AutocompleteProvider { return (
{ completions } diff --git a/src/autocomplete/UserProvider.tsx b/src/autocomplete/UserProvider.tsx index 182743abb3..48854657de 100644 --- a/src/autocomplete/UserProvider.tsx +++ b/src/autocomplete/UserProvider.tsx @@ -181,7 +181,7 @@ export default class UserProvider extends AutocompleteProvider { return (
{ completions } diff --git a/src/components/structures/AutoHideScrollbar.tsx b/src/components/structures/AutoHideScrollbar.tsx index 184d883dda..a60df45770 100644 --- a/src/components/structures/AutoHideScrollbar.tsx +++ b/src/components/structures/AutoHideScrollbar.tsx @@ -61,7 +61,9 @@ export default class AutoHideScrollbar extends React.Component { style={style} className={["mx_AutoHideScrollbar", className].join(" ")} onWheel={onWheel} - tabIndex={tabIndex} + // Firefox sometimes makes this element focusable due to + // overflow:scroll;, so force it out of tab order by default. + tabIndex={tabIndex ?? -1} > { children }
); diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 99fa94e62b..f4f1d50d63 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -41,6 +41,9 @@ import RightPanelStore from "../../stores/RightPanelStore"; import AutoHideScrollbar from "./AutoHideScrollbar"; import { mediaFromMxc } from "../../customisations/Media"; import { replaceableComponent } from "../../utils/replaceableComponent"; +import { createSpaceFromCommunity } from "../../utils/space"; +import { Action } from "../../dispatcher/actions"; +import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; const LONG_DESC_PLACEHOLDER = _td( `

HTML for your community's page

@@ -399,6 +402,8 @@ class FeaturedUser extends React.Component { const GROUP_JOINPOLICY_OPEN = "open"; const GROUP_JOINPOLICY_INVITE = "invite"; +const UPGRADE_NOTICE_LS_KEY = "mx_hide_community_upgrade_notice"; + @replaceableComponent("structures.GroupView") export default class GroupView extends React.Component { static propTypes = { @@ -422,6 +427,7 @@ export default class GroupView extends React.Component { publicityBusy: false, inviterProfile: null, showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup, + showUpgradeNotice: !localStorage.getItem(UPGRADE_NOTICE_LS_KEY), }; componentDidMount() { @@ -807,6 +813,22 @@ export default class GroupView extends React.Component { showGroupAddRoomDialog(this.props.groupId); }; + _dismissUpgradeNotice = () => { + localStorage.setItem(UPGRADE_NOTICE_LS_KEY, "true"); + this.setState({ showUpgradeNotice: false }); + } + + _onCreateSpaceClick = () => { + createSpaceFromCommunity(this._matrixClient, this.props.groupId); + }; + + _onAdminsLinkClick = () => { + dis.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.GroupMemberList, + }); + }; + _getGroupSection() { const groupSettingsSectionClasses = classnames({ "mx_GroupView_group": this.state.editing, @@ -843,10 +865,46 @@ export default class GroupView extends React.Component { }, ) }
:
; + + let communitiesUpgradeNotice; + if (this.state.showUpgradeNotice) { + let text; + if (this.state.isUserPrivileged) { + text = _t("You can create a Space from this community here.", {}, { + a: sub => + { sub } + , + }); + } else { + text = _t("Ask the admins of this community to make it into a Space " + + "and keep a look out for the invite.", {}, { + a: sub => + { sub } + , + }); + } + + communitiesUpgradeNotice =
+

{ _t("Communities can now be made into Spaces") }

+

+ { _t("Spaces are a new way to make a community, with new features coming.") } +   + { text } +   + { _t("Communities won't receive further updates.") } +

+ +
; + } + return
{ header } { hostingSignup } { changeDelayWarning } + { communitiesUpgradeNotice } { this._getJoinableNode() } { this._getLongDescriptionNode() } { this._getRoomsNode() } diff --git a/src/components/structures/InteractiveAuth.js b/src/components/structures/InteractiveAuth.js deleted file mode 100644 index 61ae1882df..0000000000 --- a/src/components/structures/InteractiveAuth.js +++ /dev/null @@ -1,300 +0,0 @@ -/* -Copyright 2017 Vector Creations Ltd. -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { InteractiveAuth } from "matrix-js-sdk/src/interactive-auth"; -import React, { createRef } from 'react'; -import PropTypes from 'prop-types'; - -import getEntryComponentForLoginType from '../views/auth/InteractiveAuthEntryComponents'; - -import * as sdk from '../../index'; -import { replaceableComponent } from "../../utils/replaceableComponent"; - -export const ERROR_USER_CANCELLED = new Error("User cancelled auth session"); - -@replaceableComponent("structures.InteractiveAuthComponent") -export default class InteractiveAuthComponent extends React.Component { - static propTypes = { - // matrix client to use for UI auth requests - matrixClient: PropTypes.object.isRequired, - - // response from initial request. If not supplied, will do a request on - // mount. - authData: PropTypes.shape({ - flows: PropTypes.array, - params: PropTypes.object, - session: PropTypes.string, - }), - - // callback - makeRequest: PropTypes.func.isRequired, - - // callback called when the auth process has finished, - // successfully or unsuccessfully. - // @param {bool} status True if the operation requiring - // auth was completed sucessfully, false if canceled. - // @param {object} result The result of the authenticated call - // if successful, otherwise the error object. - // @param {object} extra Additional information about the UI Auth - // process: - // * emailSid {string} If email auth was performed, the sid of - // the auth session. - // * clientSecret {string} The client secret used in auth - // sessions with the identity server. - onAuthFinished: PropTypes.func.isRequired, - - // Inputs provided by the user to the auth process - // and used by various stages. As passed to js-sdk - // interactive-auth - inputs: PropTypes.object, - - // As js-sdk interactive-auth - requestEmailToken: PropTypes.func, - sessionId: PropTypes.string, - clientSecret: PropTypes.string, - emailSid: PropTypes.string, - - // If true, poll to see if the auth flow has been completed - // out-of-band - poll: PropTypes.bool, - - // If true, components will be told that the 'Continue' button - // is managed by some other party and should not be managed by - // the component itself. - continueIsManaged: PropTypes.bool, - - // Called when the stage changes, or the stage's phase changes. First - // argument is the stage, second is the phase. Some stages do not have - // phases and will be counted as 0 (numeric). - onStagePhaseChange: PropTypes.func, - - // continueText and continueKind are passed straight through to the AuthEntryComponent. - continueText: PropTypes.string, - continueKind: PropTypes.string, - }; - - constructor(props) { - super(props); - - this.state = { - authStage: null, - busy: false, - errorText: null, - stageErrorText: null, - submitButtonEnabled: false, - }; - - this._unmounted = false; - this._authLogic = new InteractiveAuth({ - authData: this.props.authData, - doRequest: this._requestCallback, - busyChanged: this._onBusyChanged, - inputs: this.props.inputs, - stateUpdated: this._authStateUpdated, - matrixClient: this.props.matrixClient, - sessionId: this.props.sessionId, - clientSecret: this.props.clientSecret, - emailSid: this.props.emailSid, - requestEmailToken: this._requestEmailToken, - }); - - this._intervalId = null; - if (this.props.poll) { - this._intervalId = setInterval(() => { - this._authLogic.poll(); - }, 2000); - } - - this._stageComponent = createRef(); - } - - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount() { // eslint-disable-line camelcase - this._authLogic.attemptAuth().then((result) => { - const extra = { - emailSid: this._authLogic.getEmailSid(), - clientSecret: this._authLogic.getClientSecret(), - }; - this.props.onAuthFinished(true, result, extra); - }).catch((error) => { - this.props.onAuthFinished(false, error); - console.error("Error during user-interactive auth:", error); - if (this._unmounted) { - return; - } - - const msg = error.message || error.toString(); - this.setState({ - errorText: msg, - }); - }); - } - - componentWillUnmount() { - this._unmounted = true; - - if (this._intervalId !== null) { - clearInterval(this._intervalId); - } - } - - _requestEmailToken = async (...args) => { - this.setState({ - busy: true, - }); - try { - return await this.props.requestEmailToken(...args); - } finally { - this.setState({ - busy: false, - }); - } - }; - - tryContinue = () => { - if (this._stageComponent.current && this._stageComponent.current.tryContinue) { - this._stageComponent.current.tryContinue(); - } - }; - - _authStateUpdated = (stageType, stageState) => { - const oldStage = this.state.authStage; - this.setState({ - busy: false, - authStage: stageType, - stageState: stageState, - errorText: stageState.error, - }, () => { - if (oldStage !== stageType) { - this._setFocus(); - } else if ( - !stageState.error && this._stageComponent.current && - this._stageComponent.current.attemptFailed - ) { - this._stageComponent.current.attemptFailed(); - } - }); - }; - - _requestCallback = (auth) => { - // This wrapper just exists because the js-sdk passes a second - // 'busy' param for backwards compat. This throws the tests off - // so discard it here. - return this.props.makeRequest(auth); - }; - - _onBusyChanged = (busy) => { - // if we've started doing stuff, reset the error messages - if (busy) { - this.setState({ - busy: true, - errorText: null, - stageErrorText: null, - }); - } - // The JS SDK eagerly reports itself as "not busy" right after any - // immediate work has completed, but that's not really what we want at - // the UI layer, so we ignore this signal and show a spinner until - // there's a new screen to show the user. This is implemented by setting - // `busy: false` in `_authStateUpdated`. - // See also https://github.com/vector-im/element-web/issues/12546 - }; - - _setFocus() { - if (this._stageComponent.current && this._stageComponent.current.focus) { - this._stageComponent.current.focus(); - } - } - - _submitAuthDict = authData => { - this._authLogic.submitAuthDict(authData); - }; - - _onPhaseChange = newPhase => { - if (this.props.onStagePhaseChange) { - this.props.onStagePhaseChange(this.state.authStage, newPhase || 0); - } - }; - - _onStageCancel = () => { - this.props.onAuthFinished(false, ERROR_USER_CANCELLED); - }; - - _renderCurrentStage() { - const stage = this.state.authStage; - if (!stage) { - if (this.state.busy) { - const Loader = sdk.getComponent("elements.Spinner"); - return ; - } else { - return null; - } - } - - const StageComponent = getEntryComponentForLoginType(stage); - return ( - - ); - } - - _onAuthStageFailed = e => { - this.props.onAuthFinished(false, e); - }; - - _setEmailSid = sid => { - this._authLogic.setEmailSid(sid); - }; - - render() { - let error = null; - if (this.state.errorText) { - error = ( -
- { this.state.errorText } -
- ); - } - - return ( -
-
- { this._renderCurrentStage() } - { error } -
-
- ); - } -} diff --git a/src/components/structures/InteractiveAuth.tsx b/src/components/structures/InteractiveAuth.tsx new file mode 100644 index 0000000000..869cd29cba --- /dev/null +++ b/src/components/structures/InteractiveAuth.tsx @@ -0,0 +1,300 @@ +/* +Copyright 2017 - 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { + AuthType, + IAuthData, + IAuthDict, + IInputs, + InteractiveAuth, + IStageStatus, +} from "matrix-js-sdk/src/interactive-auth"; +import { MatrixClient } from "matrix-js-sdk/src/client"; +import React, { createRef } from 'react'; + +import getEntryComponentForLoginType, { IStageComponent } from '../views/auth/InteractiveAuthEntryComponents'; +import Spinner from "../views/elements/Spinner"; +import { replaceableComponent } from "../../utils/replaceableComponent"; + +export const ERROR_USER_CANCELLED = new Error("User cancelled auth session"); + +interface IProps { + // matrix client to use for UI auth requests + matrixClient: MatrixClient; + // response from initial request. If not supplied, will do a request on mount. + authData?: IAuthData; + // Inputs provided by the user to the auth process + // and used by various stages. As passed to js-sdk + // interactive-auth + inputs?: IInputs; + sessionId?: string; + clientSecret?: string; + emailSid?: string; + // If true, poll to see if the auth flow has been completed out-of-band + poll?: boolean; + // If true, components will be told that the 'Continue' button + // is managed by some other party and should not be managed by + // the component itself. + continueIsManaged?: boolean; + // continueText and continueKind are passed straight through to the AuthEntryComponent. + continueText?: string; + continueKind?: string; + // callback + makeRequest(auth: IAuthData): Promise; + // callback called when the auth process has finished, + // successfully or unsuccessfully. + // @param {boolean} status True if the operation requiring + // auth was completed successfully, false if canceled. + // @param {object} result The result of the authenticated call + // if successful, otherwise the error object. + // @param {object} extra Additional information about the UI Auth + // process: + // * emailSid {string} If email auth was performed, the sid of + // the auth session. + // * clientSecret {string} The client secret used in auth + // sessions with the ID server. + onAuthFinished( + status: boolean, + result: IAuthData | Error, + extra?: { emailSid?: string, clientSecret?: string }, + ): void; + // As js-sdk interactive-auth + requestEmailToken?(email: string, secret: string, attempt: number, session: string): Promise<{ sid: string }>; + // Called when the stage changes, or the stage's phase changes. First + // argument is the stage, second is the phase. Some stages do not have + // phases and will be counted as 0 (numeric). + onStagePhaseChange?(stage: string, phase: string | number): void; +} + +interface IState { + authStage?: AuthType; + stageState?: IStageStatus; + busy: boolean; + errorText?: string; + stageErrorText?: string; + submitButtonEnabled: boolean; +} + +@replaceableComponent("structures.InteractiveAuthComponent") +export default class InteractiveAuthComponent extends React.Component { + private readonly authLogic: InteractiveAuth; + private readonly intervalId: number = null; + private readonly stageComponent = createRef(); + + private unmounted = false; + + constructor(props) { + super(props); + + this.state = { + authStage: null, + busy: false, + errorText: null, + stageErrorText: null, + submitButtonEnabled: false, + }; + + this.authLogic = new InteractiveAuth({ + authData: this.props.authData, + doRequest: this.requestCallback, + busyChanged: this.onBusyChanged, + inputs: this.props.inputs, + stateUpdated: this.authStateUpdated, + matrixClient: this.props.matrixClient, + sessionId: this.props.sessionId, + clientSecret: this.props.clientSecret, + emailSid: this.props.emailSid, + requestEmailToken: this.requestEmailToken, + }); + + if (this.props.poll) { + this.intervalId = setInterval(() => { + this.authLogic.poll(); + }, 2000); + } + } + + // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs + UNSAFE_componentWillMount() { // eslint-disable-line @typescript-eslint/naming-convention, camelcase + this.authLogic.attemptAuth().then((result) => { + const extra = { + emailSid: this.authLogic.getEmailSid(), + clientSecret: this.authLogic.getClientSecret(), + }; + this.props.onAuthFinished(true, result, extra); + }).catch((error) => { + this.props.onAuthFinished(false, error); + console.error("Error during user-interactive auth:", error); + if (this.unmounted) { + return; + } + + const msg = error.message || error.toString(); + this.setState({ + errorText: msg, + }); + }); + } + + componentWillUnmount() { + this.unmounted = true; + + if (this.intervalId !== null) { + clearInterval(this.intervalId); + } + } + + private requestEmailToken = async ( + email: string, + secret: string, + attempt: number, + session: string, + ): Promise<{sid: string}> => { + this.setState({ + busy: true, + }); + try { + return await this.props.requestEmailToken(email, secret, attempt, session); + } finally { + this.setState({ + busy: false, + }); + } + }; + + private tryContinue = (): void => { + this.stageComponent.current?.tryContinue?.(); + }; + + private authStateUpdated = (stageType: AuthType, stageState: IStageStatus): void => { + const oldStage = this.state.authStage; + this.setState({ + busy: false, + authStage: stageType, + stageState: stageState, + errorText: stageState.error, + }, () => { + if (oldStage !== stageType) { + this.setFocus(); + } else if (!stageState.error) { + this.stageComponent.current?.attemptFailed?.(); + } + }); + }; + + private requestCallback = (auth: IAuthData, background: boolean): Promise => { + // This wrapper just exists because the js-sdk passes a second + // 'busy' param for backwards compat. This throws the tests off + // so discard it here. + return this.props.makeRequest(auth); + }; + + private onBusyChanged = (busy: boolean): void => { + // if we've started doing stuff, reset the error messages + if (busy) { + this.setState({ + busy: true, + errorText: null, + stageErrorText: null, + }); + } + // The JS SDK eagerly reports itself as "not busy" right after any + // immediate work has completed, but that's not really what we want at + // the UI layer, so we ignore this signal and show a spinner until + // there's a new screen to show the user. This is implemented by setting + // `busy: false` in `authStateUpdated`. + // See also https://github.com/vector-im/element-web/issues/12546 + }; + + private setFocus(): void { + this.stageComponent.current?.focus?.(); + } + + private submitAuthDict = (authData: IAuthDict): void => { + this.authLogic.submitAuthDict(authData); + }; + + private onPhaseChange = (newPhase: number): void => { + this.props.onStagePhaseChange?.(this.state.authStage, newPhase || 0); + }; + + private onStageCancel = (): void => { + this.props.onAuthFinished(false, ERROR_USER_CANCELLED); + }; + + private renderCurrentStage(): JSX.Element { + const stage = this.state.authStage; + if (!stage) { + if (this.state.busy) { + return ; + } else { + return null; + } + } + + const StageComponent = getEntryComponentForLoginType(stage); + return ( + + ); + } + + private onAuthStageFailed = (e: Error): void => { + this.props.onAuthFinished(false, e); + }; + + private setEmailSid = (sid: string): void => { + this.authLogic.setEmailSid(sid); + }; + + render() { + let error = null; + if (this.state.errorText) { + error = ( +
+ { this.state.errorText } +
+ ); + } + + return ( +
+
+ { this.renderCurrentStage() } + { error } +
+
+ ); + } +} diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 3bd2c68c6c..ff5d15d44d 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -392,9 +392,6 @@ export default class LeftPanel extends React.Component { diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index d496c4ad21..2392a8b28d 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -529,24 +529,24 @@ class LoggedInView extends React.Component { } const isModifier = ev.key === Key.ALT || ev.key === Key.CONTROL || ev.key === Key.META || ev.key === Key.SHIFT; - if (!isModifier && !ev.altKey && !ev.ctrlKey && !ev.metaKey) { + if (!isModifier && !ev.ctrlKey && !ev.metaKey) { // The above condition is crafted to _allow_ characters with Shift // already pressed (but not the Shift key down itself). - const isClickShortcut = ev.target !== document.body && (ev.key === Key.SPACE || ev.key === Key.ENTER); - // Do not capture the context menu key to improve keyboard accessibility - if (ev.key === Key.CONTEXT_MENU) { - return; - } + // We explicitly allow alt to be held due to it being a common accent modifier. + // XXX: Forwarding Dead keys in this way does not work as intended but better to at least + // move focus to the composer so the user can re-type the dead key correctly. + const isPrintable = ev.key.length === 1 || ev.key === "Dead"; - if (!isClickShortcut && ev.key !== Key.TAB && !canElementReceiveInput(ev.target)) { + // If the user is entering a printable character outside of an input field + // redirect it to the composer for them. + if (!isClickShortcut && isPrintable && !canElementReceiveInput(ev.target)) { // synchronous dispatch so we focus before key generates input dis.fire(Action.FocusSendMessageComposer, true); ev.stopPropagation(); - // we should *not* preventDefault() here as - // that would prevent typing in the now-focussed composer + // we should *not* preventDefault() here as that would prevent typing in the now-focused composer } } }; diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index d8cc9593f0..27b70c6841 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactNode, useMemo, useState } from "react"; +import React, { ReactNode, KeyboardEvent, useMemo, useState, KeyboardEventHandler } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import { EventType, RoomType } from "matrix-js-sdk/src/@types/event"; import { ISpaceSummaryRoom, ISpaceSummaryEvent } from "matrix-js-sdk/src/@types/spaces"; @@ -46,6 +46,8 @@ import { getDisplayAliasForAliasSet } from "../../Rooms"; import { useDispatcher } from "../../hooks/useDispatcher"; import defaultDispatcher from "../../dispatcher/dispatcher"; import { Action } from "../../dispatcher/actions"; +import { Key } from "../../Keyboard"; +import { IState, RovingTabIndexProvider, useRovingTabIndex } from "../../accessibility/RovingTabIndex"; interface IHierarchyProps { space: Room; @@ -80,6 +82,7 @@ const Tile: React.FC = ({ || (room.room_type === RoomType.Space ? _t("Unnamed Space") : _t("Unnamed Room")); const [showChildren, toggleShowChildren] = useStateToggle(true); + const [onFocus, isActive, ref] = useRovingTabIndex(); const onPreviewClick = (ev: ButtonEvent) => { ev.preventDefault(); @@ -94,11 +97,21 @@ const Tile: React.FC = ({ let button; if (joinedRoom) { - button = + button = { _t("View") } ; } else if (onJoinClick) { - button = + button = { _t("Join") } ; } @@ -106,13 +119,13 @@ const Tile: React.FC = ({ let checkbox; if (onToggleClick) { if (hasPermissions) { - checkbox = ; + checkbox = ; } else { checkbox = { ev.stopPropagation(); }} > - + ; } } @@ -172,8 +185,9 @@ const Tile: React.FC = ({
; - let childToggle; - let childSection; + let childToggle: JSX.Element; + let childSection: JSX.Element; + let onKeyDown: KeyboardEventHandler; if (children) { // the chevron is purposefully a div rather than a button as it should be ignored for a11y childToggle =
= ({ toggleShowChildren(); }} />; + if (showChildren) { - childSection =
+ const onChildrenKeyDown = (e) => { + if (e.key === Key.ARROW_LEFT) { + e.preventDefault(); + e.stopPropagation(); + ref.current?.focus(); + } + }; + + childSection =
{ children }
; } + + onKeyDown = (e) => { + let handled = false; + + switch (e.key) { + case Key.ARROW_LEFT: + if (showChildren) { + handled = true; + toggleShowChildren(); + } + break; + + case Key.ARROW_RIGHT: + handled = true; + if (showChildren) { + const childSection = ref.current?.nextElementSibling; + childSection?.querySelector(".mx_SpaceRoomDirectory_roomTile")?.focus(); + } else { + toggleShowChildren(); + } + break; + } + + if (handled) { + e.preventDefault(); + e.stopPropagation(); + } + }; } - return <> + return
  • { content } { childToggle } { childSection } - ; +
  • ; }; export const showRoom = (room: ISpaceSummaryRoom, viaServers?: string[], autoJoin = false) => { @@ -414,176 +477,196 @@ export const SpaceHierarchy: React.FC = ({ return

    { _t("Your server does not support showing space hierarchies.") }

    ; } - let content; - if (roomsMap) { - const numRooms = Array.from(roomsMap.values()).filter(r => !r.room_type).length; - const numSpaces = roomsMap.size - numRooms - 1; // -1 at the end to exclude the space we are looking at - - let countsStr; - if (numSpaces > 1) { - countsStr = _t("%(count)s rooms and %(numSpaces)s spaces", { count: numRooms, numSpaces }); - } else if (numSpaces > 0) { - countsStr = _t("%(count)s rooms and 1 space", { count: numRooms, numSpaces }); - } else { - countsStr = _t("%(count)s rooms", { count: numRooms, numSpaces }); + const onKeyDown = (ev: KeyboardEvent, state: IState) => { + if (ev.key === Key.ARROW_DOWN && ev.currentTarget.classList.contains("mx_SpaceRoomDirectory_search")) { + state.refs[0]?.current?.focus(); } - - let manageButtons; - if (space.getMyMembership() === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) { - const selectedRelations = Array.from(selected.keys()).flatMap(parentId => { - return [...selected.get(parentId).values()].map(childId => [parentId, childId]) as [string, string][]; - }); - - const selectionAllSuggested = selectedRelations.every(([parentId, childId]) => { - return parentChildMap.get(parentId)?.get(childId)?.content.suggested; - }); - - const disabled = !selectedRelations.length || removing || saving; - - let Button: React.ComponentType> = AccessibleButton; - let props = {}; - if (!selectedRelations.length) { - Button = AccessibleTooltipButton; - props = { - tooltip: _t("Select a room below first"), - yOffset: -40, - }; - } - - manageButtons = <> - - - ; - } - - let results; - if (roomsMap.size) { - const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId()); - - results = <> - { - setError(""); - if (!selected.has(parentId)) { - setSelected(new Map(selected.set(parentId, new Set([childId])))); - return; - } - - const parentSet = selected.get(parentId); - if (!parentSet.has(childId)) { - setSelected(new Map(selected.set(parentId, new Set([...parentSet, childId])))); - return; - } - - parentSet.delete(childId); - setSelected(new Map(selected.set(parentId, new Set(parentSet)))); - } : undefined} - onViewRoomClick={(roomId, autoJoin) => { - showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), autoJoin); - }} - /> - { children &&
    } - ; - } else { - results =
    -

    { _t("No results found") }

    -
    { _t("You may want to try a different search or check for typos.") }
    -
    ; - } - - content = <> -
    - { countsStr } - - { additionalButtons } - { manageButtons } - -
    - { error &&
    - { error } -
    } - - { results } - { children } - - ; - } else { - content = ; - } + }; // TODO loading state/error state - return <> - + return + { ({ onKeyDownHandler }) => { + let content; + if (roomsMap) { + const numRooms = Array.from(roomsMap.values()).filter(r => !r.room_type).length; + const numSpaces = roomsMap.size - numRooms - 1; // -1 at the end to exclude the space we are looking at - { content } - ; + let countsStr; + if (numSpaces > 1) { + countsStr = _t("%(count)s rooms and %(numSpaces)s spaces", { count: numRooms, numSpaces }); + } else if (numSpaces > 0) { + countsStr = _t("%(count)s rooms and 1 space", { count: numRooms, numSpaces }); + } else { + countsStr = _t("%(count)s rooms", { count: numRooms, numSpaces }); + } + + let manageButtons; + if (space.getMyMembership() === "join" && + space.currentState.maySendStateEvent(EventType.SpaceChild, userId) + ) { + const selectedRelations = Array.from(selected.keys()).flatMap(parentId => { + return [ + ...selected.get(parentId).values(), + ].map(childId => [parentId, childId]) as [string, string][]; + }); + + const selectionAllSuggested = selectedRelations.every(([parentId, childId]) => { + return parentChildMap.get(parentId)?.get(childId)?.content.suggested; + }); + + const disabled = !selectedRelations.length || removing || saving; + + let Button: React.ComponentType> = AccessibleButton; + let props = {}; + if (!selectedRelations.length) { + Button = AccessibleTooltipButton; + props = { + tooltip: _t("Select a room below first"), + yOffset: -40, + }; + } + + manageButtons = <> + + + ; + } + + let results; + if (roomsMap.size) { + const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId()); + + results = <> + { + setError(""); + if (!selected.has(parentId)) { + setSelected(new Map(selected.set(parentId, new Set([childId])))); + return; + } + + const parentSet = selected.get(parentId); + if (!parentSet.has(childId)) { + setSelected(new Map(selected.set(parentId, new Set([...parentSet, childId])))); + return; + } + + parentSet.delete(childId); + setSelected(new Map(selected.set(parentId, new Set(parentSet)))); + } : undefined} + onViewRoomClick={(roomId, autoJoin) => { + showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), autoJoin); + }} + /> + { children &&
    } + ; + } else { + results =
    +

    { _t("No results found") }

    +
    { _t("You may want to try a different search or check for typos.") }
    +
    ; + } + + content = <> +
    + { countsStr } + + { additionalButtons } + { manageButtons } + +
    + { error &&
    + { error } +
    } + + { results } + { children } + + ; + } else { + content = ; + } + + return <> + + + { content } + ; + } } +
    ; }; interface IProps { diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 6f63ea090c..7887e9b744 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -74,6 +74,10 @@ import { BetaPill } from "../views/beta/BetaCard"; import { UserTab } from "../views/dialogs/UserSettingsDialog"; import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership"; import { SpaceFeedbackPrompt } from "../views/spaces/SpaceCreateMenu"; +import { CreateEventField, IGroupSummary } from "../views/dialogs/CreateSpaceFromCommunityDialog"; +import { useAsyncMemo } from "../../hooks/useAsyncMemo"; +import Spinner from "../views/elements/Spinner"; +import GroupAvatar from "../views/avatars/GroupAvatar"; interface IProps { space: Room; @@ -158,7 +162,33 @@ const onBetaClick = () => { }); }; -const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => { +// XXX: temporary community migration component +const GroupTile = ({ groupId }: { groupId: string }) => { + const cli = useContext(MatrixClientContext); + const groupSummary = useAsyncMemo(() => cli.getGroupSummary(groupId), [cli, groupId]); + + if (!groupSummary) return ; + + return <> + + { groupSummary.profile.name } + ; +}; + +interface ISpacePreviewProps { + space: Room; + onJoinButtonClicked(): void; + onRejectButtonClicked(): void; +} + +const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }: ISpacePreviewProps) => { const cli = useContext(MatrixClientContext); const myMembership = useMyRoomMembership(space); @@ -270,8 +300,18 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
    ; } + let migratedCommunitySection: JSX.Element; + const createContent = space.currentState.getStateEvents(EventType.RoomCreate, "")?.getContent(); + if (createContent[CreateEventField]) { + migratedCommunitySection =
    + { _t("Created from ", {}, { + Community: () => , + }) } +
    ; + } + return
    - + { migratedCommunitySection } { inviterSection }

    diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx index d9db140645..423738acb8 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx +++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx @@ -17,6 +17,7 @@ limitations under the License. import React, { ChangeEvent, createRef, FormEvent, MouseEvent } from 'react'; import classNames from 'classnames'; import { MatrixClient } from "matrix-js-sdk/src/client"; +import { AuthType, IAuthDict, IInputs, IStageStatus } from 'matrix-js-sdk/src/interactive-auth'; import { _t } from '../../../languageHandler'; import SettingsStore from "../../../settings/SettingsStore"; @@ -74,33 +75,6 @@ import CaptchaForm from "./CaptchaForm"; * focus: set the input focus appropriately in the form. */ -enum AuthType { - Password = "m.login.password", - Recaptcha = "m.login.recaptcha", - Terms = "m.login.terms", - Email = "m.login.email.identity", - Msisdn = "m.login.msisdn", - Sso = "m.login.sso", - SsoUnstable = "org.matrix.login.sso", -} - -/* eslint-disable camelcase */ -interface IAuthDict { - type?: AuthType; - // TODO: Remove `user` once servers support proper UIA - // See https://github.com/vector-im/element-web/issues/10312 - user?: string; - identifier?: any; - password?: string; - response?: string; - // TODO: Remove `threepid_creds` once servers support proper UIA - // See https://github.com/vector-im/element-web/issues/10312 - // See https://github.com/matrix-org/matrix-doc/issues/2220 - threepid_creds?: any; - threepidCreds?: any; -} -/* eslint-enable camelcase */ - export const DEFAULT_PHASE = 0; interface IAuthEntryProps { @@ -835,7 +809,26 @@ export class FallbackAuthEntry extends React.Component { } } -export default function getEntryComponentForLoginType(loginType: AuthType): typeof React.Component { +export interface IStageComponentProps extends IAuthEntryProps { + clientSecret?: string; + stageParams?: Record; + inputs?: IInputs; + stageState?: IStageStatus; + showContinue?: boolean; + continueText?: string; + continueKind?: string; + fail?(e: Error): void; + setEmailSid?(sid: string): void; + onCancel?(): void; +} + +export interface IStageComponent extends React.ComponentClass> { + tryContinue?(): void; + attemptFailed?(): void; + focus?(): void; +} + +export default function getEntryComponentForLoginType(loginType: AuthType): IStageComponent { switch (loginType) { case AuthType.Password: return PasswordAuthEntry; diff --git a/src/components/views/context_menus/DialpadContextMenu.tsx b/src/components/views/context_menus/DialpadContextMenu.tsx index 0bb96f9397..01c7c6c1d8 100644 --- a/src/components/views/context_menus/DialpadContextMenu.tsx +++ b/src/components/views/context_menus/DialpadContextMenu.tsx @@ -14,8 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; -import AccessibleButton from "../elements/AccessibleButton"; +import * as React from "react"; +import { createRef } from "react"; +import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import { ContextMenu, IProps as IContextMenuProps } from '../../structures/ContextMenu'; import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import Field from "../elements/Field"; @@ -32,6 +33,8 @@ interface IState { @replaceableComponent("views.context_menus.DialpadContextMenu") export default class DialpadContextMenu extends React.Component { + private numberEntryFieldRef: React.RefObject = createRef(); + constructor(props) { super(props); @@ -40,9 +43,16 @@ export default class DialpadContextMenu extends React.Component }; } - onDigitPress = (digit) => { + onDigitPress = (digit: string, ev: ButtonEvent) => { this.props.call.sendDtmfDigit(digit); this.setState({ value: this.state.value + digit }); + + // Keep the number field focused so that keyboard entry is still available + // However, don't focus if this wasn't the result of directly clicking on the button, + // i.e someone using keyboard navigation. + if (ev.type === "click") { + this.numberEntryFieldRef.current?.focus(); + } }; onCancelClick = () => { @@ -68,6 +78,7 @@ export default class DialpadContextMenu extends React.Component

    { + createSpaceFromCommunity(this.context, this.props.tag); + this.props.onFinished(); + }; + _onMoveUp = () => { dis.dispatch(TagOrderActions.moveTag(this.context, this.props.tag, this.props.index - 1)); this.props.onFinished(); @@ -77,6 +84,16 @@ export default class TagTileContextMenu extends React.Component { ); } + let createSpaceOption; + if (GroupStore.isUserPrivileged(this.props.tag)) { + createSpaceOption = <> +
    + + { _t("Create Space") } + + ; + } + return
    { _t('View Community') } @@ -88,6 +105,7 @@ export default class TagTileContextMenu extends React.Component { { _t("Unpin") } + { createSpaceOption }
    ; } } diff --git a/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx b/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx new file mode 100644 index 0000000000..4fb0994e23 --- /dev/null +++ b/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx @@ -0,0 +1,340 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, { useEffect, useRef, useState } from "react"; +import { JoinRule } from "matrix-js-sdk/src/@types/partials"; +import { EventType } from "matrix-js-sdk/src/@types/event"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; + +import { _t } from '../../../languageHandler'; +import BaseDialog from "./BaseDialog"; +import AccessibleButton from "../elements/AccessibleButton"; +import { createSpace, SpaceCreateForm } from "../spaces/SpaceCreateMenu"; +import JoinRuleDropdown from "../elements/JoinRuleDropdown"; +import Field from "../elements/Field"; +import RoomAliasField from "../elements/RoomAliasField"; +import { GroupMember } from "../right_panel/UserInfo"; +import { parseMembersResponse, parseRoomsResponse } from "../../../stores/GroupStore"; +import { calculateRoomVia, makeRoomPermalink } from "../../../utils/permalinks/Permalinks"; +import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; +import Spinner from "../elements/Spinner"; +import { mediaFromMxc } from "../../../customisations/Media"; +import SpaceStore from "../../../stores/SpaceStore"; +import Modal from "../../../Modal"; +import InfoDialog from "./InfoDialog"; +import dis from "../../../dispatcher/dispatcher"; +import { Action } from "../../../dispatcher/actions"; +import { UserTab } from "./UserSettingsDialog"; +import TagOrderActions from "../../../actions/TagOrderActions"; + +interface IProps { + matrixClient: MatrixClient; + groupId: string; + onFinished(spaceId?: string): void; +} + +export const CreateEventField = "io.element.migrated_from_community"; + +interface IGroupRoom { + displayname: string; + name?: string; + roomId: string; + canonicalAlias?: string; + avatarUrl?: string; + topic?: string; + numJoinedMembers?: number; + worldReadable?: boolean; + guestCanJoin?: boolean; + isPublic?: boolean; +} + +/* eslint-disable camelcase */ +export interface IGroupSummary { + profile: { + avatar_url?: string; + is_openly_joinable?: boolean; + is_public?: boolean; + long_description: string; + name: string; + short_description: string; + }; + rooms_section: { + rooms: unknown[]; + categories: Record; + total_room_count_estimate: number; + }; + user: { + is_privileged: boolean; + is_public: boolean; + is_publicised: boolean; + membership: string; + }; + users_section: { + users: unknown[]; + roles: Record; + total_user_count_estimate: number; + }; +} +/* eslint-enable camelcase */ + +const CreateSpaceFromCommunityDialog: React.FC = ({ matrixClient: cli, groupId, onFinished }) => { + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [busy, setBusy] = useState(false); + + const [avatar, setAvatar] = useState(null); // undefined means to remove avatar + const [name, setName] = useState(""); + const spaceNameField = useRef(); + const [alias, setAlias] = useState("#" + groupId.substring(1, groupId.indexOf(":")) + ":" + cli.getDomain()); + const spaceAliasField = useRef(); + const [topic, setTopic] = useState(""); + const [joinRule, setJoinRule] = useState(JoinRule.Public); + + const groupSummary = useAsyncMemo(() => cli.getGroupSummary(groupId), [groupId]); + useEffect(() => { + if (groupSummary) { + setName(groupSummary.profile.name || ""); + setTopic(groupSummary.profile.short_description || ""); + setJoinRule(groupSummary.profile.is_openly_joinable ? JoinRule.Public : JoinRule.Invite); + setLoading(false); + } + }, [groupSummary]); + + if (loading) { + return ; + } + + const onCreateSpaceClick = async (e) => { + e.preventDefault(); + if (busy) return; + + setError(null); + setBusy(true); + + // require & validate the space name field + if (!await spaceNameField.current.validate({ allowEmpty: false })) { + setBusy(false); + spaceNameField.current.focus(); + spaceNameField.current.validate({ allowEmpty: false, focused: true }); + return; + } + // validate the space name alias field but do not require it + if (joinRule === JoinRule.Public && !await spaceAliasField.current.validate({ allowEmpty: true })) { + setBusy(false); + spaceAliasField.current.focus(); + spaceAliasField.current.validate({ allowEmpty: true, focused: true }); + return; + } + + try { + const [rooms, members, invitedMembers] = await Promise.all([ + cli.getGroupRooms(groupId).then(parseRoomsResponse) as Promise, + cli.getGroupUsers(groupId).then(parseMembersResponse) as Promise, + cli.getGroupInvitedUsers(groupId).then(parseMembersResponse) as Promise, + ]); + + const viaMap = new Map(); + for (const { roomId, canonicalAlias } of rooms) { + const room = cli.getRoom(roomId); + if (room) { + viaMap.set(roomId, calculateRoomVia(room)); + } else if (canonicalAlias) { + try { + const { servers } = await cli.getRoomIdForAlias(canonicalAlias); + viaMap.set(roomId, servers); + } catch (e) { + console.warn("Failed to resolve alias during community migration", e); + } + } + + if (!viaMap.get(roomId)?.length) { + // XXX: lets guess the via, this might end up being incorrect. + const str = canonicalAlias || roomId; + viaMap.set(roomId, [str.substring(1, str.indexOf(":"))]); + } + } + + const spaceAvatar = avatar !== undefined ? avatar : groupSummary.profile.avatar_url; + const roomId = await createSpace(name, joinRule === JoinRule.Public, alias, topic, spaceAvatar, { + creation_content: { + [CreateEventField]: groupId, + }, + initial_state: rooms.map(({ roomId }) => ({ + type: EventType.SpaceChild, + state_key: roomId, + content: { + via: viaMap.get(roomId) || [], + }, + })), + invite: [...members, ...invitedMembers].map(m => m.userId).filter(m => m !== cli.getUserId()), + }, { + andView: false, + }); + + // eagerly remove it from the community panel + dis.dispatch(TagOrderActions.removeTag(cli, groupId)); + + // don't bother awaiting this, as we don't hugely care if it fails + cli.setGroupProfile(groupId, { + ...groupSummary.profile, + long_description: `

    ` + + _t("This community has been upgraded into a Space") + `


    ` + + groupSummary.profile.long_description, + } as IGroupSummary["profile"]).catch(e => { + console.warn("Failed to update community profile during migration", e); + }); + + onFinished(roomId); + + const onSpaceClick = () => { + dis.dispatch({ + action: "view_room", + room_id: roomId, + }); + }; + + const onPreferencesClick = () => { + dis.dispatch({ + action: Action.ViewUserSettings, + initialTabId: UserTab.Preferences, + }); + }; + + let spacesDisabledCopy; + if (!SpaceStore.spacesEnabled) { + spacesDisabledCopy = _t("To view Spaces, hide communities in Preferences", {}, { + a: sub => { sub }, + }); + } + + Modal.createDialog(InfoDialog, { + title: _t("Space created"), + description: <> +
    +

    + { _t(" has been made and everyone who was a part of the community has " + + "been invited to it.", {}, { + SpaceName: () => + { name } + , + }) } +   + { spacesDisabledCopy } +

    +

    + { _t("To create a Space from another community, just pick the community in Preferences.") } +

    + , + button: _t("Preferences"), + onFinished: (openPreferences: boolean) => { + if (openPreferences) { + onPreferencesClick(); + } + }, + }, "mx_CreateSpaceFromCommunityDialog_SuccessInfoDialog"); + } catch (e) { + console.error(e); + setError(e); + } + + setBusy(false); + }; + + let footer; + if (error) { + footer = <> + + + +
    { _t("Failed to migrate community") }
    +
    { _t("Try again") }
    +
    + + + { _t("Retry") } + + ; + } else { + footer = <> + onFinished()}> + { _t("Cancel") } + + + { busy ? _t("Creating...") : _t("Create Space") } + + ; + } + + return +
    +

    + { _t("A link to the Space will be put in your community description.") } +   + { _t("All rooms will be added and all community members will be invited.") } +

    +

    + { _t("Flair won't be available in Spaces for the foreseeable future.") } +

    + + +

    { _t("This description will be shown to people when they view your space") }

    + +

    { joinRule === JoinRule.Public + ? _t("Open space for anyone, best for communities") + : _t("Invite only, best for yourself or teams") + }

    + { joinRule !== JoinRule.Public && +
    + } + +
    + +
    + { footer } +
    + ; +}; + +export default CreateSpaceFromCommunityDialog; + diff --git a/src/components/views/dialogs/CreateSubspaceDialog.tsx b/src/components/views/dialogs/CreateSubspaceDialog.tsx index 0d71eb2de3..03927c7d62 100644 --- a/src/components/views/dialogs/CreateSubspaceDialog.tsx +++ b/src/components/views/dialogs/CreateSubspaceDialog.tsx @@ -16,8 +16,7 @@ limitations under the License. import React, { useRef, useState } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; -import { JoinRule, Preset } from "matrix-js-sdk/src/@types/partials"; -import { RoomType } from "matrix-js-sdk/src/@types/event"; +import { JoinRule } from "matrix-js-sdk/src/@types/partials"; import { _t } from '../../../languageHandler'; import BaseDialog from "./BaseDialog"; @@ -27,8 +26,7 @@ import { BetaPill } from "../beta/BetaCard"; import Field from "../elements/Field"; import RoomAliasField from "../elements/RoomAliasField"; import SpaceStore from "../../../stores/SpaceStore"; -import { SpaceCreateForm } from "../spaces/SpaceCreateMenu"; -import createRoom from "../../../createRoom"; +import { createSpace, SpaceCreateForm } from "../spaces/SpaceCreateMenu"; import { SubspaceSelector } from "./AddExistingToSpaceDialog"; import JoinRuleDropdown from "../elements/JoinRuleDropdown"; @@ -81,28 +79,7 @@ const CreateSubspaceDialog: React.FC = ({ space, onAddExistingSpaceClick } try { - await createRoom({ - createOpts: { - preset: joinRule === JoinRule.Public ? Preset.PublicChat : Preset.PrivateChat, - name, - power_level_content_override: { - // Only allow Admins to write to the timeline to prevent hidden sync spam - events_default: 100, - ...joinRule === JoinRule.Public ? { invite: 0 } : {}, - }, - room_alias_name: joinRule === JoinRule.Public && alias - ? alias.substr(1, alias.indexOf(":") - 1) - : undefined, - topic, - }, - avatar, - roomType: RoomType.Space, - parentSpace, - spinner: false, - encryption: false, - andView: true, - inlineErrors: true, - }); + await createSpace(name, joinRule === JoinRule.Public, alias, topic, avatar, {}, { parentSpace }); onFinished(true); } catch (e) { diff --git a/src/components/views/dialogs/DeactivateAccountDialog.tsx b/src/components/views/dialogs/DeactivateAccountDialog.tsx index 7221df222f..6548bd78fc 100644 --- a/src/components/views/dialogs/DeactivateAccountDialog.tsx +++ b/src/components/views/dialogs/DeactivateAccountDialog.tsx @@ -16,6 +16,7 @@ limitations under the License. */ import React from 'react'; +import { AuthType, IAuthData } from 'matrix-js-sdk/src/interactive-auth'; import Analytics from '../../../Analytics'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; @@ -65,7 +66,7 @@ export default class DeactivateAccountDialog extends React.Component { + private onStagePhaseChange = (stage: AuthType, phase: string): void => { const dialogAesthetics = { [SSOAuthEntry.PHASE_PREAUTH]: { body: _t("Confirm your account deactivation by using Single Sign On to prove your identity."), @@ -115,7 +116,10 @@ export default class DeactivateAccountDialog extends React.Component { + private onUIAuthComplete = (auth: IAuthData): void => { + // XXX: this should be returning a promise to maintain the state inside the state machine correct + // but given that a deactivation is followed by a local logout and all object instances being thrown away + // this isn't done. MatrixClientPeg.get().deactivateAccount(auth, this.state.shouldErase).then(r => { // Deactivation worked - logout & close this dialog Analytics.trackEvent('Account', 'Deactivate Account'); @@ -180,7 +184,9 @@ export default class DeactivateAccountDialog extends React.Component void; private debounceTimer: number = null; // actually number because we're in the browser private editorRef = createRef(); + private numberEntryFieldRef: React.RefObject = createRef(); private unmounted = false; constructor(props) { @@ -1283,13 +1284,27 @@ export default class InviteDialog extends React.PureComponent { + private onDigitPress = (digit: string, ev: ButtonEvent) => { this.setState({ dialPadValue: this.state.dialPadValue + digit }); + + // Keep the number field focused so that keyboard entry is still available + // However, don't focus if this wasn't the result of directly clicking on the button, + // i.e someone using keyboard navigation. + if (ev.type === "click") { + this.numberEntryFieldRef.current?.focus(); + } }; - private onDeletePress = () => { + private onDeletePress = (ev: ButtonEvent) => { if (this.state.dialPadValue.length === 0) return; this.setState({ dialPadValue: this.state.dialPadValue.slice(0, -1) }); + + // Keep the number field focused so that keyboard entry is still available + // However, don't focus if this wasn't the result of directly clicking on the button, + // i.e someone using keyboard navigation. + if (ev.type === "click") { + this.numberEntryFieldRef.current?.focus(); + } }; private onTabChange = (tabId: TabId) => { @@ -1543,6 +1558,7 @@ export default class InviteDialog extends React.PureComponent; } else { dialPadField = UserTab.Preferences, _td("Preferences"), "mx_UserSettingsDialog_preferencesIcon", - , + , )); if (SettingsStore.getValue(UIFeature.Voip)) { diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx index 8bb6341c3d..0ce9a3a030 100644 --- a/src/components/views/elements/AccessibleButton.tsx +++ b/src/components/views/elements/AccessibleButton.tsx @@ -67,7 +67,9 @@ export default function AccessibleButton({ ...restProps }: IProps) { const newProps: IAccessibleButtonProps = restProps; - if (!disabled) { + if (disabled) { + newProps["aria-disabled"] = true; + } else { newProps.onClick = onClick; // We need to consume enter onKeyDown and space onKeyUp // otherwise we are risking also activating other keyboard focusable elements @@ -118,7 +120,7 @@ export default function AccessibleButton({ ); // React.createElement expects InputHTMLAttributes - return React.createElement(element, restProps, children); + return React.createElement(element, newProps, children); } AccessibleButton.defaultProps = { diff --git a/src/components/views/elements/DialPadBackspaceButton.tsx b/src/components/views/elements/DialPadBackspaceButton.tsx index 69f0fcb39a..d64ced8239 100644 --- a/src/components/views/elements/DialPadBackspaceButton.tsx +++ b/src/components/views/elements/DialPadBackspaceButton.tsx @@ -15,11 +15,11 @@ limitations under the License. */ import * as React from "react"; -import AccessibleButton from "./AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "./AccessibleButton"; interface IProps { // Callback for when the button is pressed - onBackspacePress: () => void; + onBackspacePress: (ev: ButtonEvent) => void; } export default class DialPadBackspaceButton extends React.PureComponent { diff --git a/src/components/views/elements/Dropdown.tsx b/src/components/views/elements/Dropdown.tsx index dddcceb97c..b4f382c9c3 100644 --- a/src/components/views/elements/Dropdown.tsx +++ b/src/components/views/elements/Dropdown.tsx @@ -18,7 +18,7 @@ limitations under the License. import React, { ChangeEvent, createRef, CSSProperties, ReactElement, ReactNode, Ref } from 'react'; import classnames from 'classnames'; -import AccessibleButton from './AccessibleButton'; +import AccessibleButton, { ButtonEvent } from './AccessibleButton'; import { _t } from '../../../languageHandler'; import { Key } from "../../../Keyboard"; import { replaceableComponent } from "../../../utils/replaceableComponent"; @@ -178,7 +178,7 @@ export default class Dropdown extends React.Component { this.ignoreEvent = ev; }; - private onInputClick = (ev: React.MouseEvent) => { + private onAccessibleButtonClick = (ev: ButtonEvent) => { if (this.props.disabled) return; if (!this.state.expanded) { @@ -186,6 +186,10 @@ export default class Dropdown extends React.Component { expanded: true, }); ev.preventDefault(); + } else if ((ev as React.KeyboardEvent).key === Key.ENTER) { + // the accessible button consumes enter onKeyDown for firing onClick, so handle it here + this.props.onOptionChange(this.state.highlightedOption); + this.close(); } }; @@ -204,7 +208,7 @@ export default class Dropdown extends React.Component { this.props.onOptionChange(dropdownKey); }; - private onInputKeyDown = (e: React.KeyboardEvent) => { + private onKeyDown = (e: React.KeyboardEvent) => { let handled = true; // These keys don't generate keypress events and so needs to be on keyup @@ -269,7 +273,7 @@ export default class Dropdown extends React.Component { private prevOption(optionKey: string): string { const keys = Object.keys(this.childrenByKey); const index = keys.indexOf(optionKey); - return keys[(index - 1) % keys.length]; + return keys[index <= 0 ? keys.length - 1 : (index - 1) % keys.length]; } private scrollIntoView(node: Element) { @@ -320,7 +324,6 @@ export default class Dropdown extends React.Component { type="text" autoFocus={true} className="mx_Dropdown_option" - onKeyDown={this.onInputKeyDown} onChange={this.onInputChange} value={this.state.searchQuery} role="combobox" @@ -329,6 +332,7 @@ export default class Dropdown extends React.Component { aria-owns={`${this.props.id}_listbox`} aria-disabled={this.props.disabled} aria-label={this.props.label} + onKeyDown={this.onKeyDown} /> ); } @@ -361,13 +365,14 @@ export default class Dropdown extends React.Component { return
    { currentValue } diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index bc868c35b3..f723f5f66f 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, { createRef } from 'react'; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { _t, _td } from '../../../languageHandler'; @@ -27,6 +27,8 @@ import classNames from 'classnames'; import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; import { formatCallTime } from "../../../DateUtils"; +const MAX_NON_NARROW_WIDTH = 400 / 70 * 100; + interface IProps { mxEvent: MatrixEvent; callEventGrouper: CallEventGrouper; @@ -35,6 +37,7 @@ interface IProps { interface IState { callState: CallState | CustomCallState; silenced: boolean; + narrow: boolean; } const TEXTUAL_STATES: Map = new Map([ @@ -42,26 +45,42 @@ const TEXTUAL_STATES: Map = new Map([ [CallState.Connecting, _td("Connecting")], ]); -export default class CallEvent extends React.Component { +export default class CallEvent extends React.PureComponent { + private wrapperElement = createRef(); + private resizeObserver: ResizeObserver; + constructor(props: IProps) { super(props); this.state = { callState: this.props.callEventGrouper.state, silenced: false, + narrow: false, }; } componentDidMount() { this.props.callEventGrouper.addListener(CallEventGrouperEvent.StateChanged, this.onStateChanged); this.props.callEventGrouper.addListener(CallEventGrouperEvent.SilencedChanged, this.onSilencedChanged); + + this.resizeObserver = new ResizeObserver(this.resizeObserverCallback); + this.resizeObserver.observe(this.wrapperElement.current); } componentWillUnmount() { this.props.callEventGrouper.removeListener(CallEventGrouperEvent.StateChanged, this.onStateChanged); this.props.callEventGrouper.removeListener(CallEventGrouperEvent.SilencedChanged, this.onSilencedChanged); + + this.resizeObserver.disconnect(); } + private resizeObserverCallback = (entries: ResizeObserverEntry[]): void => { + const wrapperElementEntry = entries.find((entry) => entry.target === this.wrapperElement.current); + if (!wrapperElementEntry) return; + + this.setState({ narrow: wrapperElementEntry.contentRect.width < MAX_NON_NARROW_WIDTH }); + }; + private onSilencedChanged = (newState) => { this.setState({ silenced: newState }); }; @@ -82,21 +101,32 @@ export default class CallEvent extends React.Component { ); } + private renderSilenceIcon(): JSX.Element { + const silenceClass = classNames({ + "mx_CallEvent_iconButton": true, + "mx_CallEvent_unSilence": this.state.silenced, + "mx_CallEvent_silence": !this.state.silenced, + }); + + return ( + + ); + } + private renderContent(state: CallState | CustomCallState): JSX.Element { if (state === CallState.Ringing) { - const silenceClass = classNames({ - "mx_CallEvent_iconButton": true, - "mx_CallEvent_unSilence": this.state.silenced, - "mx_CallEvent_silence": !this.state.silenced, - }); + let silenceIcon; + if (!this.state.narrow) { + silenceIcon = this.renderSilenceIcon(); + } return (
    - + { silenceIcon } { } else if (hangupReason === CallErrorCode.InviteTimeout) { return (
    - { _t("Missed call") } + { _t("No answer") } { this.renderCallBackButton(_t("Call back")) }
    ); @@ -169,7 +199,7 @@ export default class CallEvent extends React.Component { } else if (hangupReason === CallErrorCode.UserBusy) { reason = _t("The user you called is busy."); } else { - reason = _t('Unknown failure: %(reason)s)', { reason: hangupReason }); + reason = _t('Unknown failure: %(reason)s', { reason: hangupReason }); } return ( @@ -215,35 +245,41 @@ export default class CallEvent extends React.Component { const callState = this.state.callState; const hangupReason = this.props.callEventGrouper.hangupReason; const content = this.renderContent(callState); - const className = classNames({ - mx_CallEvent: true, + const className = classNames("mx_CallEvent", { mx_CallEvent_voice: isVoice, mx_CallEvent_video: !isVoice, - mx_CallEvent_missed: ( - callState === CustomCallState.Missed || - (callState === CallState.Ended && hangupReason === CallErrorCode.InviteTimeout) - ), + mx_CallEvent_narrow: this.state.narrow, + mx_CallEvent_missed: callState === CustomCallState.Missed, + mx_CallEvent_noAnswer: callState === CallState.Ended && hangupReason === CallErrorCode.InviteTimeout, + mx_CallEvent_rejected: callState === CallState.Ended && this.props.callEventGrouper.gotRejected, }); + let silenceIcon; + if (this.state.narrow && this.state.callState === CallState.Ringing) { + silenceIcon = this.renderSilenceIcon(); + } return ( -
    -
    - -
    -
    - { sender } -
    -
    -
    - { callType } +
    +
    + { silenceIcon } +
    + +
    +
    + { sender } +
    +
    +
    + { callType } +
    + { content }
    - { content }
    ); } diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index f682f1c8c0..e7b77b731f 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -25,12 +25,14 @@ import SettingsStore from "../../../settings/SettingsStore"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import InlineSpinner from '../elements/InlineSpinner'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -import { mediaFromContent } from "../../../customisations/Media"; +import { Media, mediaFromContent } from "../../../customisations/Media"; import { BLURHASH_FIELD } from "../../../ContentMessages"; import { IMediaEventContent } from '../../../customisations/models/IMediaEventContent'; import ImageView from '../elements/ImageView'; import { SyncState } from 'matrix-js-sdk/src/sync.api'; import { IBodyProps } from "./IBodyProps"; +import classNames from 'classnames'; +import { CSSTransition, SwitchTransition } from 'react-transition-group'; interface IState { decryptedUrl?: string; @@ -157,19 +159,21 @@ export default class MImageBody extends React.Component { // this is only used as a fallback in case content.info.w/h is missing loadedImageDimensions = { naturalWidth, naturalHeight }; } - this.setState({ imgLoaded: true, loadedImageDimensions }); }; protected getContentUrl(): string { - const media = mediaFromContent(this.props.mxEvent.getContent()); - if (media.isEncrypted) { + if (this.media.isEncrypted) { return this.state.decryptedUrl; } else { - return media.srcHttp; + return this.media.srcHttp; } } + private get media(): Media { + return mediaFromContent(this.props.mxEvent.getContent()); + } + protected getThumbUrl(): string { // FIXME: we let images grow as wide as you like, rather than capped to 800x600. // So either we need to support custom timeline widths here, or reimpose the cap, otherwise the @@ -225,7 +229,7 @@ export default class MImageBody extends React.Component { info.w > thumbWidth || info.h > thumbHeight ); - const isLargeFileSize = info.size > 1*1024*1024; // 1mb + const isLargeFileSize = info.size > 1 * 1024 * 1024; // 1mb if (isLargeFileSize && isLargerThanThumbnail) { // image is too large physically and bytewise to clutter our timeline so @@ -347,12 +351,21 @@ export default class MImageBody extends React.Component { className="mx_MImageBody_thumbnail" src={thumbUrl} ref={this.image} - style={{ maxWidth: `min(100%, ${maxWidth}px)` }} + // Force the image to be the full size of the container, even if the + // pixel size is smaller. The problem here is that we don't know what + // thumbnail size the HS is going to give us, but we have to commit to + // a container size immediately and not change it when the image loads + // or we'll get a scroll jump (or have to leave blank space). + // This will obviously result in an upscaled image which will be a bit + // blurry. The best fix would be for the HS to advertise what size thumbnails + // it guarantees to produce. + style={{ height: '100%' }} alt={content.body} onError={this.onImageError} onLoad={this.onImageLoad} onMouseEnter={this.onImageEnter} - onMouseLeave={this.onImageLeave} /> + onMouseLeave={this.onImageLeave} + /> ); } @@ -365,21 +378,41 @@ export default class MImageBody extends React.Component { gifLabel =

    GIF

    ; } + const classes = classNames({ + 'mx_MImageBody_thumbnail': true, + 'mx_MImageBody_thumbnail--blurhash': this.props.mxEvent.getContent().info[BLURHASH_FIELD], + }); + + // This has incredibly broken types. + const C = CSSTransition as any; const thumbnail = (
    - { showPlaceholder && -
    + - { placeholder } -
    - } + { /* This weirdly looking div is necessary here, otherwise SwitchTransition fails */ } +
    + { showPlaceholder &&
    + { placeholder } +
    } +
    + + -
    +
    { img } { gifLabel }
    @@ -401,7 +434,7 @@ export default class MImageBody extends React.Component { // Overidden by MStickerBody protected getPlaceholder(width: number, height: number): JSX.Element { const blurhash = this.props.mxEvent.getContent().info[BLURHASH_FIELD]; - if (blurhash) return ; + if (blurhash) return ; return ( ); @@ -443,10 +476,12 @@ export default class MImageBody extends React.Component { const thumbnail = this.messageContent(contentUrl, thumbUrl, content); const fileBody = this.getFileBody(); - return
    - { thumbnail } - { fileBody } -
    ; + return ( +
    + { thumbnail } + { fileBody } +
    + ); } } diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index ba6bb26cbf..138f5bf9fe 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -851,7 +851,7 @@ const RoomAdminToolsContainer: React.FC = ({ return
    ; }; -interface GroupMember { +export interface GroupMember { userId: string; displayname?: string; // XXX: GroupMember objects are inconsistent :(( avatarUrl?: string; diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index 6b5edcf91b..34909baef1 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -25,7 +25,6 @@ import SettingsStore from "../../../settings/SettingsStore"; import Autocompleter from '../../../autocomplete/Autocompleter'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -const COMPOSER_SELECTED = 0; const MAX_PROVIDER_MATCHES = 20; export const generateCompletionDomId = (number) => `mx_Autocomplete_Completion_${number}`; @@ -34,9 +33,9 @@ interface IProps { // the query string for which to show autocomplete suggestions query: string; // method invoked with range and text content when completion is confirmed - onConfirm: (ICompletion) => void; + onConfirm: (completion: ICompletion) => void; // method invoked when selected (if any) completion changes - onSelectionChange?: (ICompletion, number) => void; + onSelectionChange?: (partIndex: number) => void; selection: ISelectionRange; // The room in which we're autocompleting room: Room; @@ -71,7 +70,7 @@ export default class Autocomplete extends React.PureComponent { completionList: [], // how far down the completion list we are (THIS IS 1-INDEXED!) - selectionOffset: COMPOSER_SELECTED, + selectionOffset: 1, // whether we should show completions if they're available shouldShowCompletions: true, @@ -86,7 +85,7 @@ export default class Autocomplete extends React.PureComponent { this.applyNewProps(); } - private applyNewProps(oldQuery?: string, oldRoom?: Room) { + private applyNewProps(oldQuery?: string, oldRoom?: Room): void { if (oldRoom && this.props.room.roomId !== oldRoom.roomId) { this.autocompleter.destroy(); this.autocompleter = new Autocompleter(this.props.room); @@ -104,7 +103,7 @@ export default class Autocomplete extends React.PureComponent { this.autocompleter.destroy(); } - complete(query: string, selection: ISelectionRange) { + private complete(query: string, selection: ISelectionRange): Promise { this.queryRequested = query; if (this.debounceCompletionsRequest) { clearTimeout(this.debounceCompletionsRequest); @@ -115,7 +114,7 @@ export default class Autocomplete extends React.PureComponent { completions: [], completionList: [], // Reset selected completion - selectionOffset: COMPOSER_SELECTED, + selectionOffset: 1, // Hide the autocomplete box hide: true, }); @@ -135,7 +134,7 @@ export default class Autocomplete extends React.PureComponent { }); } - processQuery(query: string, selection: ISelectionRange) { + private processQuery(query: string, selection: ISelectionRange): Promise { return this.autocompleter.getCompletions( query, selection, this.state.forceComplete, MAX_PROVIDER_MATCHES, ).then((completions) => { @@ -147,30 +146,35 @@ export default class Autocomplete extends React.PureComponent { }); } - processCompletions(completions: IProviderCompletions[]) { + private processCompletions(completions: IProviderCompletions[]): void { const completionList = flatMap(completions, (provider) => provider.completions); // Reset selection when completion list becomes empty. - let selectionOffset = COMPOSER_SELECTED; + let selectionOffset = 1; if (completionList.length > 0) { /* If the currently selected completion is still in the completion list, try to find it and jump to it. If not, select composer. */ - const currentSelection = this.state.selectionOffset === 0 ? null : + const currentSelection = this.state.selectionOffset <= 1 ? null : this.state.completionList[this.state.selectionOffset - 1].completion; selectionOffset = completionList.findIndex( (completion) => completion.completion === currentSelection); if (selectionOffset === -1) { - selectionOffset = COMPOSER_SELECTED; + selectionOffset = 1; } else { selectionOffset++; // selectionOffset is 1-indexed! } } - let hide = this.state.hide; + let hide = true; // If `completion.command.command` is truthy, then a provider has matched with the query const anyMatches = completions.some((completion) => !!completion.command.command); - hide = !anyMatches; + if (anyMatches) { + hide = false; + if (this.props.onSelectionChange) { + this.props.onSelectionChange(selectionOffset - 1); + } + } this.setState({ completions, @@ -182,25 +186,25 @@ export default class Autocomplete extends React.PureComponent { }); } - hasSelection(): boolean { + public hasSelection(): boolean { return this.countCompletions() > 0 && this.state.selectionOffset !== 0; } - countCompletions(): number { + public countCompletions(): number { return this.state.completionList.length; } // called from MessageComposerInput - moveSelection(delta: number) { + public moveSelection(delta: number): void { const completionCount = this.countCompletions(); if (completionCount === 0) return; // there are no items to move the selection through // Note: selectionOffset 0 represents the unsubstituted text, while 1 means first pill selected - const index = (this.state.selectionOffset + delta + completionCount + 1) % (completionCount + 1); - this.setSelection(index); + const index = (this.state.selectionOffset + delta + completionCount - 1) % completionCount; + this.setSelection(1 + index); } - onEscape(e: KeyboardEvent): boolean { + public onEscape(e: KeyboardEvent): boolean { const completionCount = this.countCompletions(); if (completionCount === 0) { // autocomplete is already empty, so don't preventDefault @@ -213,16 +217,16 @@ export default class Autocomplete extends React.PureComponent { this.hide(); } - hide = () => { + private hide = (): void => { this.setState({ hide: true, - selectionOffset: 0, + selectionOffset: 1, completions: [], completionList: [], }); }; - forceComplete() { + public forceComplete(): Promise { return new Promise((resolve) => { this.setState({ forceComplete: true, @@ -235,8 +239,13 @@ export default class Autocomplete extends React.PureComponent { }); } - onCompletionClicked = (selectionOffset: number): boolean => { - if (this.countCompletions() === 0 || selectionOffset === COMPOSER_SELECTED) { + public onConfirmCompletion = (): void => { + this.onCompletionClicked(this.state.selectionOffset); + }; + + private onCompletionClicked = (selectionOffset: number): boolean => { + const count = this.countCompletions(); + if (count === 0 || selectionOffset < 1 || selectionOffset > count) { return false; } @@ -246,10 +255,10 @@ export default class Autocomplete extends React.PureComponent { return true; }; - setSelection(selectionOffset: number) { + private setSelection(selectionOffset: number): void { this.setState({ selectionOffset, hide: false }); if (this.props.onSelectionChange) { - this.props.onSelectionChange(this.state.completionList[selectionOffset - 1], selectionOffset - 1); + this.props.onSelectionChange(selectionOffset - 1); } } @@ -292,7 +301,7 @@ export default class Autocomplete extends React.PureComponent { }); return completions.length > 0 ? ( -
    +
    { completionResult.provider.getName() }
    { completionResult.provider.renderCompletions(completions) }
    @@ -300,7 +309,7 @@ export default class Autocomplete extends React.PureComponent { }).filter((completion) => !!completion); return !this.state.hide && renderedCompletions.length > 0 ? ( -
    +
    { renderedCompletions }
    ) : null; diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index db3d3eee5e..48f2e2a39b 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -31,7 +31,7 @@ import { } from '../../../editor/operations'; import { getCaretOffsetAndText, getRangeForSelection } from '../../../editor/dom'; import Autocomplete, { generateCompletionDomId } from '../rooms/Autocomplete'; -import { getAutoCompleteCreator } from '../../../editor/parts'; +import { getAutoCompleteCreator, Type } from '../../../editor/parts'; import { parseEvent, parsePlainTextMessage } from '../../../editor/deserialize'; import { renderModel } from '../../../editor/render'; import TypingStore from "../../../stores/TypingStore"; @@ -133,6 +133,7 @@ export default class BasicMessageEditor extends React.Component this.state = { showPillAvatar: SettingsStore.getValue("Pill.shouldShowPillAvatar"), surroundWith: SettingsStore.getValue("MessageComposerInput.surroundWith"), + showVisualBell: false, }; this.emoticonSettingHandle = SettingsStore.watchSetting('MessageComposerInput.autoReplaceEmoji', null, @@ -169,7 +170,7 @@ export default class BasicMessageEditor extends React.Component range.expandBackwardsWhile((index, offset) => { const part = model.parts[index]; n -= 1; - return n >= 0 && (part.type === "plain" || part.type === "pill-candidate"); + return n >= 0 && (part.type === Type.Plain || part.type === Type.PillCandidate); }); const emoticonMatch = REGEX_EMOTICON_WHITESPACE.exec(range.text); if (emoticonMatch) { @@ -215,7 +216,11 @@ export default class BasicMessageEditor extends React.Component if (isEmpty) { this.formatBarRef.current.hide(); } - this.setState({ autoComplete: this.props.model.autoComplete }); + this.setState({ + autoComplete: this.props.model.autoComplete, + // if a change is happening then clear the showVisualBell + showVisualBell: diff ? false : this.state.showVisualBell, + }); this.historyManager.tryPush(this.props.model, selection, inputType, diff); let isTyping = !this.props.model.isEmpty; @@ -435,7 +440,7 @@ export default class BasicMessageEditor extends React.Component const model = this.props.model; let handled = false; - if (this.state.surroundWith && document.getSelection().type != "Caret") { + if (this.state.surroundWith && document.getSelection().type !== "Caret") { // This surrounds the selected text with a character. This is // intentionally left out of the keybinding manager as the keybinds // here shouldn't be changeable @@ -456,6 +461,44 @@ export default class BasicMessageEditor extends React.Component } } + const autocompleteAction = getKeyBindingsManager().getAutocompleteAction(event); + if (model.autoComplete?.hasCompletions()) { + const autoComplete = model.autoComplete; + switch (autocompleteAction) { + case AutocompleteAction.ForceComplete: + case AutocompleteAction.Complete: + autoComplete.confirmCompletion(); + handled = true; + break; + case AutocompleteAction.PrevSelection: + autoComplete.selectPreviousSelection(); + handled = true; + break; + case AutocompleteAction.NextSelection: + autoComplete.selectNextSelection(); + handled = true; + break; + case AutocompleteAction.Cancel: + autoComplete.onEscape(event); + handled = true; + break; + default: + return; // don't preventDefault on anything else + } + } else if (autocompleteAction === AutocompleteAction.ForceComplete && !this.state.showVisualBell) { + // there is no current autocomplete window, try to open it + this.tabCompleteName(); + handled = true; + } else if (event.key === Key.BACKSPACE || event.key === Key.DELETE) { + this.formatBarRef.current.hide(); + } + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + return; + } + const action = getKeyBindingsManager().getMessageComposerAction(event); switch (action) { case MessageComposerAction.FormatBold: @@ -507,42 +550,6 @@ export default class BasicMessageEditor extends React.Component handled = true; break; } - if (handled) { - event.preventDefault(); - event.stopPropagation(); - return; - } - - const autocompleteAction = getKeyBindingsManager().getAutocompleteAction(event); - if (model.autoComplete && model.autoComplete.hasCompletions()) { - const autoComplete = model.autoComplete; - switch (autocompleteAction) { - case AutocompleteAction.CompleteOrPrevSelection: - case AutocompleteAction.PrevSelection: - autoComplete.selectPreviousSelection(); - handled = true; - break; - case AutocompleteAction.CompleteOrNextSelection: - case AutocompleteAction.NextSelection: - autoComplete.selectNextSelection(); - handled = true; - break; - case AutocompleteAction.Cancel: - autoComplete.onEscape(event); - handled = true; - break; - default: - return; // don't preventDefault on anything else - } - } else if (autocompleteAction === AutocompleteAction.CompleteOrPrevSelection - || autocompleteAction === AutocompleteAction.CompleteOrNextSelection) { - // there is no current autocomplete window, try to open it - this.tabCompleteName(); - handled = true; - } else if (event.key === Key.BACKSPACE || event.key === Key.DELETE) { - this.formatBarRef.current.hide(); - } - if (handled) { event.preventDefault(); event.stopPropagation(); @@ -558,9 +565,9 @@ export default class BasicMessageEditor extends React.Component const range = model.startRange(position); range.expandBackwardsWhile((index, offset, part) => { return part.text[offset] !== " " && part.text[offset] !== "+" && ( - part.type === "plain" || - part.type === "pill-candidate" || - part.type === "command" + part.type === Type.Plain || + part.type === Type.PillCandidate || + part.type === Type.Command ); }); const { partCreator } = model; @@ -577,6 +584,8 @@ export default class BasicMessageEditor extends React.Component this.setState({ showVisualBell: true }); model.autoComplete.close(); } + } else { + this.setState({ showVisualBell: true }); } } catch (err) { console.error(err); @@ -592,9 +601,8 @@ export default class BasicMessageEditor extends React.Component this.props.model.autoComplete.onComponentConfirm(completion); }; - private onAutoCompleteSelectionChange = (completion: ICompletion, completionIndex: number): void => { + private onAutoCompleteSelectionChange = (completionIndex: number): void => { this.modifiedFlag = true; - this.props.model.autoComplete.onComponentSelectionChange(completion); this.setState({ completionIndex }); }; @@ -718,6 +726,11 @@ export default class BasicMessageEditor extends React.Component }; const { completionIndex } = this.state; + const hasAutocomplete = Boolean(this.state.autoComplete); + let activeDescendant; + if (hasAutocomplete && completionIndex >= 0) { + activeDescendant = generateCompletionDomId(completionIndex); + } return (
    { autoComplete } @@ -736,10 +749,11 @@ export default class BasicMessageEditor extends React.Component aria-label={this.props.label} role="textbox" aria-multiline="true" - aria-autocomplete="both" + aria-autocomplete="list" aria-haspopup="listbox" - aria-expanded={Boolean(this.state.autoComplete)} - aria-activedescendant={completionIndex >= 0 ? generateCompletionDomId(completionIndex) : undefined} + aria-expanded={hasAutocomplete} + aria-owns="mx_Autocomplete" + aria-activedescendant={activeDescendant} dir="auto" aria-disabled={this.props.disabled} /> diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index e4b13e2155..b7e067ee93 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -25,7 +25,7 @@ import { getCaretOffsetAndText } from '../../../editor/dom'; import { htmlSerializeIfNeeded, textSerialize, containsEmote, stripEmoteCommand } from '../../../editor/serialize'; import { findEditableEvent } from '../../../utils/EventUtils'; import { parseEvent } from '../../../editor/deserialize'; -import { CommandPartCreator, Part, PartCreator } from '../../../editor/parts'; +import { CommandPartCreator, Part, PartCreator, Type } from '../../../editor/parts'; import EditorStateTransfer from '../../../utils/EditorStateTransfer'; import BasicMessageComposer from "./BasicMessageComposer"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; @@ -242,12 +242,12 @@ export default class EditMessageComposer extends React.Component const parts = this.model.parts; const firstPart = parts[0]; if (firstPart) { - if (firstPart.type === "command" && firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")) { + if (firstPart.type === Type.Command && firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")) { return true; } if (firstPart.text.startsWith("/") && !firstPart.text.startsWith("//") - && (firstPart.type === "plain" || firstPart.type === "pill-candidate")) { + && (firstPart.type === Type.Plain || firstPart.type === Type.PillCandidate)) { return true; } } @@ -268,7 +268,7 @@ export default class EditMessageComposer extends React.Component private getSlashCommand(): [Command, string, string] { const commandText = this.model.parts.reduce((text, part) => { // use mxid to textify user pills in a command - if (part.type === "user-pill") { + if (part.type === Type.UserPill) { return text + part.resourceId; } return text + part.text; diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 30355e55bf..52a544c56b 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -969,8 +969,11 @@ export default class EventTile extends React.Component { } else if (this.props.layout == Layout.IRC) { avatarSize = 14; needsSenderProfile = true; - } else if (this.props.continuation && this.props.tileShape !== TileShape.FileGrid) { - // no avatar or sender profile for continuation messages + } else if ( + (this.props.continuation && this.props.tileShape !== TileShape.FileGrid) || + this.props.mxEvent.getType() === EventType.CallInvite + ) { + // no avatar or sender profile for continuation messages and call tiles avatarSize = 0; needsSenderProfile = false; } else { diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index e967aa2852..205320fb68 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -31,7 +31,7 @@ import { textSerialize, unescapeMessage, } from '../../../editor/serialize'; -import { CommandPartCreator, Part, PartCreator, SerializedPart } from '../../../editor/parts'; +import { CommandPartCreator, Part, PartCreator, SerializedPart, Type } from '../../../editor/parts'; import BasicMessageComposer from "./BasicMessageComposer"; import ReplyThread from "../elements/ReplyThread"; import { findEditableEvent } from '../../../utils/EventUtils'; @@ -240,14 +240,14 @@ export default class SendMessageComposer extends React.Component { const parts = this.model.parts; const firstPart = parts[0]; if (firstPart) { - if (firstPart.type === "command" && firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")) { + if (firstPart.type === Type.Command && firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")) { return true; } // be extra resilient when somehow the AutocompleteWrapperModel or // CommandPartCreator fails to insert a command part, so we don't send // a command as a message if (firstPart.text.startsWith("/") && !firstPart.text.startsWith("//") - && (firstPart.type === "plain" || firstPart.type === "pill-candidate")) { + && (firstPart.type === Type.Plain || firstPart.type === Type.PillCandidate)) { return true; } } diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.tsx similarity index 85% rename from src/components/views/settings/CrossSigningPanel.js rename to src/components/views/settings/CrossSigningPanel.tsx index 8b9d68bfa5..21e38a762a 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.tsx @@ -24,36 +24,39 @@ import Spinner from '../elements/Spinner'; import InteractiveAuthDialog from '../dialogs/InteractiveAuthDialog'; import ConfirmDestroyCrossSigningDialog from '../dialogs/security/ConfirmDestroyCrossSigningDialog'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { MatrixEvent } from 'matrix-js-sdk/src'; + +interface IState { + error?: Error; + crossSigningPublicKeysOnDevice?: boolean; + crossSigningPrivateKeysInStorage?: boolean; + masterPrivateKeyCached?: boolean; + selfSigningPrivateKeyCached?: boolean; + userSigningPrivateKeyCached?: boolean; + homeserverSupportsCrossSigning?: boolean; + crossSigningReady?: boolean; +} @replaceableComponent("views.settings.CrossSigningPanel") -export default class CrossSigningPanel extends React.PureComponent { +export default class CrossSigningPanel extends React.PureComponent<{}, IState> { + private unmounted = false; + constructor(props) { super(props); - this._unmounted = false; - - this.state = { - error: null, - crossSigningPublicKeysOnDevice: null, - crossSigningPrivateKeysInStorage: null, - masterPrivateKeyCached: null, - selfSigningPrivateKeyCached: null, - userSigningPrivateKeyCached: null, - homeserverSupportsCrossSigning: null, - crossSigningReady: null, - }; + this.state = {}; } - componentDidMount() { + public componentDidMount() { const cli = MatrixClientPeg.get(); cli.on("accountData", this.onAccountData); cli.on("userTrustStatusChanged", this.onStatusChanged); cli.on("crossSigning.keysChanged", this.onStatusChanged); - this._getUpdatedStatus(); + this.getUpdatedStatus(); } - componentWillUnmount() { - this._unmounted = true; + public componentWillUnmount() { + this.unmounted = true; const cli = MatrixClientPeg.get(); if (!cli) return; cli.removeListener("accountData", this.onAccountData); @@ -61,28 +64,28 @@ export default class CrossSigningPanel extends React.PureComponent { cli.removeListener("crossSigning.keysChanged", this.onStatusChanged); } - onAccountData = (event) => { + private onAccountData = (event: MatrixEvent): void => { const type = event.getType(); if (type.startsWith("m.cross_signing") || type.startsWith("m.secret_storage")) { - this._getUpdatedStatus(); + this.getUpdatedStatus(); } }; - _onBootstrapClick = () => { - this._bootstrapCrossSigning({ forceReset: false }); + private onBootstrapClick = () => { + this.bootstrapCrossSigning({ forceReset: false }); }; - onStatusChanged = () => { - this._getUpdatedStatus(); + private onStatusChanged = () => { + this.getUpdatedStatus(); }; - async _getUpdatedStatus() { + private async getUpdatedStatus(): Promise { const cli = MatrixClientPeg.get(); const pkCache = cli.getCrossSigningCacheCallbacks(); const crossSigning = cli.crypto.crossSigningInfo; const secretStorage = cli.crypto.secretStorage; - const crossSigningPublicKeysOnDevice = crossSigning.getId(); - const crossSigningPrivateKeysInStorage = await crossSigning.isStoredInSecretStorage(secretStorage); + const crossSigningPublicKeysOnDevice = Boolean(crossSigning.getId()); + const crossSigningPrivateKeysInStorage = Boolean(await crossSigning.isStoredInSecretStorage(secretStorage)); const masterPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("master")); const selfSigningPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("self_signing")); const userSigningPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("user_signing")); @@ -110,8 +113,8 @@ export default class CrossSigningPanel extends React.PureComponent { * 3. All keys are loaded and there's nothing to do. * @param {bool} [forceReset] Bootstrap again even if keys already present */ - _bootstrapCrossSigning = async ({ forceReset = false }) => { - this.setState({ error: null }); + private bootstrapCrossSigning = async ({ forceReset = false }): Promise => { + this.setState({ error: undefined }); try { const cli = MatrixClientPeg.get(); await cli.bootstrapCrossSigning({ @@ -135,20 +138,20 @@ export default class CrossSigningPanel extends React.PureComponent { this.setState({ error: e }); console.error("Error bootstrapping cross-signing", e); } - if (this._unmounted) return; - this._getUpdatedStatus(); - } + if (this.unmounted) return; + this.getUpdatedStatus(); + }; - _resetCrossSigning = () => { + private resetCrossSigning = (): void => { Modal.createDialog(ConfirmDestroyCrossSigningDialog, { onFinished: (act) => { if (!act) return; - this._bootstrapCrossSigning({ forceReset: true }); + this.bootstrapCrossSigning({ forceReset: true }); }, }); - } + }; - render() { + public render() { const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); const { error, @@ -208,7 +211,7 @@ export default class CrossSigningPanel extends React.PureComponent { // TODO: determine how better to expose this to users in addition to prompts at login/toast if (!keysExistEverywhere && homeserverSupportsCrossSigning) { actions.push( - + { _t("Set up") } , ); @@ -216,7 +219,7 @@ export default class CrossSigningPanel extends React.PureComponent { if (keysExistAnywhere) { actions.push( - + { _t("Reset") } , ); diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js index 19a97151d6..8cd991134f 100644 --- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js @@ -79,7 +79,7 @@ export default class LabsUserSettingsTab extends React.Component { let hiddenReadReceipts; if (this.state.showHiddenReadReceipts) { hiddenReadReceipts = ( - + ); } diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index ca85db967a..21c3ab24ec 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -15,7 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, { useContext, useEffect, useState } from 'react'; +import { EventType } from 'matrix-js-sdk/src/@types/event'; + import { _t } from "../../../../../languageHandler"; import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; import SettingsStore from "../../../../../settings/SettingsStore"; @@ -27,6 +29,18 @@ import SettingsFlag from '../../../elements/SettingsFlag'; import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts"; import AccessibleButton from "../../../elements/AccessibleButton"; import SpaceStore from "../../../../../stores/SpaceStore"; +import GroupAvatar from "../../../avatars/GroupAvatar"; +import dis from "../../../../../dispatcher/dispatcher"; +import GroupActions from "../../../../../actions/GroupActions"; +import MatrixClientContext from "../../../../../contexts/MatrixClientContext"; +import { useDispatcher } from "../../../../../hooks/useDispatcher"; +import { CreateEventField, IGroupSummary } from "../../../dialogs/CreateSpaceFromCommunityDialog"; +import { createSpaceFromCommunity } from "../../../../../utils/space"; +import Spinner from "../../../elements/Spinner"; + +interface IProps { + closeSettingsFn(success: boolean): void; +} interface IState { autoLaunch: boolean; @@ -42,8 +56,86 @@ interface IState { readMarkerOutOfViewThresholdMs: string; } +type Community = IGroupSummary & { + groupId: string; + spaceId?: string; +}; + +const CommunityMigrator = ({ onFinished }) => { + const cli = useContext(MatrixClientContext); + const [communities, setCommunities] = useState(null); + useEffect(() => { + dis.dispatch(GroupActions.fetchJoinedGroups(cli)); + }, [cli]); + useDispatcher(dis, async payload => { + if (payload.action === "GroupActions.fetchJoinedGroups.success") { + const communities: Community[] = []; + + const migratedSpaceMap = new Map(cli.getRooms().map(room => { + const createContent = room.currentState.getStateEvents(EventType.RoomCreate, "")?.getContent(); + if (createContent?.[CreateEventField]) { + return [createContent[CreateEventField], room.roomId] as [string, string]; + } + }).filter(Boolean)); + + for (const groupId of payload.result.groups) { + const summary = await cli.getGroupSummary(groupId) as IGroupSummary; + if (summary.user.is_privileged) { + communities.push({ + ...summary, + groupId, + spaceId: migratedSpaceMap.get(groupId), + }); + } + } + + setCommunities(communities); + } + }); + + if (!communities) { + return ; + } + + return
    + { communities.map(community => ( +
    + + { community.profile.name } + { + if (community.spaceId) { + dis.dispatch({ + action: "view_room", + room_id: community.spaceId, + }); + onFinished(); + } else { + createSpaceFromCommunity(cli, community.groupId).then(([spaceId]) => { + if (spaceId) { + community.spaceId = spaceId; + setCommunities([...communities]); // force component re-render + } + }); + } + }} + > + { community.spaceId ? _t("Open Space") : _t("Create Space") } + +
    + )) } +
    ; +}; + @replaceableComponent("views.settings.tabs.user.PreferencesUserSettingsTab") -export default class PreferencesUserSettingsTab extends React.Component<{}, IState> { +export default class PreferencesUserSettingsTab extends React.Component { static ROOM_LIST_SETTINGS = [ 'breadcrumbs', ]; @@ -52,6 +144,10 @@ export default class PreferencesUserSettingsTab extends React.Component<{}, ISta "Spaces.allRoomsInHome", ]; + static COMMUNITIES_SETTINGS = [ + // TODO: part of delabsing move the toggle here - https://github.com/vector-im/element-web/issues/18088 + ]; + static KEYBINDINGS_SETTINGS = [ 'ctrlFForSearch', ]; @@ -242,6 +338,19 @@ export default class PreferencesUserSettingsTab extends React.Component<{}, ISta { this.renderGroup(PreferencesUserSettingsTab.SPACES_SETTINGS) }
    } +
    + { _t("Communities") } +

    { _t("Communities have been archived to make way for Spaces but you can convert your " + + "communities into Spaces below. Converting will ensure your conversations get the latest " + + "features.") }

    +
    + { _t("Show my Communities") } +

    { _t("If a community isn't shown you may not have permission to convert it.") }

    + +
    + { this.renderGroup(PreferencesUserSettingsTab.COMMUNITIES_SETTINGS) } +
    +
    { _t("Keyboard shortcuts") } diff --git a/src/components/views/spaces/SpaceBasicSettings.tsx b/src/components/views/spaces/SpaceBasicSettings.tsx index 9d3696c5a9..4f305edd8b 100644 --- a/src/components/views/spaces/SpaceBasicSettings.tsx +++ b/src/components/views/spaces/SpaceBasicSettings.tsx @@ -65,6 +65,7 @@ export const SpaceAvatar = ({ }} kind="link" className="mx_SpaceBasicSettings_avatar_remove" + aria-label={_t("Delete avatar")} > { _t("Delete") } @@ -72,7 +73,11 @@ export const SpaceAvatar = ({ } else { avatarSection =
    avatarUploadRef.current?.click()} /> - avatarUploadRef.current?.click()} kind="link"> + avatarUploadRef.current?.click()} + kind="link" + aria-label={_t("Upload avatar")} + > { _t("Upload") } ; diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx index 406028dbc7..33e4a990ef 100644 --- a/src/components/views/spaces/SpaceCreateMenu.tsx +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -18,22 +18,59 @@ import React, { ComponentProps, RefObject, SyntheticEvent, useContext, useRef, u import classNames from "classnames"; import { RoomType } from "matrix-js-sdk/src/@types/event"; import FocusLock from "react-focus-lock"; +import { HistoryVisibility, Preset } from "matrix-js-sdk/src/@types/partials"; +import { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests"; import { _t } from "../../../languageHandler"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { ChevronFace, ContextMenu } from "../../structures/ContextMenu"; -import createRoom from "../../../createRoom"; +import createRoom, { IOpts as ICreateOpts } from "../../../createRoom"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import SpaceBasicSettings, { SpaceAvatar } from "./SpaceBasicSettings"; import AccessibleButton from "../elements/AccessibleButton"; import Field from "../elements/Field"; import withValidation from "../elements/Validation"; -import { HistoryVisibility, Preset } from "matrix-js-sdk/src/@types/partials"; import RoomAliasField from "../elements/RoomAliasField"; import SdkConfig from "../../../SdkConfig"; import Modal from "../../../Modal"; import GenericFeatureFeedbackDialog from "../dialogs/GenericFeatureFeedbackDialog"; import SettingsStore from "../../../settings/SettingsStore"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import { Action } from "../../../dispatcher/actions"; +import { UserTab } from "../dialogs/UserSettingsDialog"; + +export const createSpace = async ( + name: string, + isPublic: boolean, + alias?: string, + topic?: string, + avatar?: string | File, + createOpts: Partial = {}, + otherOpts: Partial> = {}, +) => { + return createRoom({ + createOpts: { + name, + preset: isPublic ? Preset.PublicChat : Preset.PrivateChat, + power_level_content_override: { + // Only allow Admins to write to the timeline to prevent hidden sync spam + events_default: 100, + ...isPublic ? { invite: 0 } : {}, + }, + room_alias_name: isPublic && alias ? alias.substr(1, alias.indexOf(":") - 1) : undefined, + topic, + ...createOpts, + }, + avatar, + roomType: RoomType.Space, + historyVisibility: isPublic ? HistoryVisibility.WorldReadable : HistoryVisibility.Invited, + spinner: false, + encryption: false, + andView: true, + inlineErrors: true, + ...otherOpts, + }); +}; const SpaceCreateMenuType = ({ title, description, className, onClick }) => { return ( @@ -92,7 +129,7 @@ export const SpaceFeedbackPrompt = ({ onClick }: { onClick?: () => void }) => {
    ; }; -type BProps = Pick, "setAvatar" | "name" | "setName" | "topic" | "setTopic">; +type BProps = Omit, "nameDisabled" | "topicDisabled" | "avatarDisabled">; interface ISpaceCreateFormProps extends BProps { busy: boolean; alias: string; @@ -106,6 +143,7 @@ interface ISpaceCreateFormProps extends BProps { export const SpaceCreateForm: React.FC = ({ busy, onSubmit, + avatarUrl, setAvatar, name, setName, @@ -122,7 +160,7 @@ export const SpaceCreateForm: React.FC = ({ const domain = cli.getDomain(); return
    - + { } try { - await createRoom({ - createOpts: { - preset: visibility === Visibility.Public ? Preset.PublicChat : Preset.PrivateChat, - name, - power_level_content_override: { - // Only allow Admins to write to the timeline to prevent hidden sync spam - events_default: 100, - ...visibility === Visibility.Public ? { invite: 0 } : {}, - }, - room_alias_name: visibility === Visibility.Public && alias - ? alias.substr(1, alias.indexOf(":") - 1) - : undefined, - topic, - }, - avatar, - roomType: RoomType.Space, - historyVisibility: visibility === Visibility.Public - ? HistoryVisibility.WorldReadable - : HistoryVisibility.Invited, - spinner: false, - encryption: false, - andView: true, - inlineErrors: true, - }); + await createSpace(name, visibility === Visibility.Public, alias, topic, avatar); onFinished(); } catch (e) { @@ -233,10 +248,23 @@ const SpaceCreateMenu = ({ onFinished }) => { let body; if (visibility === null) { + const onCreateSpaceFromCommunityClick = () => { + defaultDispatcher.dispatch({ + action: Action.ViewUserSettings, + initialTabId: UserTab.Preferences, + }); + onFinished(); + }; + body =

    { _t("Create a space") }

    -

    { _t("Spaces are a new way to group rooms and people. " + - "To join an existing space you'll need an invite.") }

    +

    + { _t("Spaces are a new way to group rooms and people.") } +   + { _t("What kind of Space do you want to create?") } +   + { _t("You can change this later.") } +

    { onClick={() => setVisibility(Visibility.Private)} /> -

    { _t("You can change this later") }

    +

    + { _t("You can also create a Space from a community.", {}, { + a: sub => + { sub } + , + }) } +
    + { _t("To join an existing space you'll need an invite.") } +

    ; diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 58e1db4b1d..40016af36f 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -100,9 +100,12 @@ const HomeButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => { return SpaceStore.instance.allRoomsInHome; }); - return
  • + return
  • SpaceStore.instance.setActiveSpace(null)} @@ -142,9 +145,12 @@ const CreateSpaceButton = ({ openMenu(); }; - return
  • + return
  • {
      { (provided, snapshot) => ( diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index bb2184853e..399c137e97 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -77,11 +77,17 @@ export const SpaceButton: React.FC = ({ let notifBadge; if (notificationState) { + let ariaLabel = _t("Jump to first unread room."); + if (space?.getMyMembership() === "invite") { + ariaLabel = _t("Jump to first invite."); + } + notifBadge =
      SpaceStore.instance.setActiveRoomInSpace(space || null)} forceCount={false} notification={notificationState} + aria-label={ariaLabel} />
      ; } @@ -107,7 +113,6 @@ export const SpaceButton: React.FC = ({ onClick={onClick} onContextMenu={openMenu} forceHide={!isNarrow || menuDisplayed} - role="treeitem" inputRef={handle} > { children } @@ -284,7 +289,7 @@ export class SpaceItem extends React.PureComponent { /> : null; return ( -
    • +
    • { avatarSize={isNested ? 24 : 32} onClick={this.onClick} onKeyDown={this.onKeyDown} - aria-expanded={!collapsed} - ContextMenuComponent={this.props.space.getMyMembership() === "join" - ? SpaceContextMenu : undefined} + ContextMenuComponent={this.props.space.getMyMembership() === "join" ? SpaceContextMenu : undefined} > { toggleCollapseButton } @@ -322,7 +325,7 @@ const SpaceTreeLevel: React.FC = ({ isNested, parents, }) => { - return
        + return
          { spaces.map(s => { return ( Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,15 +27,7 @@ import { CallEvent, CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/we import classNames from 'classnames'; import AccessibleButton from '../elements/AccessibleButton'; import { isOnlyCtrlOrCmdKeyEvent, Key } from '../../../Keyboard'; -import { - alwaysAboveLeftOf, - alwaysAboveRightOf, - ChevronFace, - ContextMenuTooltipButton, -} from '../../structures/ContextMenu'; -import CallContextMenu from '../context_menus/CallContextMenu'; import { avatarUrlForMember } from '../../../Avatar'; -import DialpadContextMenu from '../context_menus/DialpadContextMenu'; import { CallFeed } from 'matrix-js-sdk/src/webrtc/callFeed'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import DesktopCapturerSourcePicker from "../elements/DesktopCapturerSourcePicker"; @@ -43,8 +35,7 @@ import Modal from '../../../Modal'; import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes'; import CallViewSidebar from './CallViewSidebar'; import CallViewHeader from './CallView/CallViewHeader'; -import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -import { Alignment } from "../elements/Tooltip"; +import CallViewButtons from "./CallView/CallViewButtons"; interface IProps { // The call for us to display @@ -83,8 +74,6 @@ interface IState { sidebarShown: boolean; } -const tooltipYOffset = -24; - function getFullScreenElement() { return ( document.fullscreenElement || @@ -113,23 +102,16 @@ function exitFullscreen() { if (exitMethod) exitMethod.call(document); } -const CONTROLS_HIDE_DELAY = 2000; -// Height of the header duplicated from CSS because we need to subtract it from our max -// height to get the max height of the video -const CONTEXT_MENU_VPADDING = 8; // How far the context menu sits above the button (px) - @replaceableComponent("views.voip.CallView") export default class CallView extends React.Component { private dispatcherRef: string; private contentRef = createRef(); - private controlsHideTimer: number = null; - private dialpadButton = createRef(); - private contextMenuButton = createRef(); + private buttonsRef = createRef(); constructor(props: IProps) { super(props); - const { primary, secondary } = this.getOrderedFeeds(this.props.call.getFeeds()); + const { primary, secondary } = CallView.getOrderedFeeds(this.props.call.getFeeds()); this.state = { isLocalOnHold: this.props.call.isLocalOnHold(), @@ -153,7 +135,6 @@ export default class CallView extends React.Component { public componentDidMount() { this.dispatcherRef = dis.register(this.onAction); document.addEventListener('keydown', this.onNativeKeyDown); - this.showControls(); } public componentWillUnmount() { @@ -166,7 +147,16 @@ export default class CallView extends React.Component { dis.unregister(this.dispatcherRef); } - public componentDidUpdate(prevProps) { + static getDerivedStateFromProps(props: IProps): Partial { + const { primary, secondary } = CallView.getOrderedFeeds(props.call.getFeeds()); + + return { + primaryFeed: primary, + secondaryFeeds: secondary, + }; + } + + public componentDidUpdate(prevProps: IProps): void { if (this.props.call === prevProps.call) return; this.setState({ @@ -220,7 +210,7 @@ export default class CallView extends React.Component { }; private onFeedsChanged = (newFeeds: Array) => { - const { primary, secondary } = this.getOrderedFeeds(newFeeds); + const { primary, secondary } = CallView.getOrderedFeeds(newFeeds); this.setState({ primaryFeed: primary, secondaryFeeds: secondary, @@ -241,19 +231,11 @@ export default class CallView extends React.Component { }); }; - private onControlsHideTimer = () => { - if (this.state.hoveringControls || this.state.showDialpad || this.state.showMoreMenu) return; - this.controlsHideTimer = null; - this.setState({ - controlsVisible: false, - }); - }; - private onMouseMove = () => { - this.showControls(); + this.buttonsRef.current?.showControls(); }; - private getOrderedFeeds(feeds: Array): { primary: CallFeed, secondary: Array } { + static getOrderedFeeds(feeds: Array): { primary: CallFeed, secondary: Array } { let primary; // Try to use a screensharing as primary, a remote one if possible @@ -276,29 +258,6 @@ export default class CallView extends React.Component { return { primary, secondary }; } - private showControls(): void { - if (this.state.showMoreMenu || this.state.showDialpad) return; - - if (!this.state.controlsVisible) { - this.setState({ - controlsVisible: true, - }); - } - if (this.controlsHideTimer !== null) { - clearTimeout(this.controlsHideTimer); - } - this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY); - } - - private onDialpadClick = (): void => { - if (!this.state.showDialpad) { - this.setState({ showDialpad: true }); - this.showControls(); - } else { - this.setState({ showDialpad: false }); - } - }; - private onMicMuteClick = (): void => { const newVal = !this.state.micMuted; @@ -329,19 +288,6 @@ export default class CallView extends React.Component { }); }; - private onMoreClick = (): void => { - this.setState({ showMoreMenu: true }); - this.showControls(); - }; - - private closeDialpad = (): void => { - this.setState({ showDialpad: false }); - }; - - private closeContextMenu = (): void => { - this.setState({ showMoreMenu: false }); - }; - // we register global shortcuts here, they *must not conflict* with local shortcuts elsewhere or both will fire // Note that this assumes we always have a CallView on screen at any given time // CallHandler would probably be a better place for this @@ -354,7 +300,7 @@ export default class CallView extends React.Component { if (ctrlCmdOnly) { this.onMicMuteClick(); // show the controls to give feedback - this.showControls(); + this.buttonsRef.current?.showControls(); handled = true; } break; @@ -363,7 +309,7 @@ export default class CallView extends React.Component { if (ctrlCmdOnly) { this.onVidMuteClick(); // show the controls to give feedback - this.showControls(); + this.buttonsRef.current?.showControls(); handled = true; } break; @@ -375,15 +321,6 @@ export default class CallView extends React.Component { } }; - private onCallControlsMouseEnter = (): void => { - this.setState({ hoveringControls: true }); - this.showControls(); - }; - - private onCallControlsMouseLeave = (): void => { - this.setState({ hoveringControls: false }); - }; - private onCallResumeClick = (): void => { const userFacingRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.call); CallHandler.sharedInstance().setActiveCallRoomId(userFacingRoomId); @@ -402,206 +339,60 @@ export default class CallView extends React.Component { }; private onToggleSidebar = (): void => { - this.setState({ - sidebarShown: !this.state.sidebarShown, - }); + this.setState({ sidebarShown: !this.state.sidebarShown }); }; private renderCallControls(): JSX.Element { - const micClasses = classNames({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_micOn: !this.state.micMuted, - mx_CallView_callControls_button_micOff: this.state.micMuted, - }); - - const vidClasses = classNames({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_vidOn: !this.state.vidMuted, - mx_CallView_callControls_button_vidOff: this.state.vidMuted, - }); - - const screensharingClasses = classNames({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_screensharingOn: this.state.screensharing, - mx_CallView_callControls_button_screensharingOff: !this.state.screensharing, - }); - - const sidebarButtonClasses = classNames({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_sidebarOn: this.state.sidebarShown, - mx_CallView_callControls_button_sidebarOff: !this.state.sidebarShown, - }); - - // Put the other states of the mic/video icons in the document to make sure they're cached - // (otherwise the icon disappears briefly when toggled) - const micCacheClasses = classNames({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_micOn: this.state.micMuted, - mx_CallView_callControls_button_micOff: !this.state.micMuted, - mx_CallView_callControls_button_invisible: true, - }); - - const vidCacheClasses = classNames({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_vidOn: this.state.micMuted, - mx_CallView_callControls_button_vidOff: !this.state.micMuted, - mx_CallView_callControls_button_invisible: true, - }); - - const callControlsClasses = classNames({ - mx_CallView_callControls: true, - mx_CallView_callControls_hidden: !this.state.controlsVisible, - }); - // We don't support call upgrades (yet) so hide the video mute button in voice calls - let vidMuteButton; - if (this.props.call.type === CallType.Video) { - vidMuteButton = ( - - ); - } - + const vidMuteButtonShown = this.props.call.type === CallType.Video; // Screensharing is possible, if we can send a second stream and // identify it using SDPStreamMetadata or if we can replace the already // existing usermedia track by a screensharing track. We also need to be // connected to know the state of the other side - let screensharingButton; - if ( + const screensharingButtonShown = ( (this.props.call.opponentSupportsSDPStreamMetadata() || this.props.call.type === CallType.Video) && this.props.call.state === CallState.Connected - ) { - screensharingButton = ( - - ); - } - + ); // To show the sidebar we need secondary feeds, if we don't have them, // we can hide this button. If we are in PiP, sidebar is also hidden, so // we can hide the button too - let sidebarButton; - if ( - !this.props.pipMode && - ( - this.state.primaryFeed?.purpose === SDPStreamMetadataPurpose.Screenshare || - this.props.call.isScreensharing() - ) - ) { - sidebarButton = ( - - ); - } - + const sidebarButtonShown = ( + this.state.primaryFeed?.purpose === SDPStreamMetadataPurpose.Screenshare || + this.props.call.isScreensharing() + ); // The dial pad & 'more' button actions are only relevant in a connected call - let contextMenuButton; - if (this.state.callState === CallState.Connected) { - contextMenuButton = ( - - ); - } - let dialpadButton; - if (this.state.callState === CallState.Connected && this.props.call.opponentSupportsDTMF()) { - dialpadButton = ( - - ); - } - - let dialPad; - if (this.state.showDialpad) { - dialPad = ; - } - - let contextMenu; - if (this.state.showMoreMenu) { - contextMenu = ; - } + const contextMenuButtonShown = this.state.callState === CallState.Connected; + const dialpadButtonShown = ( + this.state.callState === CallState.Connected && + this.props.call.opponentSupportsDTMF() + ); return ( -
          - { dialPad } - { contextMenu } - { dialpadButton } - - { vidMuteButton } -
          -
          - { screensharingButton } - { sidebarButton } - { contextMenuButton } - -
          + ); } diff --git a/src/components/views/voip/CallView/CallViewButtons.tsx b/src/components/views/voip/CallView/CallViewButtons.tsx new file mode 100644 index 0000000000..466311f421 --- /dev/null +++ b/src/components/views/voip/CallView/CallViewButtons.tsx @@ -0,0 +1,316 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2019 - 2021 The Matrix.org Foundation C.I.C. +Copyright 2021 Šimon Brandner + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, { createRef } from "react"; +import classNames from "classnames"; +import AccessibleTooltipButton from "../../elements/AccessibleTooltipButton"; +import CallContextMenu from "../../context_menus/CallContextMenu"; +import DialpadContextMenu from "../../context_menus/DialpadContextMenu"; +import { MatrixCall } from "matrix-js-sdk/src/webrtc/call"; +import { Alignment } from "../../elements/Tooltip"; +import { + alwaysAboveLeftOf, + alwaysAboveRightOf, + ChevronFace, + ContextMenuTooltipButton, +} from '../../../structures/ContextMenu'; +import { _t } from "../../../../languageHandler"; + +// Height of the header duplicated from CSS because we need to subtract it from our max +// height to get the max height of the video +const CONTEXT_MENU_VPADDING = 8; // How far the context menu sits above the button (px) + +const TOOLTIP_Y_OFFSET = -24; + +const CONTROLS_HIDE_DELAY = 2000; + +interface IProps { + call: MatrixCall; + pipMode: boolean; + handlers: { + onHangupClick: () => void; + onScreenshareClick: () => void; + onToggleSidebarClick: () => void; + onMicMuteClick: () => void; + onVidMuteClick: () => void; + }; + buttonsState: { + micMuted: boolean; + vidMuted: boolean; + sidebarShown: boolean; + screensharing: boolean; + }; + buttonsVisibility: { + screensharing: boolean; + vidMute: boolean; + sidebar: boolean; + dialpad: boolean; + contextMenu: boolean; + }; +} + +interface IState { + visible: boolean; + showDialpad: boolean; + hoveringControls: boolean; + showMoreMenu: boolean; +} + +export default class CallViewButtons extends React.Component { + private dialpadButton = createRef(); + private contextMenuButton = createRef(); + private controlsHideTimer: number = null; + + constructor(props: IProps) { + super(props); + + this.state = { + showDialpad: false, + hoveringControls: false, + showMoreMenu: false, + visible: true, + }; + } + + public componentDidMount(): void { + this.showControls(); + } + + public showControls(): void { + if (this.state.showMoreMenu || this.state.showDialpad) return; + + if (!this.state.visible) { + this.setState({ + visible: true, + }); + } + if (this.controlsHideTimer !== null) { + clearTimeout(this.controlsHideTimer); + } + this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY); + } + + private onControlsHideTimer = (): void => { + if (this.state.hoveringControls || this.state.showDialpad || this.state.showMoreMenu) return; + this.controlsHideTimer = null; + this.setState({ visible: false }); + }; + + private onMouseEnter = (): void => { + this.setState({ hoveringControls: true }); + }; + + private onMouseLeave = (): void => { + this.setState({ hoveringControls: false }); + }; + + private onDialpadClick = (): void => { + if (!this.state.showDialpad) { + this.setState({ showDialpad: true }); + this.showControls(); + } else { + this.setState({ showDialpad: false }); + } + }; + + private onMoreClick = (): void => { + this.setState({ showMoreMenu: true }); + this.showControls(); + }; + + private closeDialpad = (): void => { + this.setState({ showDialpad: false }); + }; + + private closeContextMenu = (): void => { + this.setState({ showMoreMenu: false }); + }; + + public render(): JSX.Element { + const micClasses = classNames("mx_CallViewButtons_button", { + mx_CallViewButtons_button_micOn: !this.props.buttonsState.micMuted, + mx_CallViewButtons_button_micOff: this.props.buttonsState.micMuted, + }); + + const vidClasses = classNames("mx_CallViewButtons_button", { + mx_CallViewButtons_button_vidOn: !this.props.buttonsState.vidMuted, + mx_CallViewButtons_button_vidOff: this.props.buttonsState.vidMuted, + }); + + const screensharingClasses = classNames("mx_CallViewButtons_button", { + mx_CallViewButtons_button_screensharingOn: this.props.buttonsState.screensharing, + mx_CallViewButtons_button_screensharingOff: !this.props.buttonsState.screensharing, + }); + + const sidebarButtonClasses = classNames("mx_CallViewButtons_button", { + mx_CallViewButtons_button_sidebarOn: this.props.buttonsState.sidebarShown, + mx_CallViewButtons_button_sidebarOff: !this.props.buttonsState.sidebarShown, + }); + + // Put the other states of the mic/video icons in the document to make sure they're cached + // (otherwise the icon disappears briefly when toggled) + const micCacheClasses = classNames("mx_CallViewButtons_button", "mx_CallViewButtons_button_invisible", { + mx_CallViewButtons_button_micOn: this.props.buttonsState.micMuted, + mx_CallViewButtons_button_micOff: !this.props.buttonsState.micMuted, + }); + + const vidCacheClasses = classNames("mx_CallViewButtons_button", "mx_CallViewButtons_button_invisible", { + mx_CallViewButtons_button_vidOn: this.props.buttonsState.micMuted, + mx_CallViewButtons_button_vidOff: !this.props.buttonsState.micMuted, + }); + + const callControlsClasses = classNames("mx_CallViewButtons", { + mx_CallViewButtons_hidden: !this.state.visible, + }); + + let vidMuteButton; + if (this.props.buttonsVisibility.vidMute) { + vidMuteButton = ( + + ); + } + + let screensharingButton; + if (this.props.buttonsVisibility.screensharing) { + screensharingButton = ( + + ); + } + + let sidebarButton; + if (this.props.buttonsVisibility.sidebar) { + sidebarButton = ( + + ); + } + + let contextMenuButton; + if (this.props.buttonsVisibility.contextMenu) { + contextMenuButton = ( + + ); + } + let dialpadButton; + if (this.props.buttonsVisibility.dialpad) { + dialpadButton = ( + + ); + } + + let dialPad; + if (this.state.showDialpad) { + dialPad = ; + } + + let contextMenu; + if (this.state.showMoreMenu) { + contextMenu = ; + } + + return ( +
          + { dialPad } + { contextMenu } + { dialpadButton } + + { vidMuteButton } +
          +
          + { screensharingButton } + { sidebarButton } + { contextMenuButton } + +
          + ); + } +} diff --git a/src/components/views/voip/DialPad.tsx b/src/components/views/voip/DialPad.tsx index 3b4a29b3f9..46584e0870 100644 --- a/src/components/views/voip/DialPad.tsx +++ b/src/components/views/voip/DialPad.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import * as React from "react"; -import AccessibleButton from "../elements/AccessibleButton"; +import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import { replaceableComponent } from "../../../utils/replaceableComponent"; const BUTTONS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#']; @@ -30,12 +30,12 @@ interface IButtonProps { kind: DialPadButtonKind; digit?: string; digitSubtext?: string; - onButtonPress: (string) => void; + onButtonPress: (digit: string, ev: ButtonEvent) => void; } class DialPadButton extends React.PureComponent { - onClick = () => { - this.props.onButtonPress(this.props.digit); + onClick = (ev: ButtonEvent) => { + this.props.onButtonPress(this.props.digit, ev); }; render() { @@ -54,10 +54,10 @@ class DialPadButton extends React.PureComponent { } interface IProps { - onDigitPress: (string) => void; + onDigitPress: (digit: string, ev: ButtonEvent) => void; hasDial: boolean; - onDeletePress?: (string) => void; - onDialPress?: (string) => void; + onDeletePress?: (ev: ButtonEvent) => void; + onDialPress?: () => void; } @replaceableComponent("views.voip.DialPad") diff --git a/src/components/views/voip/DialPadModal.tsx b/src/components/views/voip/DialPadModal.tsx index a36fc37dff..4d69260565 100644 --- a/src/components/views/voip/DialPadModal.tsx +++ b/src/components/views/voip/DialPadModal.tsx @@ -15,7 +15,8 @@ limitations under the License. */ import * as React from "react"; -import AccessibleButton from "../elements/AccessibleButton"; +import { createRef } from "react"; +import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import Field from "../elements/Field"; import DialPad from './DialPad'; import dis from '../../../dispatcher/dispatcher'; @@ -34,6 +35,8 @@ interface IState { @replaceableComponent("views.voip.DialPadModal") export default class DialpadModal extends React.PureComponent { + private numberEntryFieldRef: React.RefObject = createRef(); + constructor(props) { super(props); this.state = { @@ -54,13 +57,27 @@ export default class DialpadModal extends React.PureComponent { this.onDialPress(); }; - onDigitPress = (digit) => { + onDigitPress = (digit: string, ev: ButtonEvent) => { this.setState({ value: this.state.value + digit }); + + // Keep the number field focused so that keyboard entry is still available. + // However, don't focus if this wasn't the result of directly clicking on the button, + // i.e someone using keyboard navigation. + if (ev.type === "click") { + this.numberEntryFieldRef.current?.focus(); + } }; - onDeletePress = () => { + onDeletePress = (ev: ButtonEvent) => { if (this.state.value.length === 0) return; this.setState({ value: this.state.value.slice(0, -1) }); + + // Keep the number field focused so that keyboard entry is still available + // However, don't focus if this wasn't the result of directly clicking on the button, + // i.e someone using keyboard navigation. + if (ev.type === "click") { + this.numberEntryFieldRef.current?.focus(); + } }; onDialPress = async () => { @@ -82,6 +99,7 @@ export default class DialpadModal extends React.PureComponent { let dialPadField; if (this.state.value.length !== 0) { dialPadField = { />; } else { dialPadField = Autocomplete; export type UpdateQuery = (test: string) => Promise; export default class AutocompleteWrapperModel { - private queryPart: Part; private partIndex: number; constructor( @@ -43,80 +42,60 @@ export default class AutocompleteWrapperModel { ) { } - public onEscape(e: KeyboardEvent) { + public onEscape(e: KeyboardEvent): void { this.getAutocompleterComponent().onEscape(e); - this.updateCallback({ - replaceParts: [this.partCreator.plain(this.queryPart.text)], - close: true, - }); } - public close() { + public close(): void { this.updateCallback({ close: true }); } - public hasSelection() { + public hasSelection(): boolean { return this.getAutocompleterComponent().hasSelection(); } - public hasCompletions() { + public hasCompletions(): boolean { const ac = this.getAutocompleterComponent(); return ac && ac.countCompletions() > 0; } - public onEnter() { + public async confirmCompletion(): Promise { + await this.getAutocompleterComponent().onConfirmCompletion(); this.updateCallback({ close: true }); } /** * If there is no current autocompletion, start one and move to the first selection. */ - public async startSelection() { + public async startSelection(): Promise { const acComponent = this.getAutocompleterComponent(); if (acComponent.countCompletions() === 0) { // Force completions to show for the text currently entered await acComponent.forceComplete(); - // Select the first item by moving "down" - await acComponent.moveSelection(+1); } } - public selectPreviousSelection() { + public selectPreviousSelection(): void { this.getAutocompleterComponent().moveSelection(-1); } - public selectNextSelection() { + public selectNextSelection(): void { this.getAutocompleterComponent().moveSelection(+1); } - public onPartUpdate(part: Part, pos: DocumentPosition) { - // cache the typed value and caret here - // so we can restore it in onComponentSelectionChange when the value is undefined (meaning it should be the typed text) - this.queryPart = part; + public onPartUpdate(part: Part, pos: DocumentPosition): Promise { this.partIndex = pos.index; return this.updateQuery(part.text); } - public onComponentSelectionChange(completion: ICompletion) { - if (!completion) { - this.updateCallback({ - replaceParts: [this.queryPart], - }); - } else { - this.updateCallback({ - replaceParts: this.partForCompletion(completion), - }); - } - } - - public onComponentConfirm(completion: ICompletion) { + public onComponentConfirm(completion: ICompletion): void { this.updateCallback({ replaceParts: this.partForCompletion(completion), close: true, }); } - private partForCompletion(completion: ICompletion) { + private partForCompletion(completion: ICompletion): Part[] { const { completionId } = completion; const text = completion.completion; switch (completion.type) { diff --git a/src/editor/caret.ts b/src/editor/caret.ts index 67d10ddbb5..2b5035b567 100644 --- a/src/editor/caret.ts +++ b/src/editor/caret.ts @@ -19,7 +19,7 @@ import { needsCaretNodeBefore, needsCaretNodeAfter } from "./render"; import Range from "./range"; import EditorModel from "./model"; import DocumentPosition, { IPosition } from "./position"; -import { Part } from "./parts"; +import { Part, Type } from "./parts"; export type Caret = Range | DocumentPosition; @@ -113,7 +113,7 @@ function findNodeInLineForPart(parts: Part[], partIndex: number) { // to find newline parts for (let i = 0; i <= partIndex; ++i) { const part = parts[i]; - if (part.type === "newline") { + if (part.type === Type.Newline) { lineIndex += 1; nodeIndex = -1; prevPart = null; @@ -128,7 +128,7 @@ function findNodeInLineForPart(parts: Part[], partIndex: number) { // and not an adjacent caret node if (i < partIndex) { const nextPart = parts[i + 1]; - const isLastOfLine = !nextPart || nextPart.type === "newline"; + const isLastOfLine = !nextPart || nextPart.type === Type.Newline; if (needsCaretNodeAfter(part, isLastOfLine)) { nodeIndex += 1; } diff --git a/src/editor/deserialize.ts b/src/editor/deserialize.ts index 9033f99b6c..14be9f8a92 100644 --- a/src/editor/deserialize.ts +++ b/src/editor/deserialize.ts @@ -20,7 +20,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { walkDOMDepthFirst } from "./dom"; import { checkBlockNode } from "../HtmlUtils"; import { getPrimaryPermalinkEntity } from "../utils/permalinks/Permalinks"; -import { PartCreator } from "./parts"; +import { PartCreator, Type } from "./parts"; import SdkConfig from "../SdkConfig"; function parseAtRoomMentions(text: string, partCreator: PartCreator) { @@ -206,7 +206,7 @@ function prefixQuoteLines(isFirstNode, parts, partCreator) { parts.splice(0, 0, partCreator.plain(QUOTE_LINE_PREFIX)); } for (let i = 0; i < parts.length; i += 1) { - if (parts[i].type === "newline") { + if (parts[i].type === Type.Newline) { parts.splice(i + 1, 0, partCreator.plain(QUOTE_LINE_PREFIX)); i += 1; } diff --git a/src/editor/diff.ts b/src/editor/diff.ts index de8efc9c21..5cf94560ce 100644 --- a/src/editor/diff.ts +++ b/src/editor/diff.ts @@ -21,7 +21,7 @@ export interface IDiff { at?: number; } -function firstDiff(a: string, b: string) { +function firstDiff(a: string, b: string): number { const compareLen = Math.min(a.length, b.length); for (let i = 0; i < compareLen; ++i) { if (a[i] !== b[i]) { diff --git a/src/editor/history.ts b/src/editor/history.ts index 350ba6c99a..7764dbf682 100644 --- a/src/editor/history.ts +++ b/src/editor/history.ts @@ -36,7 +36,7 @@ export default class HistoryManager { private addedSinceLastPush = false; private removedSinceLastPush = false; - clear() { + public clear(): void { this.stack = []; this.newlyTypedCharCount = 0; this.currentIndex = -1; @@ -103,7 +103,7 @@ export default class HistoryManager { } // needs to persist parts and caret position - tryPush(model: EditorModel, caret: Caret, inputType: string, diff: IDiff) { + public tryPush(model: EditorModel, caret: Caret, inputType: string, diff: IDiff): boolean { // ignore state restoration echos. // these respect the inputType values of the input event, // but are actually passed in from MessageEditor calling model.reset() @@ -121,22 +121,22 @@ export default class HistoryManager { return shouldPush; } - ensureLastChangesPushed(model: EditorModel) { + public ensureLastChangesPushed(model: EditorModel): void { if (this.changedSinceLastPush) { this.pushState(model, this.lastCaret); } } - canUndo() { + public canUndo(): boolean { return this.currentIndex >= 1 || this.changedSinceLastPush; } - canRedo() { + public canRedo(): boolean { return this.currentIndex < (this.stack.length - 1); } // returns state that should be applied to model - undo(model: EditorModel) { + public undo(model: EditorModel): IHistory { if (this.canUndo()) { this.ensureLastChangesPushed(model); this.currentIndex -= 1; @@ -145,7 +145,7 @@ export default class HistoryManager { } // returns state that should be applied to model - redo() { + public redo(): IHistory { if (this.canRedo()) { this.changedSinceLastPush = false; this.currentIndex += 1; diff --git a/src/editor/model.ts b/src/editor/model.ts index da1c2f47f5..212a7d17c0 100644 --- a/src/editor/model.ts +++ b/src/editor/model.ts @@ -237,7 +237,7 @@ export default class EditorModel { } } } - // not _autoComplete, only there if active part is autocomplete part + // not autoComplete, only there if active part is autocomplete part if (this.autoComplete) { return this.autoComplete.onPartUpdate(part, pos); } diff --git a/src/editor/offset.ts b/src/editor/offset.ts index 413a22c71b..2e6e0ffe21 100644 --- a/src/editor/offset.ts +++ b/src/editor/offset.ts @@ -15,16 +15,17 @@ limitations under the License. */ import EditorModel from "./model"; +import DocumentPosition from "./position"; export default class DocumentOffset { constructor(public offset: number, public readonly atNodeEnd: boolean) { } - asPosition(model: EditorModel) { + public asPosition(model: EditorModel): DocumentPosition { return model.positionForOffset(this.offset, this.atNodeEnd); } - add(delta: number, atNodeEnd = false) { + public add(delta: number, atNodeEnd = false): DocumentOffset { return new DocumentOffset(this.offset + delta, atNodeEnd); } } diff --git a/src/editor/operations.ts b/src/editor/operations.ts index a738f2d111..2ff09ccce6 100644 --- a/src/editor/operations.ts +++ b/src/editor/operations.ts @@ -15,13 +15,13 @@ limitations under the License. */ import Range from "./range"; -import { Part } from "./parts"; +import { Part, Type } from "./parts"; /** * Some common queries and transformations on the editor model */ -export function replaceRangeAndExpandSelection(range: Range, newParts: Part[]) { +export function replaceRangeAndExpandSelection(range: Range, newParts: Part[]): void { const { model } = range; model.transform(() => { const oldLen = range.length; @@ -32,7 +32,7 @@ export function replaceRangeAndExpandSelection(range: Range, newParts: Part[]) { }); } -export function replaceRangeAndMoveCaret(range: Range, newParts: Part[]) { +export function replaceRangeAndMoveCaret(range: Range, newParts: Part[]): void { const { model } = range; model.transform(() => { const oldLen = range.length; @@ -43,29 +43,29 @@ export function replaceRangeAndMoveCaret(range: Range, newParts: Part[]) { }); } -export function rangeStartsAtBeginningOfLine(range: Range) { +export function rangeStartsAtBeginningOfLine(range: Range): boolean { const { model } = range; const startsWithPartial = range.start.offset !== 0; const isFirstPart = range.start.index === 0; - const previousIsNewline = !isFirstPart && model.parts[range.start.index - 1].type === "newline"; + const previousIsNewline = !isFirstPart && model.parts[range.start.index - 1].type === Type.Newline; return !startsWithPartial && (isFirstPart || previousIsNewline); } -export function rangeEndsAtEndOfLine(range: Range) { +export function rangeEndsAtEndOfLine(range: Range): boolean { const { model } = range; const lastPart = model.parts[range.end.index]; const endsWithPartial = range.end.offset !== lastPart.text.length; const isLastPart = range.end.index === model.parts.length - 1; - const nextIsNewline = !isLastPart && model.parts[range.end.index + 1].type === "newline"; + const nextIsNewline = !isLastPart && model.parts[range.end.index + 1].type === Type.Newline; return !endsWithPartial && (isLastPart || nextIsNewline); } -export function formatRangeAsQuote(range: Range) { +export function formatRangeAsQuote(range: Range): void { const { model, parts } = range; const { partCreator } = model; for (let i = 0; i < parts.length; ++i) { const part = parts[i]; - if (part.type === "newline") { + if (part.type === Type.Newline) { parts.splice(i + 1, 0, partCreator.plain("> ")); } } @@ -81,10 +81,10 @@ export function formatRangeAsQuote(range: Range) { replaceRangeAndExpandSelection(range, parts); } -export function formatRangeAsCode(range: Range) { +export function formatRangeAsCode(range: Range): void { const { model, parts } = range; const { partCreator } = model; - const needsBlock = parts.some(p => p.type === "newline"); + const needsBlock = parts.some(p => p.type === Type.Newline); if (needsBlock) { parts.unshift(partCreator.plain("```"), partCreator.newline()); if (!rangeStartsAtBeginningOfLine(range)) { @@ -105,9 +105,9 @@ export function formatRangeAsCode(range: Range) { // parts helper methods const isBlank = part => !part.text || !/\S/.test(part.text); -const isNL = part => part.type === "newline"; +const isNL = part => part.type === Type.Newline; -export function toggleInlineFormat(range: Range, prefix: string, suffix = prefix) { +export function toggleInlineFormat(range: Range, prefix: string, suffix = prefix): void { const { model, parts } = range; const { partCreator } = model; diff --git a/src/editor/parts.ts b/src/editor/parts.ts index 7bda8e1901..277b4bb526 100644 --- a/src/editor/parts.ts +++ b/src/editor/parts.ts @@ -25,6 +25,8 @@ import AutocompleteWrapperModel, { UpdateQuery, } from "./autocomplete"; import * as Avatar from "../Avatar"; +import defaultDispatcher from "../dispatcher/dispatcher"; +import { Action } from "../dispatcher/actions"; interface ISerializedPart { type: Type.Plain | Type.Newline | Type.Command | Type.PillCandidate; @@ -39,7 +41,7 @@ interface ISerializedPillPart { export type SerializedPart = ISerializedPart | ISerializedPillPart; -enum Type { +export enum Type { Plain = "plain", Newline = "newline", Command = "command", @@ -57,12 +59,12 @@ interface IBasePart { createAutoComplete(updateCallback: UpdateCallback): void; serialize(): SerializedPart; - remove(offset: number, len: number): string; + remove(offset: number, len: number): string | undefined; split(offset: number): IBasePart; validateAndInsert(offset: number, str: string, inputType: string): boolean; - appendUntilRejected(str: string, inputType: string): string; - updateDOMNode(node: Node); - canUpdateDOMNode(node: Node); + appendUntilRejected(str: string, inputType: string): string | undefined; + updateDOMNode(node: Node): void; + canUpdateDOMNode(node: Node): boolean; toDOMNode(): Node; } @@ -85,19 +87,19 @@ abstract class BasePart { this._text = text; } - acceptsInsertion(chr: string, offset: number, inputType: string) { + protected acceptsInsertion(chr: string, offset: number, inputType: string): boolean { return true; } - acceptsRemoval(position: number, chr: string) { + protected acceptsRemoval(position: number, chr: string): boolean { return true; } - merge(part: Part) { + public merge(part: Part): boolean { return false; } - split(offset: number) { + public split(offset: number): IBasePart { const splitText = this.text.substr(offset); this._text = this.text.substr(0, offset); return new PlainPart(splitText); @@ -105,7 +107,7 @@ abstract class BasePart { // removes len chars, or returns the plain text this part should be replaced with // if the part would become invalid if it removed everything. - remove(offset: number, len: number) { + public remove(offset: number, len: number): string | undefined { // validate const strWithRemoval = this.text.substr(0, offset) + this.text.substr(offset + len); for (let i = offset; i < (len + offset); ++i) { @@ -118,7 +120,7 @@ abstract class BasePart { } // append str, returns the remaining string if a character was rejected. - appendUntilRejected(str: string, inputType: string) { + public appendUntilRejected(str: string, inputType: string): string | undefined { const offset = this.text.length; for (let i = 0; i < str.length; ++i) { const chr = str.charAt(i); @@ -132,7 +134,7 @@ abstract class BasePart { // inserts str at offset if all the characters in str were accepted, otherwise don't do anything // return whether the str was accepted or not. - validateAndInsert(offset: number, str: string, inputType: string) { + public validateAndInsert(offset: number, str: string, inputType: string): boolean { for (let i = 0; i < str.length; ++i) { const chr = str.charAt(i); if (!this.acceptsInsertion(chr, offset + i, inputType)) { @@ -145,42 +147,42 @@ abstract class BasePart { return true; } - createAutoComplete(updateCallback: UpdateCallback): void {} + public createAutoComplete(updateCallback: UpdateCallback): void {} - trim(len: number) { + protected trim(len: number): string { const remaining = this._text.substr(len); this._text = this._text.substr(0, len); return remaining; } - get text() { + public get text(): string { return this._text; } - abstract get type(): Type; + public abstract get type(): Type; - get canEdit() { + public get canEdit(): boolean { return true; } - toString() { + public toString(): string { return `${this.type}(${this.text})`; } - serialize(): SerializedPart { + public serialize(): SerializedPart { return { type: this.type as ISerializedPart["type"], text: this.text, }; } - abstract updateDOMNode(node: Node); - abstract canUpdateDOMNode(node: Node); - abstract toDOMNode(): Node; + public abstract updateDOMNode(node: Node): void; + public abstract canUpdateDOMNode(node: Node): boolean; + public abstract toDOMNode(): Node; } abstract class PlainBasePart extends BasePart { - acceptsInsertion(chr: string, offset: number, inputType: string) { + protected acceptsInsertion(chr: string, offset: number, inputType: string): boolean { if (chr === "\n") { return false; } @@ -203,11 +205,11 @@ abstract class PlainBasePart extends BasePart { return true; } - toDOMNode() { + public toDOMNode(): Node { return document.createTextNode(this.text); } - merge(part) { + public merge(part): boolean { if (part.type === this.type) { this._text = this.text + part.text; return true; @@ -215,47 +217,49 @@ abstract class PlainBasePart extends BasePart { return false; } - updateDOMNode(node: Node) { + public updateDOMNode(node: Node): void { if (node.textContent !== this.text) { node.textContent = this.text; } } - canUpdateDOMNode(node: Node) { + public canUpdateDOMNode(node: Node): boolean { return node.nodeType === Node.TEXT_NODE; } } // exported for unit tests, should otherwise only be used through PartCreator export class PlainPart extends PlainBasePart implements IBasePart { - get type(): IBasePart["type"] { + public get type(): IBasePart["type"] { return Type.Plain; } } -abstract class PillPart extends BasePart implements IPillPart { +export abstract class PillPart extends BasePart implements IPillPart { constructor(public resourceId: string, label) { super(label); } - acceptsInsertion(chr: string) { + protected acceptsInsertion(chr: string): boolean { return chr !== " "; } - acceptsRemoval(position: number, chr: string) { + protected acceptsRemoval(position: number, chr: string): boolean { return position !== 0; //if you remove initial # or @, pill should become plain } - toDOMNode() { + public toDOMNode(): Node { const container = document.createElement("span"); container.setAttribute("spellcheck", "false"); + container.setAttribute("contentEditable", "false"); + container.onclick = this.onClick; container.className = this.className; container.appendChild(document.createTextNode(this.text)); this.setAvatar(container); return container; } - updateDOMNode(node: HTMLElement) { + public updateDOMNode(node: HTMLElement): void { const textNode = node.childNodes[0]; if (textNode.textContent !== this.text) { textNode.textContent = this.text; @@ -263,10 +267,13 @@ abstract class PillPart extends BasePart implements IPillPart { if (node.className !== this.className) { node.className = this.className; } + if (node.onclick !== this.onClick) { + node.onclick = this.onClick; + } this.setAvatar(node); } - canUpdateDOMNode(node: HTMLElement) { + public canUpdateDOMNode(node: HTMLElement): boolean { return node.nodeType === Node.ELEMENT_NODE && node.nodeName === "SPAN" && node.childNodes.length === 1 && @@ -274,7 +281,7 @@ abstract class PillPart extends BasePart implements IPillPart { } // helper method for subclasses - protected setAvatarVars(node: HTMLElement, avatarUrl: string, initialLetter: string) { + protected setAvatarVars(node: HTMLElement, avatarUrl: string, initialLetter: string): void { const avatarBackground = `url('${avatarUrl}')`; const avatarLetter = `'${initialLetter}'`; // check if the value is changing, @@ -287,7 +294,7 @@ abstract class PillPart extends BasePart implements IPillPart { } } - serialize(): ISerializedPillPart { + public serialize(): ISerializedPillPart { return { type: this.type, text: this.text, @@ -295,41 +302,43 @@ abstract class PillPart extends BasePart implements IPillPart { }; } - get canEdit() { + public get canEdit(): boolean { return false; } - abstract get type(): IPillPart["type"]; + public abstract get type(): IPillPart["type"]; - abstract get className(): string; + protected abstract get className(): string; - abstract setAvatar(node: HTMLElement): void; + protected onClick?: () => void; + + protected abstract setAvatar(node: HTMLElement): void; } class NewlinePart extends BasePart implements IBasePart { - acceptsInsertion(chr: string, offset: number) { + protected acceptsInsertion(chr: string, offset: number): boolean { return offset === 0 && chr === "\n"; } - acceptsRemoval(position: number, chr: string) { + protected acceptsRemoval(position: number, chr: string): boolean { return true; } - toDOMNode() { + public toDOMNode(): Node { return document.createElement("br"); } - merge() { + public merge(): boolean { return false; } - updateDOMNode() {} + public updateDOMNode(): void {} - canUpdateDOMNode(node: HTMLElement) { + public canUpdateDOMNode(node: HTMLElement): boolean { return node.tagName === "BR"; } - get type(): IBasePart["type"] { + public get type(): IBasePart["type"] { return Type.Newline; } @@ -337,7 +346,7 @@ class NewlinePart extends BasePart implements IBasePart { // rather than trying to append to it, which is what we want. // As a newline can also be only one character, it makes sense // as it can only be one character long. This caused #9741. - get canEdit() { + public get canEdit(): boolean { return false; } } @@ -347,7 +356,7 @@ class RoomPillPart extends PillPart { super(resourceId, label); } - setAvatar(node: HTMLElement) { + protected setAvatar(node: HTMLElement): void { let initialLetter = ""; let avatarUrl = Avatar.avatarUrlForRoom(this.room, 16, 16, "crop"); if (!avatarUrl) { @@ -357,11 +366,11 @@ class RoomPillPart extends PillPart { this.setAvatarVars(node, avatarUrl, initialLetter); } - get type(): IPillPart["type"] { + public get type(): IPillPart["type"] { return Type.RoomPill; } - get className() { + protected get className() { return "mx_RoomPill mx_Pill"; } } @@ -371,11 +380,11 @@ class AtRoomPillPart extends RoomPillPart { super(text, text, room); } - get type(): IPillPart["type"] { + public get type(): IPillPart["type"] { return Type.AtRoomPill; } - serialize(): ISerializedPillPart { + public serialize(): ISerializedPillPart { return { type: this.type, text: this.text, @@ -388,7 +397,15 @@ class UserPillPart extends PillPart { super(userId, displayName); } - setAvatar(node: HTMLElement) { + public get type(): IPillPart["type"] { + return Type.UserPill; + } + + protected get className() { + return "mx_UserPill mx_Pill"; + } + + protected setAvatar(node: HTMLElement): void { if (!this.member) { return; } @@ -402,13 +419,12 @@ class UserPillPart extends PillPart { this.setAvatarVars(node, avatarUrl, initialLetter); } - get type(): IPillPart["type"] { - return Type.UserPill; - } - - get className() { - return "mx_UserPill mx_Pill"; - } + protected onClick = (): void => { + defaultDispatcher.dispatch({ + action: Action.ViewUser, + member: this.member, + }); + }; } class PillCandidatePart extends PlainBasePart implements IPillCandidatePart { @@ -416,11 +432,11 @@ class PillCandidatePart extends PlainBasePart implements IPillCandidatePart { super(text); } - createAutoComplete(updateCallback: UpdateCallback): AutocompleteWrapperModel { + public createAutoComplete(updateCallback: UpdateCallback): AutocompleteWrapperModel { return this.autoCompleteCreator.create(updateCallback); } - acceptsInsertion(chr: string, offset: number, inputType: string) { + protected acceptsInsertion(chr: string, offset: number, inputType: string): boolean { if (offset === 0) { return true; } else { @@ -428,11 +444,11 @@ class PillCandidatePart extends PlainBasePart implements IPillCandidatePart { } } - merge() { + public merge(): boolean { return false; } - acceptsRemoval(position: number, chr: string) { + protected acceptsRemoval(position: number, chr: string): boolean { return true; } @@ -463,17 +479,21 @@ interface IAutocompleteCreator { export class PartCreator { protected readonly autoCompleteCreator: IAutocompleteCreator; - constructor(private room: Room, private client: MatrixClient, autoCompleteCreator: AutoCompleteCreator = null) { + constructor( + private readonly room: Room, + private readonly client: MatrixClient, + autoCompleteCreator: AutoCompleteCreator = null, + ) { // pre-create the creator as an object even without callback so it can already be passed // to PillCandidatePart (e.g. while deserializing) and set later on - this.autoCompleteCreator = { create: autoCompleteCreator && autoCompleteCreator(this) }; + this.autoCompleteCreator = { create: autoCompleteCreator?.(this) }; } - setAutoCompleteCreator(autoCompleteCreator: AutoCompleteCreator) { + public setAutoCompleteCreator(autoCompleteCreator: AutoCompleteCreator): void { this.autoCompleteCreator.create = autoCompleteCreator(this); } - createPartForInput(input: string, partIndex: number, inputType?: string): Part { + public createPartForInput(input: string, partIndex: number, inputType?: string): Part { switch (input[0]) { case "#": case "@": @@ -487,11 +507,11 @@ export class PartCreator { } } - createDefaultPart(text: string) { + public createDefaultPart(text: string): Part { return this.plain(text); } - deserializePart(part: SerializedPart): Part { + public deserializePart(part: SerializedPart): Part { switch (part.type) { case Type.Plain: return this.plain(part.text); @@ -508,19 +528,19 @@ export class PartCreator { } } - plain(text: string) { + public plain(text: string): PlainPart { return new PlainPart(text); } - newline() { + public newline(): NewlinePart { return new NewlinePart("\n"); } - pillCandidate(text: string) { + public pillCandidate(text: string): PillCandidatePart { return new PillCandidatePart(text, this.autoCompleteCreator); } - roomPill(alias: string, roomId?: string) { + public roomPill(alias: string, roomId?: string): RoomPillPart { let room; if (roomId || alias[0] !== "#") { room = this.client.getRoom(roomId || alias); @@ -533,16 +553,20 @@ export class PartCreator { return new RoomPillPart(alias, room ? room.name : alias, room); } - atRoomPill(text: string) { + public atRoomPill(text: string): AtRoomPillPart { return new AtRoomPillPart(text, this.room); } - userPill(displayName: string, userId: string) { + public userPill(displayName: string, userId: string): UserPillPart { const member = this.room.getMember(userId); return new UserPillPart(userId, displayName, member); } - createMentionParts(insertTrailingCharacter: boolean, displayName: string, userId: string) { + public createMentionParts( + insertTrailingCharacter: boolean, + displayName: string, + userId: string, + ): [UserPillPart, PlainPart] { const pill = this.userPill(displayName, userId); const postfix = this.plain(insertTrailingCharacter ? ": " : " "); return [pill, postfix]; @@ -567,7 +591,7 @@ export class CommandPartCreator extends PartCreator { } public deserializePart(part: SerializedPart): Part { - if (part.type === "command") { + if (part.type === Type.Command) { return this.command(part.text); } else { return super.deserializePart(part); diff --git a/src/editor/position.ts b/src/editor/position.ts index 37d2a07b43..50dc283eb3 100644 --- a/src/editor/position.ts +++ b/src/editor/position.ts @@ -30,7 +30,7 @@ export default class DocumentPosition implements IPosition { constructor(public readonly index: number, public readonly offset: number) { } - compare(otherPos: DocumentPosition) { + public compare(otherPos: DocumentPosition): number { if (this.index === otherPos.index) { return this.offset - otherPos.offset; } else { @@ -38,7 +38,7 @@ export default class DocumentPosition implements IPosition { } } - iteratePartsBetween(other: DocumentPosition, model: EditorModel, callback: Callback) { + public iteratePartsBetween(other: DocumentPosition, model: EditorModel, callback: Callback): void { if (this.index === -1 || other.index === -1) { return; } @@ -57,7 +57,7 @@ export default class DocumentPosition implements IPosition { } } - forwardsWhile(model: EditorModel, predicate: Predicate) { + public forwardsWhile(model: EditorModel, predicate: Predicate): DocumentPosition { if (this.index === -1) { return this; } @@ -82,7 +82,7 @@ export default class DocumentPosition implements IPosition { } } - backwardsWhile(model: EditorModel, predicate: Predicate) { + public backwardsWhile(model: EditorModel, predicate: Predicate): DocumentPosition { if (this.index === -1) { return this; } @@ -107,7 +107,7 @@ export default class DocumentPosition implements IPosition { } } - asOffset(model: EditorModel) { + public asOffset(model: EditorModel): DocumentOffset { if (this.index === -1) { return new DocumentOffset(0, true); } @@ -121,7 +121,7 @@ export default class DocumentPosition implements IPosition { return new DocumentOffset(offset, atEnd); } - isAtEnd(model: EditorModel) { + public isAtEnd(model: EditorModel): boolean { if (model.parts.length === 0) { return true; } @@ -130,7 +130,7 @@ export default class DocumentPosition implements IPosition { return this.index === lastPartIdx && this.offset === lastPart.text.length; } - isAtStart() { + public isAtStart(): boolean { return this.index === 0 && this.offset === 0; } } diff --git a/src/editor/range.ts b/src/editor/range.ts index 634805702f..13776177a7 100644 --- a/src/editor/range.ts +++ b/src/editor/range.ts @@ -32,23 +32,23 @@ export default class Range { this._end = bIsLarger ? positionB : positionA; } - moveStart(delta: number) { + public moveStart(delta: number): void { this._start = this._start.forwardsWhile(this.model, () => { delta -= 1; return delta >= 0; }); } - trim() { + public trim(): void { this._start = this._start.forwardsWhile(this.model, whitespacePredicate); this._end = this._end.backwardsWhile(this.model, whitespacePredicate); } - expandBackwardsWhile(predicate: Predicate) { + public expandBackwardsWhile(predicate: Predicate): void { this._start = this._start.backwardsWhile(this.model, predicate); } - get text() { + public get text(): string { let text = ""; this._start.iteratePartsBetween(this._end, this.model, (part, startIdx, endIdx) => { const t = part.text.substring(startIdx, endIdx); @@ -63,7 +63,7 @@ export default class Range { * @param {Part[]} parts the parts to replace the range with * @return {Number} the net amount of characters added, can be negative. */ - replace(parts: Part[]) { + public replace(parts: Part[]): number { const newLength = parts.reduce((sum, part) => sum + part.text.length, 0); let oldLength = 0; this._start.iteratePartsBetween(this._end, this.model, (part, startIdx, endIdx) => { @@ -77,8 +77,8 @@ export default class Range { * Returns a copy of the (partial) parts within the range. * For partial parts, only the text is adjusted to the part that intersects with the range. */ - get parts() { - const parts = []; + public get parts(): Part[] { + const parts: Part[] = []; this._start.iteratePartsBetween(this._end, this.model, (part, startIdx, endIdx) => { const serializedPart = part.serialize(); serializedPart.text = part.text.substring(startIdx, endIdx); @@ -88,7 +88,7 @@ export default class Range { return parts; } - get length() { + public get length(): number { let len = 0; this._start.iteratePartsBetween(this._end, this.model, (part, startIdx, endIdx) => { len += endIdx - startIdx; @@ -96,11 +96,11 @@ export default class Range { return len; } - get start() { + public get start(): DocumentPosition { return this._start; } - get end() { + public get end(): DocumentPosition { return this._end; } } diff --git a/src/editor/render.ts b/src/editor/render.ts index 0e0b7d2145..d9997de855 100644 --- a/src/editor/render.ts +++ b/src/editor/render.ts @@ -15,19 +15,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Part } from "./parts"; +import { Part, Type } from "./parts"; import EditorModel from "./model"; -export function needsCaretNodeBefore(part: Part, prevPart: Part) { - const isFirst = !prevPart || prevPart.type === "newline"; +export function needsCaretNodeBefore(part: Part, prevPart: Part): boolean { + const isFirst = !prevPart || prevPart.type === Type.Newline; return !part.canEdit && (isFirst || !prevPart.canEdit); } -export function needsCaretNodeAfter(part: Part, isLastOfLine: boolean) { +export function needsCaretNodeAfter(part: Part, isLastOfLine: boolean): boolean { return !part.canEdit && isLastOfLine; } -function insertAfter(node: HTMLElement, nodeToInsert: HTMLElement) { +function insertAfter(node: HTMLElement, nodeToInsert: HTMLElement): void { const next = node.nextSibling; if (next) { node.parentElement.insertBefore(nodeToInsert, next); @@ -44,25 +44,25 @@ export const CARET_NODE_CHAR = "\ufeff"; // a caret node is a node that allows the caret to be placed // where otherwise it wouldn't be possible // (e.g. next to a pill span without adjacent text node) -function createCaretNode() { +function createCaretNode(): HTMLElement { const span = document.createElement("span"); span.className = "caretNode"; span.appendChild(document.createTextNode(CARET_NODE_CHAR)); return span; } -function updateCaretNode(node: HTMLElement) { +function updateCaretNode(node: HTMLElement): void { // ensure the caret node contains only a zero-width space if (node.textContent !== CARET_NODE_CHAR) { node.textContent = CARET_NODE_CHAR; } } -export function isCaretNode(node: HTMLElement) { +export function isCaretNode(node: HTMLElement): boolean { return node && node.tagName === "SPAN" && node.className === "caretNode"; } -function removeNextSiblings(node: ChildNode) { +function removeNextSiblings(node: ChildNode): void { if (!node) { return; } @@ -74,7 +74,7 @@ function removeNextSiblings(node: ChildNode) { } } -function removeChildren(parent: HTMLElement) { +function removeChildren(parent: HTMLElement): void { const firstChild = parent.firstChild; if (firstChild) { removeNextSiblings(firstChild); @@ -82,7 +82,7 @@ function removeChildren(parent: HTMLElement) { } } -function reconcileLine(lineContainer: ChildNode, parts: Part[]) { +function reconcileLine(lineContainer: ChildNode, parts: Part[]): void { let currentNode; let prevPart; const lastPart = parts[parts.length - 1]; @@ -131,13 +131,13 @@ function reconcileLine(lineContainer: ChildNode, parts: Part[]) { removeNextSiblings(currentNode); } -function reconcileEmptyLine(lineContainer) { +function reconcileEmptyLine(lineContainer: HTMLElement): void { // empty div needs to have a BR in it to give it height let foundBR = false; let partNode = lineContainer.firstChild; while (partNode) { const nextNode = partNode.nextSibling; - if (!foundBR && partNode.tagName === "BR") { + if (!foundBR && (partNode as HTMLElement).tagName === "BR") { foundBR = true; } else { partNode.remove(); @@ -149,9 +149,9 @@ function reconcileEmptyLine(lineContainer) { } } -export function renderModel(editor: HTMLDivElement, model: EditorModel) { +export function renderModel(editor: HTMLDivElement, model: EditorModel): void { const lines = model.parts.reduce((linesArr, part) => { - if (part.type === "newline") { + if (part.type === Type.Newline) { linesArr.push([]); } else { const lastLine = linesArr[linesArr.length - 1]; @@ -175,7 +175,7 @@ export function renderModel(editor: HTMLDivElement, model: EditorModel) { if (parts.length) { reconcileLine(lineContainer, parts); } else { - reconcileEmptyLine(lineContainer); + reconcileEmptyLine(lineContainer as HTMLElement); } }); if (lines.length) { diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index f68173ae29..38a73cc945 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -22,30 +22,31 @@ import { AllHtmlEntities } from 'html-entities'; import SettingsStore from '../settings/SettingsStore'; import SdkConfig from '../SdkConfig'; import cheerio from 'cheerio'; +import { Type } from './parts'; -export function mdSerialize(model: EditorModel) { +export function mdSerialize(model: EditorModel): string { return model.parts.reduce((html, part) => { switch (part.type) { - case "newline": + case Type.Newline: return html + "\n"; - case "plain": - case "command": - case "pill-candidate": - case "at-room-pill": + case Type.Plain: + case Type.Command: + case Type.PillCandidate: + case Type.AtRoomPill: return html + part.text; - case "room-pill": + case Type.RoomPill: // Here we use the resourceId for compatibility with non-rich text clients // See https://github.com/vector-im/element-web/issues/16660 return html + `[${part.resourceId.replace(/[[\\\]]/g, c => "\\" + c)}](${makeGenericPermalink(part.resourceId)})`; - case "user-pill": + case Type.UserPill: return html + `[${part.text.replace(/[[\\\]]/g, c => "\\" + c)}](${makeGenericPermalink(part.resourceId)})`; } }, ""); } -export function htmlSerializeIfNeeded(model: EditorModel, { forceHTML = false } = {}) { +export function htmlSerializeIfNeeded(model: EditorModel, { forceHTML = false } = {}): string { let md = mdSerialize(model); // copy of raw input to remove unwanted math later const orig = md; @@ -156,31 +157,31 @@ export function htmlSerializeIfNeeded(model: EditorModel, { forceHTML = false } } } -export function textSerialize(model: EditorModel) { +export function textSerialize(model: EditorModel): string { return model.parts.reduce((text, part) => { switch (part.type) { - case "newline": + case Type.Newline: return text + "\n"; - case "plain": - case "command": - case "pill-candidate": - case "at-room-pill": + case Type.Plain: + case Type.Command: + case Type.PillCandidate: + case Type.AtRoomPill: return text + part.text; - case "room-pill": + case Type.RoomPill: // Here we use the resourceId for compatibility with non-rich text clients // See https://github.com/vector-im/element-web/issues/16660 return text + `${part.resourceId}`; - case "user-pill": + case Type.UserPill: return text + `${part.text}`; } }, ""); } -export function containsEmote(model: EditorModel) { +export function containsEmote(model: EditorModel): boolean { return startsWith(model, "/me ", false); } -export function startsWith(model: EditorModel, prefix: string, caseSensitive = true) { +export function startsWith(model: EditorModel, prefix: string, caseSensitive = true): boolean { const firstPart = model.parts[0]; // part type will be "plain" while editing, // and "command" while composing a message. @@ -190,26 +191,26 @@ export function startsWith(model: EditorModel, prefix: string, caseSensitive = t text = text.toLowerCase(); } - return firstPart && (firstPart.type === "plain" || firstPart.type === "command") && text.startsWith(prefix); + return firstPart && (firstPart.type === Type.Plain || firstPart.type === Type.Command) && text.startsWith(prefix); } -export function stripEmoteCommand(model: EditorModel) { +export function stripEmoteCommand(model: EditorModel): EditorModel { // trim "/me " return stripPrefix(model, "/me "); } -export function stripPrefix(model: EditorModel, prefix: string) { +export function stripPrefix(model: EditorModel, prefix: string): EditorModel { model = model.clone(); model.removeText({ index: 0, offset: 0 }, prefix.length); return model; } -export function unescapeMessage(model: EditorModel) { +export function unescapeMessage(model: EditorModel): EditorModel { const { parts } = model; if (parts.length) { const firstPart = parts[0]; // only unescape \/ to / at start of editor - if (firstPart.type === "plain" && firstPart.text.startsWith("\\/")) { + if (firstPart.type === Type.Plain && firstPart.text.startsWith("\\/")) { model = model.clone(); model.removeText({ index: 0, offset: 0 }, 1); } diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index 940ab1a79a..04956ba68c 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -4,7 +4,7 @@ "Filter room members": "Najít člena místnosti", "Historical": "Historické", "Home": "Úvod", - "Jump to first unread message.": "Přeskočit na první nepřečtenou zprávu.", + "Jump to first unread message.": "Přejít na první nepřečtenou zprávu.", "Logout": "Odhlásit se", "Low priority": "Nízká priorita", "Notifications": "Oznámení", @@ -13,7 +13,7 @@ "Settings": "Nastavení", "This room": "Tato místnost", "Video call": "Videohovor", - "Voice call": "Telefonát", + "Voice call": "Hlasový hovor", "Sun": "Ne", "Mon": "Po", "Tue": "Út", @@ -346,7 +346,7 @@ "example": "příklad", "Create": "Vytvořit", "Please select the destination room for this message": "Vyberte prosím pro tuto zprávu cílovou místnost", - "Jump to read receipt": "Skočit na poslední potvrzení o přečtení", + "Jump to read receipt": "Přejít na poslední potvrzení o přečtení", "Invite": "Pozvat", "and %(count)s others...|one": "a někdo další...", "Hangup": "Zavěsit", @@ -765,7 +765,7 @@ "Share Link to User": "Sdílet odkaz na uživatele", "Send an encrypted reply…": "Odeslat šifrovanou odpověď …", "Send an encrypted message…": "Odeslat šifrovanou zprávu…", - "Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "%(displayName)s (%(userName)s) viděl %(dateTime)s", + "Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "%(displayName)s (%(userName)s) viděl(a) %(dateTime)s", "Replying": "Odpovídá", "Share room": "Sdílet místnost", "System Alerts": "Systémová varování", @@ -1080,7 +1080,7 @@ "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Nepovedlo se najít profily následujících Matrix ID - chcete je stejně pozvat?", "Invite anyway and never warn me again": "Stejně je pozvat a nikdy mě nevarujte znovu", "Invite anyway": "Stejně je pozvat", - "Before submitting logs, you must create a GitHub issue to describe your problem.": "Pro odeslání záznamů je potřeba vyrobit issue na GitHubu s popisem problému.", + "Before submitting logs, you must create a GitHub issue to describe your problem.": "Pro odeslání záznamů je potřeba vytvořit issue na GitHubu s popisem problému.", "Unable to load commit detail: %(msg)s": "Nepovedlo se načíst detaily revize: %(msg)s", "Incompatible Database": "Nekompatibilní databáze", "Continue With Encryption Disabled": "Pokračovat bez šifrování", @@ -1562,8 +1562,8 @@ "Find a room… (e.g. %(exampleRoom)s)": "Najít místnost… (např. %(exampleRoom)s)", "If you can't find the room you're looking for, ask for an invite or Create a new room.": "Pokud nemůžete nějakou místnost najít, požádejte stávající členy o pozvánku nebo si Vytvořte novou místnost.", "Explore rooms": "Procházet místnosti", - "Jump to first unread room.": "Skočit na první nepřečtenou místnost.", - "Jump to first invite.": "Skočit na první pozvánku.", + "Jump to first unread room.": "Přejít na první nepřečtenou místnost.", + "Jump to first invite.": "Přejít na první pozvánku.", "No identity server is configured: add one in server settings to reset your password.": "Žádný server identit není nakonfigurován: přidejte si ho v nastavení, abyste mohli obnovit heslo.", "This account has been deactivated.": "Tento účet byl deaktivován.", "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "nový účet (%(newAccountId)s) je registrován, ale už jste přihlášeni pod jiným účtem (%(loggedInUserId)s).", @@ -1917,7 +1917,7 @@ "Compare unique emoji": "Porovnejte jedinečnou kombinaci emoji", "Compare a unique set of emoji if you don't have a camera on either device": "Pokud na žádném zařízení nemáte kameru, porovnejte jedinečnou kombinaci emoji", "Not Trusted": "Nedůvěryhodné", - "%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s (%(userId)s) se přihlásil do nové relace bez ověření:", + "%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s (%(userId)s) se přihlásil(a) do nové relace bez ověření:", "Ask this user to verify their session, or manually verify it below.": "Požádejte tohoto uživatele, aby ověřil svou relaci, nebo jí níže můžete ověřit manuálně.", "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.": "Relace, kterou se snažíte ověřit, neumožňuje ověření QR kódem ani pomocí emoji, což je to, co %(brand)s podporuje. Zkuste použít jiného klienta.", "Verify by scanning": "Ověřte naskenováním", @@ -2232,7 +2232,7 @@ "Collapse room list section": "Sbalit seznam místností", "Select room from the room list": "Vybrat místnost v seznamu", "Navigate up/down in the room list": "Posouvat se nahoru/dolů v seznamu místností", - "Jump to room search": "Filtrovat místnosti", + "Jump to room search": "Přejít na vyhledávání místností", "Toggle video on/off": "Zapnout nebo vypnout video", "Toggle microphone mute": "Ztlumit nebo zapnout mikrofon", "Jump to start/end of the composer": "Skočit na konec/začátek textového pole", @@ -2322,7 +2322,7 @@ "Navigation": "Navigace", "Use the + to make a new room or explore existing ones below": "Pomocí + vytvořte novou místnost nebo prozkoumejte stávající místnosti", "Secure Backup": "Zabezpečená záloha", - "Jump to oldest unread message": "Jít na nejstarší nepřečtenou zprávu", + "Jump to oldest unread message": "Přejít na nejstarší nepřečtenou zprávu", "Upload a file": "Nahrát soubor", "You've reached the maximum number of simultaneous calls.": "Dosáhli jste maximálního počtu souběžných hovorů.", "Too Many Calls": "Přiliš mnoho hovorů", @@ -2633,7 +2633,7 @@ "Move right": "Posunout doprava", "Move left": "Posunout doleva", "Go to Home View": "Přejít na domovské zobrazení", - "Dismiss read marker and jump to bottom": "Zavřít značku přečtených zpráv a skočit dolů", + "Dismiss read marker and jump to bottom": "Zavřít značku přečtených zpráv a přejít dolů", "Previous/next room or DM": "Předchozí/další místnost nebo přímá zpráva", "Previous/next unread room or DM": "Předchozí/další nepřečtená místnost nebo přímá zpráva", "Not encrypted": "Není šifrováno", @@ -3104,7 +3104,7 @@ "Open space for anyone, best for communities": "Otevřený prostor pro kohokoli, nejlepší pro komunity", "Public": "Veřejný", "Create a space": "Vytvořit prostor", - "Jump to the bottom of the timeline when you send a message": "Při odesílání zprávy přeskočit na konec časové osy", + "Jump to the bottom of the timeline when you send a message": "Po odeslání zprávy přejít na konec časové osy", "Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. Requires compatible homeserver for some features.": "Prototyp prostorů. Nejsou kompatibilní se skupinami, skupinami v2 a vlastními štítky. Pro některé funkce je vyžadován kompatibilní domovský server.", "This homeserver has been blocked by its administrator.": "Tento domovský server byl zablokován jeho správcem.", "You're already in a call with this person.": "S touto osobou již telefonujete.", @@ -3435,7 +3435,7 @@ "Connected": "Připojeno", "& %(count)s more|other": "a %(count)s dalších", "Only invited people can join.": "Připojit se mohou pouze pozvané osoby.", - "Private (invite only)": "Soukromé (pouze pro pozvané)", + "Private (invite only)": "Soukromý (pouze pro pozvané)", "This upgrade will allow members of selected spaces access to this room without an invite.": "Tato změna umožní členům vybraných prostorů přístup do této místnosti bez pozvánky.", "There was an error loading your notification settings.": "Došlo k chybě při načítání nastavení oznámení.", "Global": "Globální", @@ -3457,7 +3457,7 @@ "Unable to copy a link to the room to the clipboard.": "Nelze zkopírovat odkaz na místnost do schránky.", "Unable to copy room link": "Nelze zkopírovat odkaz na místnost", "This call has failed": "Toto volání se nezdařilo", - "Anyone can find and join.": "Kdokoliv může najít a připojit se.", + "Anyone can find and join.": "Kdokoliv může místnost najít a připojit se do ní.", "Room visibility": "Viditelnost místnosti", "Visible to space members": "Viditelné pro členy prostoru", "Public room": "Veřejná místnost", @@ -3474,12 +3474,12 @@ "Call back": "Zavolat zpět", "You missed this call": "Zmeškali jste tento hovor", "The voice message failed to upload.": "Hlasovou zprávu se nepodařilo nahrát.", - "Copy Room Link": "Kopírovat odkaz na místnost", + "Copy Room Link": "Kopírovat odkaz", "Show %(count)s other previews|one": "Zobrazit %(count)s další náhled", "Show %(count)s other previews|other": "Zobrazit %(count)s dalších náhledů", "Access": "Přístup", "People with supported clients will be able to join the room without having a registered account.": "Lidé s podporovanými klienty se budou moci do místnosti připojit, aniž by měli registrovaný účet.", - "Decide who can join %(roomName)s.": "Rozhodněte, kdo se může připojit k %(roomName)s.", + "Decide who can join %(roomName)s.": "Rozhodněte, kdo se může připojit k místnosti %(roomName)s.", "Space members": "Členové prostoru", "Anyone in %(spaceName)s can find and join. You can select other spaces too.": "Každý, kdo se nachází v prostoru %(spaceName)s, ho může najít a připojit se k němu. Můžete vybrat i jiné prostory.", "Anyone in a space can find and join. You can select multiple spaces.": "Každý, kdo se nachází v prostoru, ho může najít a připojit se k němu. Můžete vybrat více prostorů.", @@ -3502,5 +3502,67 @@ "They didn't pick up": "Nezvedli to", "Call again": "Volat znova", "They declined this call": "Odmítli tento hovor", - "You declined this call": "Odmítli jste tento hovor" + "You declined this call": "Odmítli jste tento hovor", + "Share content": "Sdílet obsah", + "Application window": "Okno aplikace", + "Share entire screen": "Sdílet celou obrazovku", + "You can now share your screen by pressing the \"screen share\" button during a call. You can even do this in audio calls if both sides support it!": "Nyní můžete sdílet obrazovku stisknutím tlačítka \"sdílení obrazovky\" během hovoru. Můžete tak učinit i při zvukových hovorech, pokud to obě strany podporují!", + "Screen sharing is here!": "Sdílení obrazovky je tu!", + "Your camera is still enabled": "Vaše kamera je stále zapnutá", + "Your camera is turned off": "Vaše kamera je vypnutá", + "%(sharerName)s is presenting": "%(sharerName)s prezentuje", + "You are presenting": "Prezentujete", + "Anyone will be able to find and join this room.": "Kdokoliv může najít tuto místnost a připojit se k ní.", + "Add existing space": "Přidat stávající prostor", + "Add space": "Přidat prostor", + "Give feedback.": "Poskytněte zpětnou vazbu.", + "Thank you for trying Spaces. Your feedback will help inform the next versions.": "Děkujeme, že jste vyzkoušeli Prostory. Vaše zpětná vazba pomůže při tvorbě dalších verzí.", + "Spaces feedback": "Zpětná vazba prostorů", + "Spaces are a new feature.": "Prostory jsou novou funkcí.", + "We're working on this, but just want to let you know.": "Pracujeme na tom, ale jen vás chceme informovat.", + "Search for rooms or spaces": "Hledat místnosti nebo prostory", + "Are you sure you want to leave ?": "Jste si jisti, že chcete opustit ?", + "Leave %(spaceName)s": "Opustit %(spaceName)s", + "You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.": "Jste jediným správcem některých místností nebo prostorů, které chcete opustit. Jejich opuštěním zůstanou bez správců.", + "You're the only admin of this space. Leaving it will mean no one has control over it.": "Jste jediným správcem tohoto prostoru. Jeho opuštění bude znamenat, že nad ním nebude mít nikdo kontrolu.", + "You won't be able to rejoin unless you are re-invited.": "Pokud nebudete znovu pozváni, nebudete se moci připojit.", + "Search %(spaceName)s": "Hledat %(spaceName)s", + "Leave specific rooms and spaces": "Opustit konkrétní místnosti a prostory", + "Don't leave any": "Neopouštět žádný", + "Leave all rooms and spaces": "Opustit všechny místnosti a prostory", + "Want to add an existing space instead?": "Chcete místo toho přidat stávající prostor?", + "Private space (invite only)": "Soukromý prostor (pouze pro pozvané)", + "Space visibility": "Viditelnost prostoru", + "Add a space to a space you manage.": "Přidat prostor do prostoru, který spravujete.", + "Only people invited will be able to find and join this space.": "Tento prostor budou moci najít a připojit se k němu pouze pozvaní lidé.", + "Anyone will be able to find and join this space, not just members of .": "Kdokoliv bude moci najít a připojit se k tomuto prostoru, nejen členové .", + "Anyone in will be able to find and join.": "Kdokoli v ho bude moci najít a připojit se.", + "Adding spaces has moved.": "Přidávání prostorů bylo přesunuto.", + "Search for rooms": "Hledat místnosti", + "Search for spaces": "Hledat prostory", + "Create a new space": "Vytvořit nový prostor", + "Want to add a new space instead?": "Chcete místo toho přidat nový prostor?", + "Decrypting": "Dešifrování", + "Show all rooms": "Zobrazit všechny místnosti", + "All rooms you're in will appear in Home.": "Všechny místnosti, ve kterých se nacházíte, se zobrazí na domovské obrazovce.", + "Send pseudonymous analytics data": "Odeslat pseudonymní analytická data", + "Missed call": "Zmeškaný hovor", + "Call declined": "Hovor odmítnut", + "Surround selected text when typing special characters": "Ohraničit označený text při psaní speciálních znaků", + "Stop recording": "Zastavit nahrávání", + "Send voice message": "Odeslat hlasovou zprávu", + "Mute the microphone": "Ztlumit mikrofon", + "Unmute the microphone": "Zrušit ztlumení mikrofonu", + "Dialpad": "Číselník", + "More": "Více", + "Show sidebar": "Zobrazit postranní panel", + "Hide sidebar": "Skrýt postranní panel", + "Start sharing your screen": "Začít sdílet obrazovku", + "Stop sharing your screen": "Přestat sdílet obrazovku", + "Stop the camera": "Vypnout kameru", + "Start the camera": "Zapnout kameru", + "%(oneUser)schanged the pinned messages for the room %(count)s times.|other": "%(oneUser)s%(count)s krát změnil(a) připnuté zprávy místnosti.", + "%(severalUsers)schanged the pinned messages for the room %(count)s times.|other": "%(severalUsers)s%(count)s krát změnili připnuté zprávy místnosti.", + "Olm version:": "Verze Olm:", + "Don't send read receipts": "Neposílat potvrzení o přečtení" } diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 4a2b6521b2..991dd312bc 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -3510,5 +3510,14 @@ "An unknown error occurred": "Ein unbekannter Fehler ist aufgetreten", "Message bubbles": "Nachrichtenblasen", "New layout switcher (with message bubbles)": "Layout ändern erlauben (mit Nachrichtenblasen)", - "New in the Spaces beta": "Neues in der Spaces Beta" + "New in the Spaces beta": "Neues in der Spaces Beta", + "To help space members find and join a private room, go to that room's Security & Privacy settings.": "Um Mitgliedern beim Finden privater Räume zu helfen, öffne die Sicherheitseinstellungen des Raumes.", + "Help space members find private rooms": "Hilf Mitgliedern, private Räume zu finden", + "More": "Mehr", + "Show sidebar": "Seitenleiste anzeigen", + "Hide sidebar": "Seitenleiste verbergen", + "Start sharing your screen": "Bildschirmfreigabe starten", + "Stop sharing your screen": "Bildschirmfreigabe beenden", + "Don't send read receipts": "Sende keine Lesebestätigungen", + "Send pseudonymous analytics data": "Sende pseudonymisierte Nutzungsdaten" } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ec28be664b..607ea4f611 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -906,6 +906,16 @@ "sends snowfall": "sends snowfall", "Sends the given message with a space themed effect": "Sends the given message with a space themed effect", "sends space invaders": "sends space invaders", + "unknown person": "unknown person", + "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Consulting with %(transferTarget)s. Transfer to %(transferee)s", + "You held the call Switch": "You held the call Switch", + "You held the call Resume": "You held the call Resume", + "%(peerName)s held the call": "%(peerName)s held the call", + "Connecting": "Connecting", + "You are presenting": "You are presenting", + "%(sharerName)s is presenting": "%(sharerName)s is presenting", + "Your camera is turned off": "Your camera is turned off", + "Your camera is still enabled": "Your camera is still enabled", "Start the camera": "Start the camera", "Stop the camera": "Stop the camera", "Stop sharing your screen": "Stop sharing your screen", @@ -917,16 +927,6 @@ "Unmute the microphone": "Unmute the microphone", "Mute the microphone": "Mute the microphone", "Hangup": "Hangup", - "unknown person": "unknown person", - "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Consulting with %(transferTarget)s. Transfer to %(transferee)s", - "You held the call Switch": "You held the call Switch", - "You held the call Resume": "You held the call Resume", - "%(peerName)s held the call": "%(peerName)s held the call", - "Connecting": "Connecting", - "You are presenting": "You are presenting", - "%(sharerName)s is presenting": "%(sharerName)s is presenting", - "Your camera is turned off": "Your camera is turned off", - "Your camera is still enabled": "Your camera is still enabled", "Video Call": "Video Call", "Voice Call": "Voice Call", "Fill Screen": "Fill Screen", @@ -1016,7 +1016,9 @@ "Your server isn't responding to some requests.": "Your server isn't responding to some requests.", "Decline (%(counter)s)": "Decline (%(counter)s)", "Accept to continue:": "Accept to continue:", + "Delete avatar": "Delete avatar", "Delete": "Delete", + "Upload avatar": "Upload avatar", "Upload": "Upload", "Name": "Name", "Description": "Description", @@ -1028,12 +1030,14 @@ "e.g. my-space": "e.g. my-space", "Address": "Address", "Create a space": "Create a space", - "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.", + "What kind of Space do you want to create?": "What kind of Space do you want to create?", + "You can change this later.": "You can change this later.", "Public": "Public", "Open space for anyone, best for communities": "Open space for anyone, best for communities", "Private": "Private", "Invite only, best for yourself or teams": "Invite only, best for yourself or teams", - "You can change this later": "You can change this later", + "You can also create a Space from a community.": "You can also create a Space from a community.", + "To join an existing space you'll need an invite.": "To join an existing space you'll need an invite.", "Go back": "Go back", "Your public space": "Your public space", "Your private space": "Your private space", @@ -1074,6 +1078,8 @@ "Preview Space": "Preview Space", "Allow people to preview your space before they join.": "Allow people to preview your space before they join.", "Recommended for public spaces.": "Recommended for public spaces.", + "Jump to first unread room.": "Jump to first unread room.", + "Jump to first invite.": "Jump to first invite.", "Expand": "Expand", "Collapse": "Collapse", "Space options": "Space options", @@ -1334,12 +1340,18 @@ "If this isn't what you want, please use a different tool to ignore users.": "If this isn't what you want, please use a different tool to ignore users.", "Room ID or address of ban list": "Room ID or address of ban list", "Subscribe": "Subscribe", + "Open Space": "Open Space", + "Create Space": "Create Space", "Start automatically after system login": "Start automatically after system login", "Warn before quitting": "Warn before quitting", "Always show the window menu bar": "Always show the window menu bar", "Show tray icon and minimize window to it on close": "Show tray icon and minimize window to it on close", "Preferences": "Preferences", "Room list": "Room list", + "Communities": "Communities", + "Communities have been archived to make way for Spaces but you can convert your communities into Spaces below. Converting will ensure your conversations get the latest features.": "Communities have been archived to make way for Spaces but you can convert your communities into Spaces below. Converting will ensure your conversations get the latest features.", + "Show my Communities": "Show my Communities", + "If a community isn't shown you may not have permission to convert it.": "If a community isn't shown you may not have permission to convert it.", "Keyboard shortcuts": "Keyboard shortcuts", "To view all keyboard shortcuts, click here.": "To view all keyboard shortcuts, click here.", "Displaying time": "Displaying time", @@ -1668,8 +1680,6 @@ "Activity": "Activity", "A-Z": "A-Z", "List options": "List options", - "Jump to first unread room.": "Jump to first unread room.", - "Jump to first invite.": "Jump to first invite.", "Show %(count)s more|other": "Show %(count)s more", "Show %(count)s more|one": "Show %(count)s more", "Show less": "Show less", @@ -1879,13 +1889,14 @@ "Connected": "Connected", "Call declined": "Call declined", "Call back": "Call back", - "Missed call": "Missed call", + "No answer": "No answer", "Could not connect media": "Could not connect media", "Connection failed": "Connection failed", "Their device couldn't start the camera or microphone": "Their device couldn't start the camera or microphone", "An unknown error occurred": "An unknown error occurred", - "Unknown failure: %(reason)s)": "Unknown failure: %(reason)s)", + "Unknown failure: %(reason)s": "Unknown failure: %(reason)s", "Retry": "Retry", + "Missed call": "Missed call", "The call is in an unknown state!": "The call is in an unknown state!", "Sunday": "Sunday", "Monday": "Monday", @@ -2225,13 +2236,24 @@ "Visible to space members": "Visible to space members", "Block anyone not part of %(serverName)s from ever joining this room.": "Block anyone not part of %(serverName)s from ever joining this room.", "Create Room": "Create Room", + "This community has been upgraded into a Space": "This community has been upgraded into a Space", + "To view Spaces, hide communities in Preferences": "To view Spaces, hide communities in Preferences", + "Space created": "Space created", + " has been made and everyone who was a part of the community has been invited to it.": " has been made and everyone who was a part of the community has been invited to it.", + "To create a Space from another community, just pick the community in Preferences.": "To create a Space from another community, just pick the community in Preferences.", + "Failed to migrate community": "Failed to migrate community", + "Create Space from community": "Create Space from community", + "A link to the Space will be put in your community description.": "A link to the Space will be put in your community description.", + "All rooms will be added and all community members will be invited.": "All rooms will be added and all community members will be invited.", + "Flair won't be available in Spaces for the foreseeable future.": "Flair won't be available in Spaces for the foreseeable future.", + "This description will be shown to people when they view your space": "This description will be shown to people when they view your space", + "Space visibility": "Space visibility", + "Private space (invite only)": "Private space (invite only)", + "Public space": "Public space", "Anyone in will be able to find and join.": "Anyone in will be able to find and join.", "Anyone will be able to find and join this space, not just members of .": "Anyone will be able to find and join this space, not just members of .", "Only people invited will be able to find and join this space.": "Only people invited will be able to find and join this space.", "Add a space to a space you manage.": "Add a space to a space you manage.", - "Space visibility": "Space visibility", - "Private space (invite only)": "Private space (invite only)", - "Public space": "Public space", "Want to add an existing space instead?": "Want to add an existing space instead?", "Adding...": "Adding...", "Sign out": "Sign out", @@ -2683,7 +2705,6 @@ "You must join the room to see its files": "You must join the room to see its files", "No files visible in this room": "No files visible in this room", "Attach files from chat or just drag and drop them anywhere in a room.": "Attach files from chat or just drag and drop them anywhere in a room.", - "Communities": "Communities", "Create community": "Create community", "

          HTML for your community's page

          \n

          \n Use the long description to introduce new members to the community, or distribute\n some important links\n

          \n

          \n You can even add images with Matrix URLs \n

          \n": "

          HTML for your community's page

          \n

          \n Use the long description to introduce new members to the community, or distribute\n some important links\n

          \n

          \n You can even add images with Matrix URLs \n

          \n", "Add rooms to the community summary": "Add rooms to the community summary", @@ -2711,6 +2732,11 @@ "Community Settings": "Community Settings", "Want more than a community? Get your own server": "Want more than a community? Get your own server", "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.", + "You can create a Space from this community here.": "You can create a Space from this community here.", + "Ask the admins of this community to make it into a Space and keep a look out for the invite.": "Ask the admins of this community to make it into a Space and keep a look out for the invite.", + "Communities can now be made into Spaces": "Communities can now be made into Spaces", + "Spaces are a new way to make a community, with new features coming.": "Spaces are a new way to make a community, with new features coming.", + "Communities won't receive further updates.": "Communities won't receive further updates.", "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.", "Featured Rooms:": "Featured Rooms:", "Featured Users:": "Featured Users:", @@ -2724,7 +2750,6 @@ "Everyone": "Everyone", "Your community hasn't got a Long Description, a HTML page to show to community members.
          Click here to open settings and give it one!": "Your community hasn't got a Long Description, a HTML page to show to community members.
          Click here to open settings and give it one!", "Long Description (HTML)": "Long Description (HTML)", - "Upload avatar": "Upload avatar", "Community %(groupId)s not found": "Community %(groupId)s not found", "This homeserver does not support communities": "This homeserver does not support communities", "Failed to load %(groupId)s": "Failed to load %(groupId)s", @@ -2834,6 +2859,7 @@ "Mark as suggested": "Mark as suggested", "No results found": "No results found", "You may want to try a different search or check for typos.": "You may want to try a different search or check for typos.", + "Space": "Space", "Search names and descriptions": "Search names and descriptions", "If you can't find the room you're looking for, ask for an invite or create a new room.": "If you can't find the room you're looking for, ask for an invite or create a new room.", "Create room": "Create room", @@ -2842,6 +2868,7 @@ "To view %(spaceName)s, turn on the Spaces beta": "To view %(spaceName)s, turn on the Spaces beta", "To join %(spaceName)s, turn on the Spaces beta": "To join %(spaceName)s, turn on the Spaces beta", "To view %(spaceName)s, you need an invite": "To view %(spaceName)s, you need an invite", + "Created from ": "Created from ", "Welcome to ": "Welcome to ", "Random": "Random", "Support": "Support", @@ -3115,7 +3142,6 @@ "Page Down": "Page Down", "Esc": "Esc", "Enter": "Enter", - "Space": "Space", "End": "End", "[number]": "[number]" } diff --git a/src/i18n/strings/en_US.json b/src/i18n/strings/en_US.json index a5d7756de8..ec9df4a214 100644 --- a/src/i18n/strings/en_US.json +++ b/src/i18n/strings/en_US.json @@ -666,5 +666,15 @@ "Add some details to help people recognise it.": "Add some details to help people recognize it.", "Unrecognised room address:": "Unrecognized room address:", "A private space to organise your rooms": "A private space to organize your rooms", - "Message search initialisation failed": "Message search initialization failed" + "Message search initialisation failed": "Message search initialization failed", + "Permission is granted to use the webcam": "Permission is granted to use the webcam", + "A microphone and webcam are plugged in and set up correctly": "A microphone and webcam are plugged in and set up correctly", + "Call failed because webcam or microphone could not be accessed. Check that:": "Call failed because webcam or microphone could not be accessed. Check that:", + "Call failed because microphone could not be accessed. Check that a microphone is plugged in and set up correctly.": "Call failed because microphone could not be accessed. Check that a microphone is plugged in and set up correctly.", + "Unable to access microphone": "Unable to access microphone", + "The call was answered on another device.": "The call was answered on another device.", + "Answered Elsewhere": "Answered Elsewhere", + "The call could not be established": "The call could not be established", + "The user you called is busy.": "The user you called is busy.", + "User Busy": "User Busy" } diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index 08a4a2be9a..f192fc9163 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -561,7 +561,7 @@ "Demote": "Quitar permisos", "Unignore": "Dejar de ignorar", "Ignore": "Ignorar", - "Jump to read receipt": "Saltar a recibo leído", + "Jump to read receipt": "Saltar al último mensaje sin leer", "Mention": "Mencionar", "Invite": "Invitar", "Share Link to User": "Compartir enlace al usuario", @@ -708,7 +708,7 @@ "collapse": "colapsar", "expand": "expandir", "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "No se pudo cargar el evento al que se respondió, bien porque no existe o no tiene permiso para verlo.", - "In reply to ": "En respuesta a ", + "In reply to ": "Respondiendo a ", "And %(count)s more...|other": "Y %(count)s más…", "ex. @bob:example.com": "ej. @bob:ejemplo.com", "Add User": "Agregar Usuario", @@ -718,7 +718,7 @@ "You have entered an invalid address.": "No ha introducido una dirección correcta.", "Try using one of the following valid address types: %(validTypesList)s.": "Intente usar uno de los tipos de direcciones válidos: %(validTypesList)s.", "Confirm Removal": "Confirmar eliminación", - "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "¿Seguro que quieres eliminar este evento? Ten en cuenta que, si borras un cambio de nombre o tema de sala, podrías deshacer el cambio.", + "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "¿Seguro que quieres eliminar este evento? Ten en cuenta que, si borras un cambio de nombre o asunto de sala, podrías deshacer el cambio.", "Community IDs cannot be empty.": "Las IDs de comunidad no pueden estar vacías.", "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Las IDs de comunidad solo pueden contener caracteres de la «a» a la «z» excluyendo la «ñ», dígitos o «=_-./»", "Something went wrong whilst creating your community": "Algo fue mal mientras se creaba la comunidad", @@ -870,10 +870,10 @@ "Developer options": "Opciones de desarrollador", "Room version": "Versión de la sala", "Room information": "Información de la sala", - "Room Topic": "Tema de la sala", + "Room Topic": "Asunto de la sala", "Theme": "Tema", "Voice & Video": "Voz y vídeo", - "Gets or sets the room topic": "Obtiene o establece el tema de la sala", + "Gets or sets the room topic": "Ver o cambiar el asunto de la sala", "This room has no topic.": "Esta sala no tiene tema.", "Sets the room name": "Establece el nombre de la sala", "Phone numbers": "Números de teléfono", @@ -1226,7 +1226,7 @@ "Do not use an identity server": "No usar un servidor de identidad", "Enter a new identity server": "Introducir un servidor de identidad nuevo", "Change": "Cambiar", - "Manage integrations": "Gestionar integraciones", + "Manage integrations": "Gestor de integraciones", "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Los administradores de integración reciben datos de configuración, y pueden modificar widgets, enviar invitaciones de sala, y establecer niveles de poder en tu nombre.", "Something went wrong trying to invite the users.": "Algo salió mal al intentar invitar a los usuarios.", "We couldn't invite those users. Please check the users you want to invite and try again.": "No se pudo invitar a esos usuarios. Por favor, revisa los usuarios que quieres invitar e inténtalo de nuevo.", @@ -1351,10 +1351,10 @@ "Enable message search in encrypted rooms": "Activar la búsqueda de mensajes en salas cifradas", "How fast should messages be downloaded.": "Con qué rapidez deben ser descargados los mensajes.", "Verify this session by completing one of the following:": "Verifica esta sesión de una de las siguientes formas:", - "Scan this unique code": "Escanea este código único", + "Scan this unique code": "Escanea este código", "or": "o", - "Compare unique emoji": "Comparar iconos", - "Compare a unique set of emoji if you don't have a camera on either device": "Comparar un conjunto de iconos si no tienes cámara en ninguno de los dispositivos", + "Compare unique emoji": "Compara los emojis", + "Compare a unique set of emoji if you don't have a camera on either device": "Compara un conjunto de emojis si no tienes cámara en ninguno de los dispositivos", "Start": "Empezar", "Waiting for %(displayName)s to verify…": "Esperando la verificación de %(displayName)s…", "Review": "Revisar", @@ -1411,7 +1411,7 @@ "Backup key stored: ": "Clave de seguridad almacenada: ", "Your keys are not being backed up from this session.": "No se está haciendo una copia de seguridad de tus claves en esta sesión.", "Clear notifications": "Limpiar notificaciones", - "Enable desktop notifications for this session": "Activar notificaciones de escritorio para esta sesión", + "Enable desktop notifications for this session": "Activa las notificaciones de escritorio para esta sesión", "Enable audible notifications for this session": "Activar notificaciones sonoras para esta sesión", "Checking server": "Comprobando servidor", "Change identity server": "Cambiar servidor de identidad", @@ -1457,12 +1457,12 @@ "%(senderName)s changed the main and alternative addresses for this room.": "%(senderName)s cambió la dirección principal y las alternativas de esta sala.", "%(senderName)s changed the addresses for this room.": "%(senderName)s cambió las direcciones de esta sala.", "You signed in to a new session without verifying it:": "Iniciaste una nueva sesión sin verificarla:", - "Verify your other session using one of the options below.": "Verificar la otra sesión utilizando una de las siguientes opciones.", + "Verify your other session using one of the options below.": "Verifica la otra sesión utilizando una de las siguientes opciones.", "%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s (%(userId)s) inició una nueva sesión sin verificarla:", "Ask this user to verify their session, or manually verify it below.": "Pídele al usuario que verifique su sesión, o verifícala manualmente a continuación.", "Not Trusted": "No es de confianza", "Manually Verify by Text": "Verificar manualmente mediante texto", - "Interactively verify by Emoji": "Verifica interactivamente con unEmoji", + "Interactively verify by Emoji": "Verificar interactivamente con emojis", "Done": "Listo", "Support adding custom themes": "Soporta la adición de temas personalizados", "Show info about bridges in room settings": "Mostrar información sobre puentes en la configuración de salas", @@ -1472,7 +1472,7 @@ "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Permitir el servidor de respaldo de asistencia de llamadas turn.matrix.org cuando tu servidor base no lo ofrezca (tu dirección IP se compartiría durante una llamada)", "Send read receipts for messages (requires compatible homeserver to disable)": "Enviar recibos de lectura de mensajes (requiere un servidor local compatible para desactivarlo)", "Manually verify all remote sessions": "Verificar manualmente todas las sesiones remotas", - "Confirm the emoji below are displayed on both sessions, in the same order:": "Confirma que los iconos de abajo se muestran en el mismo orden en ambas sesiones:", + "Confirm the emoji below are displayed on both sessions, in the same order:": "Confirma que los emojis de abajo son los mismos y tienen el mismo orden en los dos sitios:", "Verify this session by confirming the following number appears on its screen.": "Verifica esta sesión confirmando que el siguiente número aparece en su pantalla.", "Waiting for your other session, %(deviceName)s (%(deviceId)s), to verify…": "Esperando a que la otra sesión lo verifique también %(deviceName)s (%(deviceId)s)…", "Cancelling…": "Anulando…", @@ -1630,7 +1630,7 @@ "Reminder: Your browser is unsupported, so your experience may be unpredictable.": "Recordatorio: Su navegador no es compatible, por lo que su experiencia puede ser impredecible.", "GitHub issue": "Incidencia de GitHub", "Notes": "Notas", - "If there is additional context that would help in analysing the issue, such as what you were doing at the time, room IDs, user IDs, etc., please include those things here.": "Si hay algún contexto adicional que ayude a analizar el tema, como por ejemplo lo que estaba haciendo en ese momento, nombre (ID) de sala, nombre (ID)de usuario, etc., por favor incluya esas cosas aquí.", + "If there is additional context that would help in analysing the issue, such as what you were doing at the time, room IDs, user IDs, etc., please include those things here.": "Si hay algún contexto adicional que ayude a analizar el problema, como por ejemplo lo que estaba haciendo en ese momento, nombre (ID) de sala, nombre (ID) de usuario, etc., por favor incluye esas cosas aquí.", "Removing…": "Quitando…", "Destroy cross-signing keys?": "¿Destruir las claves de firma cruzada?", "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.": "La eliminación de claves de firma cruzada es definitiva. Cualquiera con el que lo hayas verificado verá alertas de seguridad. Es casi seguro que no quieres hacer esto, a menos que hayas perdido todos los dispositivos puedas usar hacer una firma cruzada.", @@ -1696,7 +1696,7 @@ "Do you want to chat with %(user)s?": "¿Quieres chatear con %(user)s?", "Do you want to join %(roomName)s?": "¿Quieres unirte a %(roomName)s?", " invited you": " te ha invitado", - "You're previewing %(roomName)s. Want to join it?": "Estás previsualizando %(roomName)s. ¿Quieres unirte?", + "You're previewing %(roomName)s. Want to join it?": "Esto es una vista previa de %(roomName)s. ¿Te quieres unir?", "%(roomName)s can't be previewed. Do you want to join it?": "La sala %(roomName)s no permite previsualización. ¿Quieres unirte?", "This room doesn't exist. Are you sure you're at the right place?": "Esta sala no existe. ¿Estás seguro de estar en el lugar correcto?", "Try again later, or ask a room admin to check if you have access.": "Inténtalo más tarde, o pide que un administrador de la sala compruebe si tienes acceso.", @@ -1722,7 +1722,7 @@ "Deactivate user": "Desactivar usuario", "Failed to deactivate user": "Error en desactivar usuario", "Remove recent messages": "Eliminar mensajes recientes", - "Send a reply…": "Enviar una respuesta …", + "Send a reply…": "Enviar una respuesta…", "Send a message…": "Enviar un mensaje…", "Bold": "Negrita", "Italics": "Cursiva", @@ -1994,7 +1994,7 @@ "Liberate your communication": "Libera tu comunicación", "Send a Direct Message": "Envía un mensaje directo", "Explore Public Rooms": "Explora las salas públicas", - "Create a Group Chat": "Crea un chat grupal", + "Create a Group Chat": "Crea una conversación grupal", "Explore": "Explorar", "Filter": "Filtrar", "Filter rooms…": "Filtrar salas…", @@ -2014,7 +2014,7 @@ "Guest": "Invitado", "Your profile": "Su perfil", "Could not load user profile": "No se pudo cargar el perfil de usuario", - "Verify this login": "Verificar este inicio de sesión", + "Verify this login": "Verifica este inicio de sesión", "Session verified": "Sesión verificada", "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Cambiar la contraseña restablecerá cualquier clave de cifrado de extremo a extremo en todas sus sesiones, haciendo ilegible el historial de chat cifrado. Configura la copia de seguridad de las claves o exporta las claves de la sala de otra sesión antes de restablecer la contraseña.", "Your Matrix account on %(serverName)s": "Su cuenta de Matrix en %(serverName)s", @@ -2096,7 +2096,7 @@ "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Tu nueva sesión ha sido verificada. Ahora tiene acceso a los mensajes cifrados y otros usuarios la verán como verificada.", - "Your new session is now verified. Other users will see it as trusted.": "Tu sesión se encuentra ahora verificada. Otros usuarios la verán como confiable.", + "Your new session is now verified. Other users will see it as trusted.": "Has verificado esta sesión. El resto la verá como «de confianza».", "This session is encrypting history using the new recovery method.": "Esta sesión está cifrando el historial usando el nuevo método de recuperación.", "Change notification settings": "Cambiar los ajustes de notificaciones", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Prototipo de comunidades v2. Requiere un servidor compatible. Altamente experimental - usar con precuación.", @@ -2196,7 +2196,7 @@ "About": "Acerca de", "%(count)s people|other": "%(count)s personas", "%(count)s people|one": "%(count)s persona", - "Show files": "Mostrar archivos", + "Show files": "Ver archivos", "Room settings": "Configuración de la sala", "You've successfully verified your device!": "¡Ha verificado correctamente su dispositivo!", "Take a picture": "Toma una foto", @@ -2421,7 +2421,7 @@ "Navigate recent messages to edit": "Navegar entre mensajes recientes para editar", "Jump to start/end of the composer": "Saltar al inicio o final del editor", "Navigate composer history": "Navegar por el historial del editor", - "Cancel replying to a message": "Cancelar la respuesta a un mensaje", + "Cancel replying to a message": "Cancelar responder al mensaje", "Toggle microphone mute": "Alternar silencio del micrófono", "Toggle video on/off": "Activar/desactivar video", "Scroll up/down in the timeline": "Desplazarse hacia arriba o hacia abajo en la línea de tiempo", @@ -2769,7 +2769,7 @@ "Add a photo, so people can easily spot your room.": "Añade una imagen para que la gente reconozca la sala fácilmente.", "%(displayName)s created this room.": "%(displayName)s creó esta sala.", "You created this room.": "Creaste esta sala.", - "Add a topic to help people know what it is about.": "Añade un tema para que la gente sepa de qué va esta sala.", + "Add a topic to help people know what it is about.": "Escribe un asunto para que la gente sepa de qué va esta sala.", "Topic: %(topic)s ": "Tema: %(topic)s ", "Topic: %(topic)s (edit)": "Tema: %(topic)s (cambiar)", "Remove messages sent by others": "Eliminar mensajes mandados por otros", @@ -2801,7 +2801,7 @@ "Safeguard against losing access to encrypted messages & data": "Evita perder acceso a datos y mensajes cifrados", "Use app": "Usar la aplicación", "Use app for a better experience": "Usa la aplicación para una experiencia mejor", - "Enable desktop notifications": "Activar notificaciones de escritorio", + "Enable desktop notifications": "Activa las notificaciones de escritorio", "Don't miss a reply": "No te pierdas ninguna respuesta", "Send messages as you in your active room": "Enviar mensajes en tu sala activa", "See messages posted to your active room": "Ver los mensajes publicados en tu sala activa", @@ -2814,10 +2814,10 @@ "Change the avatar of your active room": "Cambiar la foto de tu sala actual", "See when the avatar changes in this room": "Ver cuándo cambia la imagen de esta sala", "Change the avatar of this room": "Cambiar la imagen de esta sala", - "See when the name changes in your active room": "Ver cuándo cambia el tema de tu sala actual", + "See when the name changes in your active room": "Ver cuándo cambia el asunto de tu sala actual", "Change the name of your active room": "Cambiar el nombre de tu sala actual", - "See when the name changes in this room": "Ver cuándo cambia el tema de esta sala", - "Change the name of this room": "Cambiar el tema de esta sala", + "See when the name changes in this room": "Ver cuándo cambia el tema de esta asunto", + "Change the name of this room": "Cambiar el asunto de esta sala", "Sint Maarten": "San Martín", "Singapore": "Singapur", "Sierra Leone": "Sierra Leona", @@ -2845,10 +2845,10 @@ "Mongolia": "Mongolia", "Montenegro": "Montenegro", "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.": "Element Web en móviles es un experimento. Para una mejor experiencia y las últimas funcionalidades, usa nuestra aplicación nativa gratuita.", - "See when the topic changes in your active room": "Ver cuándo cambia el tema de tu sala actual", - "Change the topic of your active room": "Cambiar el tema de tu sala actual", - "See when the topic changes in this room": "Ver cuándo cambia el tema de esta sala", - "Change the topic of this room": "Cambiar el tema de esta sala", + "See when the topic changes in your active room": "Ver cuándo cambia el asunto de la sala en la que estés", + "Change the topic of your active room": "Cambiar el asunto de la sala en la que estés", + "See when the topic changes in this room": "Ver cuándo cambia el asunto de esta sala", + "Change the topic of this room": "Cambiar el asunto de esta sala", "%(senderName)s declined the call.": "%(senderName)s ha rechazado la llamada.", "(an error occurred)": "(ha ocurrido un error)", "(their device couldn't start the camera / microphone)": "(su dispositivo no ha podido acceder a la cámara o micrófono)", @@ -3199,7 +3199,7 @@ "Add existing rooms": "Añadir salas existentes", "%(count)s people you know have already joined|one": "%(count)s persona que ya conoces se ha unido", "%(count)s people you know have already joined|other": "%(count)s personas que ya conoces se han unido", - "Accept on your other login…": "Acepta en tu otro inicio de sesión…", + "Accept on your other login…": "Acepta en otro sitio donde hayas iniciado sesión…", "Stop & send recording": "Parar y enviar grabación", "Record a voice message": "Grabar un mensaje de voz", "Quick actions": "Acciones rápidas", @@ -3223,7 +3223,7 @@ "You can add more later too, including already existing ones.": "Puedes añadir más después, incluso si ya existen.", "Please choose a strong password": "Por favor, elige una contraseña segura", "Use another login": "Usar otro inicio de sesión", - "Verify your identity to access encrypted messages and prove your identity to others.": "Verifica tu identidad para acceder a mensajes cifrados y probar tu identidad a otros.", + "Verify your identity to access encrypted messages and prove your identity to others.": "Verifica tu identidad para leer tus mensajes cifrados y probar a las demás personas que realmente eres tú.", "Without verifying, you won’t have access to all your messages and may appear as untrusted to others.": "Si no verificas no tendrás acceso a todos tus mensajes y puede que aparezcas como no confiable para otros usuarios.", "Invite messages are hidden by default. Click to show the message.": "Los mensajes de invitación no se muestran por defecto. Haz clic para mostrarlo.", "You can select all or individual messages to retry or delete": "Puedes seleccionar uno o todos los mensajes para reintentar o eliminar", @@ -3364,7 +3364,7 @@ "Please pick a nature and describe what makes this message abusive.": "Por favor, escoge una categoría y explica por qué el mensaje es abusivo.", "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.": "Otro motivo. Por favor, describe el problema.\nSe avisará a los moderadores de la sala.", "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.": "Esta sala está dedicada a un tema ilegal o contenido tóxico, o bien los moderadores no están tomando medidas frente a este tipo de contenido.\nSe avisará a los administradores de %(homeserver)s.", - "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.": "Esta sala está dedicada a un tema ilegal o contenido tóxico, o bien los moderadores no están tomando medidas frente a este tipo de contenido.\nSe avisará a los administradores de %(homeserver)s, pero no podrán leer el contenido cifrado de la sala.", + "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.": "Esta sala está dedicada a un tema ilegal o contenido tóxico, o los moderadores no están tomando medidas frente a este tipo de contenido.\nSe avisará a los administradores de %(homeserver)s, pero no podrán leer el contenido cifrado de la sala.", "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.": "Esta persona está mandando publicidad no deseada o propaganda.\nSe avisará a los moderadores de la sala.", "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.": "Esta persona está comportándose de manera posiblemente ilegal. Por ejemplo, amenazando con violencia física o con revelar datos personales.\nSe avisará a los moderadores de la sala, que podrían denunciar los hechos.", "This user is displaying toxic behaviour, for instance by insulting other users or sharing adult-only content in a family-friendly room or otherwise violating the rules of this room.\nThis will be reported to the room moderators.": "Esta persona está teniendo un comportamiento tóxico. Por ejemplo, insultando al resto, compartiendo contenido explícito en una sala para todos los públicos, o incumpliendo las normas de la sala en general.\nSe avisará a los moderadores de la sala.", @@ -3390,7 +3390,7 @@ "anyone with the link can view and join": "cualquiera con el enlace puede verlo y unirse", "Decide who can view and join %(spaceName)s.": "Decide quién puede ver y unirse a %(spaceName)s.", "Visibility": "Visibilidad", - "Guests can join a space without having an account.": "Las personas sin cuenta podrían unirse al espacio sin invitación.", + "Guests can join a space without having an account.": "Dejar que las personas sin cuenta se unan al espacio.", "This may be useful for public spaces.": "Esto puede ser útil para espacios públicos.", "Enable guest access": "Permitir acceso a personas sin cuenta", "Failed to update the history visibility of this space": "No se ha podido cambiar la visibilidad del historial de este espacio", @@ -3423,15 +3423,15 @@ "Some invites couldn't be sent": "No se han podido enviar algunas invitaciones", "Integration manager": "Gestor de integración", "Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.": "%(brand)s no utilizar un \"gestor de integración\" para hacer esto. Por favor, contacta con un administrador.", - "Using this widget may share data with %(widgetDomain)s & your integration manager.": "Usar este widget puede resultar en que se compartan datos con %(widgetDomain)s y su administrador de integración.", - "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Los administradores de integración reciben datos de configuración, y pueden modificar widgets, enviar invitaciones de sala, y establecer niveles de poder en tu nombre.", - "Use an integration manager to manage bots, widgets, and sticker packs.": "Utiliza un administrador de integración para gestionar los bots, los widgets y los paquetes de pegatinas.", - "Use an integration manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Usar un gestor de integraciones (%(serverName)s) para manejar los bots, widgets y paquetes de pegatinas.", + "Using this widget may share data with %(widgetDomain)s & your integration manager.": "Al usar este widget puede que se compartan datos con %(widgetDomain)s y tu gestor de integraciones.", + "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Los gestores de integraciones reciben datos de configuración, y pueden modificar widgets, enviar invitaciones de sala, y establecer niveles de poder en tu nombre.", + "Use an integration manager to manage bots, widgets, and sticker packs.": "Usa un gestor de integraciones para bots, widgets y paquetes de pegatinas.", + "Use an integration manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Usar un gestor de integraciones (%(serverName)s) para gestionar bots, widgets y paquetes de pegatinas.", "Identity server": "Servidor de identidad", "Identity server (%(server)s)": "Servidor de identidad %(server)s", "Could not connect to identity server": "No se ha podido conectar al servidor de identidad", "Not a valid identity server (status code %(code)s)": "No es un servidor de identidad válido (código de estado %(code)s)", - "Identity server URL must be HTTPS": "La URL del servidor de identidad debe ser tipo HTTPS", + "Identity server URL must be HTTPS": "La URL del servidor de identidad debe ser HTTPS", "Unable to copy a link to the room to the clipboard.": "No se ha podido copiar el enlace a la sala.", "Unable to copy room link": "No se ha podido copiar el enlace a la sala", "Unnamed audio": "Audio sin título", @@ -3477,5 +3477,113 @@ "Copy Room Link": "Copiar enlace a la sala", "Displaying time": "Mostrando la hora", "IRC": "IRC", - "Use Ctrl + F to search timeline": "Usa Control + F para buscar dentro de la conversación" + "Use Ctrl + F to search timeline": "Usa Control + F para buscar dentro de la conversación", + "Please note upgrading will make a new version of the room. All current messages will stay in this archived room.": "Ten en cuenta que actualizar crea una nueva versión de la sala. Todos los mensajes hasta ahora quedarán archivados aquí, en esta sala.", + "Automatically invite members from this room to the new one": "Invitar a la nueva sala automáticamente miembros de esta", + "These are likely ones other room admins are a part of.": "Otros administradores de la sala estarán dentro.", + "Other spaces or rooms you might not know": "Otros espacios o salas que puede que no conozcas", + "Decide which spaces can access this room. If a space is selected, its members can find and join .": "Decide qué espacios pueden acceder a esta sala. Si seleccionas un espacio, sus miembros podrán encontrar y unirse a .", + "You're removing all spaces. Access will default to invite only": "Al quitar todos los espacios, el acceso por defecto pasará a ser «solo por invitación»", + "Only people invited will be able to find and join this space.": "Solo las personas invitadas podrán encontrar y unirse a este espacio.", + "Anyone will be able to find and join this space, not just members of .": "Cualquiera podrá encontrar y unirse a este espacio, incluso si no forman parte de .", + "Anyone in will be able to find and join.": "Cualquiera que forme parte de podrá encontrar y unirse.", + "%(senderName)s unbanned %(targetName)s": "%(senderName)s ha quitado el veto a %(targetName)s", + "%(senderName)s banned %(targetName)s": "%(senderName)s ha vetado a %(targetName)s", + "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s ha vetado a %(targetName)s: %(reason)s", + "New layout switcher (with message bubbles)": "Nuevo menú lateral (con burbujas de mensajes)", + "This makes it easy for rooms to stay private to a space, while letting people in the space find and join them. All new rooms in a space will have this option available.": "Esto hace que sea fácil tener salas privadas solo para un espacio, permitiendo que cualquier persona que forme parte del espacio pueda unirse a ellas. Todas las nuevas salas dentro del espacio tendrán esta opción disponible.", + "User %(userId)s is already invited to the room": "ya se ha invitado a %(userId)s a unirse a la sala", + "Screen sharing is here!": "¡Ya puedes compartir tu pantalla!", + "People with supported clients will be able to join the room without having a registered account.": "Las personas con una aplicación compatible podrán unirse a la sala sin tener que registrar una cuenta.", + "Anyone in a space can find and join. You can select multiple spaces.": "Cualquiera en un espacio puede encontrar y unirse. Puedes seleccionar varios espacios.", + "Anyone in %(spaceName)s can find and join. You can select other spaces too.": "Cualquiera en %(spaceName)s puede encontrar y unirse. También puedes seleccionar otros espacios.", + "Spaces with access": "Espacios con acceso", + "Anyone in a space can find and join. Edit which spaces can access here.": "Cualquiera en un espacio puede encontrar y unirse. Ajusta qué espacios pueden acceder desde aquí.", + "Currently, %(count)s spaces have access|other": "Ahora mismo, %(count)s espacios tienen acceso", + "& %(count)s more|other": "y %(count)s más", + "Upgrade required": "Actualización necesaria", + "Anyone can find and join.": "Cualquiera puede encontrar y unirse.", + "Only invited people can join.": "Solo las personas invitadas pueden unirse.", + "Private (invite only)": "Privado (solo por invitación)", + "This upgrade will allow members of selected spaces access to this room without an invite.": "Esta actualización permitirá a los miembros de los espacios que elijas acceder a esta sala sin que tengas que invitarles.", + "Message bubbles": "Burbujas de mensaje", + "Show all rooms": "Ver todas las salas", + "Give feedback.": "Danos tu opinión.", + "Thank you for trying Spaces. Your feedback will help inform the next versions.": "Gracias por probar los espacios. Tu opinión nos ayudará a tomar decisiones sobre las próximas versiones.", + "Spaces feedback": "Danos tu opinión sobre los espacios", + "Spaces are a new feature.": "Los espacios son una funcionalidad nueva.", + "Your camera is still enabled": "Tu cámara todavía está encendida", + "Your camera is turned off": "Tu cámara está apagada", + "%(sharerName)s is presenting": "%(sharerName)s está presentando", + "You are presenting": "Estás presentando", + "All rooms you're in will appear in Home.": "En la página de inicio aparecerán todas las salas a las que te hayas unido.", + "To help space members find and join a private room, go to that room's Security & Privacy settings.": "Para ayudar a los miembros de tus espacios a encontrar y unirse a salas privadas, ve a los ajustes seguridad y privacidad de la sala en cuestión.", + "Help space members find private rooms": "Ayuda a los miembros de tus espacios a encontrar salas privadas", + "Help people in spaces to find and join private rooms": "Ayuda a la gente en tus espacios a encontrar y unirse a salas privadas", + "New in the Spaces beta": "Novedades en la beta de los espacios", + "We're working on this, but just want to let you know.": "Todavía estamos trabajando en esto, pero queríamos enseñártelo.", + "Search for rooms or spaces": "Buscar salas o espacios", + "Add space": "Añadir espacio", + "Spaces you know that contain this room": "Espacios que conoces que contienen esta sala", + "Search spaces": "Buscar espacios", + "Select spaces": "Elegir espacios", + "Are you sure you want to leave ?": "¿Seguro que quieres irte de ?", + "Leave %(spaceName)s": "Salir de %(spaceName)s", + "You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.": "Eres la única persona con permisos de administración en algunos de los espacios de los que quieres irte. Al salir de ellos, nadie podrá gestionarlos.", + "You're the only admin of this space. Leaving it will mean no one has control over it.": "Eres la única persona con permisos de administración en el espacio. Al salir, nadie podrá gestionarlo.", + "You won't be able to rejoin unless you are re-invited.": "No podrás volverte a unir hasta que te vuelvan a invitar.", + "Search %(spaceName)s": "Buscar en %(spaceName)s", + "Leave specific rooms and spaces": "Salir de algunas salas y espacios que yo elija", + "Don't leave any": "No salir de ningún sitio", + "Leave all rooms and spaces": "Salir de todas las salas y espacios", + "Want to add an existing space instead?": "¿Quieres añadir un espacio que ya exista?", + "Private space (invite only)": "Espacio privado (solo por invitación)", + "Space visibility": "Visibilidad del espacio", + "Add a space to a space you manage.": "Añade un espacio a dentro de otros espacio que gestiones.", + "Visible to space members": "Visible para los miembros del espacio", + "Public room": "Sala pública", + "Private room (invite only)": "Sala privada (solo por invitación)", + "Room visibility": "Visibilidad de la sala", + "Create a room": "Crear una sala", + "Only people invited will be able to find and join this room.": "Solo aquellas personas invitadas podrán encontrar y unirse a esta sala.", + "Anyone will be able to find and join this room.": "Todo el mundo podrá encontrar y unirse a esta sala.", + "Anyone will be able to find and join this room, not just members of .": "Cualquiera podrá encontrar y unirse a esta sala, incluso gente que no sea miembro de .", + "You can change this at any time from room settings.": "Puedes cambiar esto cuando quieras desde los ajustes de la sala.", + "Everyone in will be able to find and join this room.": "Todo el mundo en podrá encontrar y unirse a esta sala.", + "Adding spaces has moved.": "Hemos cambiado de sitio la creación de espacios.", + "Search for rooms": "Buscar salas", + "Search for spaces": "Buscar espacios", + "Create a new space": "Crear un nuevo espacio", + "Want to add a new space instead?": "¿Quieres añadir un espacio nuevo en su lugar?", + "Add existing space": "Añadir un espacio ya existente", + "Share content": "Compartir contenido", + "Application window": "Ventana concreta", + "Share entire screen": "Compartir toda la pantalla", + "Decrypting": "Descifrando", + "They didn't pick up": "No han cogido", + "Call again": "Volver a llamar", + "They declined this call": "Han rechazado la llamada", + "You declined this call": "Has rechazado la llamada", + "The voice message failed to upload.": "Ha fallado el envío del mensaje de voz.", + "You can now share your screen by pressing the \"screen share\" button during a call. You can even do this in audio calls if both sides support it!": "Ahora puedes compartir tu pantalla dándole al botón de «compartir pantalla» durante una llamada. ¡Hasta puedes hacerlo en una llamada de voz si ambas partes lo permiten!", + "Access": "Acceso", + "Decide who can join %(roomName)s.": "Decide quién puede unirse a %(roomName)s.", + "Space members": "Miembros del espacio", + "Missed call": "Llamada perdida", + "Call declined": "Llamada rechazada", + "Stop recording": "Dejar de grabar", + "Send voice message": "Enviar mensaje de voz", + "Olm version:": "Versión de Olm:", + "Mute the microphone": "Silenciar el micrófono", + "Unmute the microphone": "Activar el micrófono", + "Dialpad": "Teclado numérico", + "More": "Más", + "Show sidebar": "Ver menú lateral", + "Hide sidebar": "Ocultar menú lateral", + "Start sharing your screen": "Comparte tu pantalla", + "Stop sharing your screen": "Dejar de compartir la pantalla", + "Stop the camera": "Parar la cámara", + "Start the camera": "Iniciar la cámara", + "Surround selected text when typing special characters": "Rodear texto seleccionado al escribir caracteres especiales", + "Send pseudonymous analytics data": "Enviar datos estadísticos seudonimizados" } diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index bb7bd575ef..c76fdbe727 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -3521,5 +3521,63 @@ "The voice message failed to upload.": "Häälsõnumi üleslaadimine ei õnnestunud.", "Everyone in will be able to find and join this room.": "Kõik kogukonna liikmed saavad seda jututuba leida ning võivad temaga liituda.", "You can change this at any time from room settings.": "Sa saad seda alati jututoa seadistustest muuta.", - "Anyone will be able to find and join this room, not just members of .": "Mitte ainult kogukonna liikmed, vaid kõik saavad seda jututuba leida ja võivad temaga liituda." + "Anyone will be able to find and join this room, not just members of .": "Mitte ainult kogukonna liikmed, vaid kõik saavad seda jututuba leida ja võivad temaga liituda.", + "You declined this call": "Sina keeldusid kõnest", + "They declined this call": "Teine osapool keeldus kõnest", + "Call again": "Helista uuesti", + "They didn't pick up": "Teine osapool ei võtnud kõnet vastu", + "You are presenting": "Sina esitad", + "%(sharerName)s is presenting": "%(sharerName)s esitab", + "Your camera is turned off": "Sinu seadme kaamera on välja lülitatud", + "Your camera is still enabled": "Sinu seadme kaamera on jätkuvalt kasutusel", + "Screen sharing is here!": "Meil on nüüd olemas ekraanijagamine!", + "Share entire screen": "Jaga tervet ekraani", + "Application window": "Rakenduse aken", + "Share content": "Jaga sisu", + "Anyone will be able to find and join this room.": "Kõik saavad seda jututuba leida ja temaga liituda.", + "Leave %(spaceName)s": "Lahku %(spaceName)s kogukonnakeskusest", + "Are you sure you want to leave ?": "Kas oled kindel, et soovid lahkuda kogukonnakeskusest?", + "Decrypting": "Dekrüptin sisu", + "Search for rooms or spaces": "Otsi jututubasid või kogukondi", + "Spaces are a new feature.": "Kogukonnakeskused on uus funktsionaalsus.", + "Spaces feedback": "Tagasiside kogukonnakeskuste kohta", + "Give feedback.": "Jaga tagasisidet.", + "We're working on this, but just want to let you know.": "Me küll alles arendame seda võimalust, kuid soovisime, et tead, mis tulemas on.", + "All rooms you're in will appear in Home.": "Kõik sinu jututoad on nähtavad kodulehel.", + "Show all rooms": "Näita kõiki jututubasid", + "Leave all rooms and spaces": "Lahku kõikidest jututubadest ja kogukondadest", + "Don't leave any": "Ära lahku ühestki", + "Leave specific rooms and spaces": "Lahku neist jututubadest ja kogukondadest", + "Search %(spaceName)s": "Otsi %(spaceName)s kogukonnast", + "You won't be able to rejoin unless you are re-invited.": "Ilma uue kutseta sa ei saa uuesti liituda.", + "Want to add a new space instead?": "Kas sa selle asemel soovid lisada uut kogukonnakeskust?", + "Create a new space": "Loo uus kogukonnakeskus", + "Search for spaces": "Otsi kogukonnakeskusi", + "Search for rooms": "Otsi jututube", + "Adding spaces has moved.": "Kogukondade lisamine asub nüüd uues kohas.", + "Anyone in will be able to find and join.": "Kõik kogukonna liikmed saavad seda leida ning võivad temaga liituda.", + "Anyone will be able to find and join this space, not just members of .": "Mitte ainult kogukonna liikmed, vaid kõik saavad seda kogukonda leida ja võivad temaga liituda.", + "Only people invited will be able to find and join this space.": "See kogukond on leitav vaid kutse olemasolul ning liitumine on võimalik vaid kutse alusel.", + "Add a space to a space you manage.": "Lisa kogukond sellesse kogukonda, mida sa juba haldad.", + "Space visibility": "Kogukonna nähtavus", + "Private space (invite only)": "Privaatne kogukond (kutse alusel)", + "Want to add an existing space instead?": "Kas sa selle asemel soovid lisada olemasoleva kogukonnakeskuse?", + "Thank you for trying Spaces. Your feedback will help inform the next versions.": "Tänud, et katsetasid kogukonnakeskuseid. Sinu tagasiside alusel saame neid tulevikus paremaks teha.", + "You're the only admin of this space. Leaving it will mean no one has control over it.": "Sa oled selle kogukonna ainus haldaja. Kui lahkud, siis ei leidu enam kedagi, kellel oleks seal haldusõigusi.", + "You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.": "Mõnedes jututubades või kogukondades oled sa ainus haldaja. Kuna sa nüüd soovid neist lahkuda, siis jäävad nad haldajata.", + "Send pseudonymous analytics data": "Saada analüütilist teavet suvalise nime alt", + "Call declined": "Osapool keeldus kõnest", + "Missed call": "Vastamata kõne", + "Send voice message": "Saada häälsõnum", + "Stop recording": "Lõpeta salvestamine", + "Start the camera": "Võta kaamera kasutusele", + "Stop the camera": "Lõpeta kaamera kasutamine", + "Stop sharing your screen": "Lõpeta oma seadme ekraani jagamine", + "Start sharing your screen": "Alusta oma seadme ekraani jagamist", + "Hide sidebar": "Peida külgpaan", + "Show sidebar": "Näita külgpaani", + "More": "Veel", + "Dialpad": "Numbriklahvistik", + "Unmute the microphone": "Eemalda mikrofoni summutamine", + "Mute the microphone": "Summuta mikrofon" } diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index dec96522b9..f5e04a6581 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -3228,7 +3228,7 @@ "You have unverified logins": "Vous avez des sessions non-vérifiées", "Without verifying, you won’t have access to all your messages and may appear as untrusted to others.": "Sans vérification vous n’aurez pas accès à tous vos messages et n’apparaîtrez pas comme de confiance aux autres.", "Verify your identity to access encrypted messages and prove your identity to others.": "Vérifiez votre identité pour accéder aux messages chiffrés et prouver votre identité aux autres.", - "Use another login": "Utiliser un autre identifiant", + "Use another login": "Utiliser une autre session", "Please choose a strong password": "Merci de choisir un mot de passe fort", "You can add more later too, including already existing ones.": "Vous pourrez en ajouter plus tard, y compris certains déjà existant.", "Let's create a room for each of them.": "Créons un salon pour chacun d’entre eux.", @@ -3493,5 +3493,132 @@ "Use Command + F to search timeline": "Utilisez Commande + F pour rechercher dans le fil de discussion", "User %(userId)s is already invited to the room": "L’utilisateur %(userId)s est déjà invité dans le salon", "Transfer Failed": "Échec du transfert", - "Unable to transfer call": "Impossible de transférer l’appel" + "Unable to transfer call": "Impossible de transférer l’appel", + "Send pseudonymous analytics data": "Envoyer des données de télémétrie pseudonymisées", + "To help space members find and join a private room, go to that room's Security & Privacy settings.": "Pour aider les membres de l’espace à trouver et rejoindre un salon privé, allez dans les paramètres de confidentialité de ce salon.", + "Help space members find private rooms": "Aidez les membres de l’espace à trouver des salons privés", + "Help people in spaces to find and join private rooms": "Aidez les personnes dans les espaces à trouver et rejoindre des salons privés", + "New in the Spaces beta": "Nouveautés dans les espaces en bêta", + "Decide who can join %(roomName)s.": "Choisir qui peut rejoindre %(roomName)s.", + "Space members": "Membres de l’espace", + "Anyone in a space can find and join. You can select multiple spaces.": "Tout le monde dans un espace peut trouver et venir. Vous pouvez sélectionner plusieurs espaces.", + "Anyone in %(spaceName)s can find and join. You can select other spaces too.": "Tout le monde dans %(spaceName)s peut trouver et venir. Vous pouvez sélectionner d’autres espaces.", + "Spaces with access": "Espaces avec accès", + "Anyone in a space can find and join. Edit which spaces can access here.": "Tout le monde dans un espace peut trouver et venir. Éditer les accès des espaces ici.", + "Currently, %(count)s spaces have access|other": "%(count)s espaces ont actuellement l’accès", + "& %(count)s more|other": "& %(count)s de plus", + "Upgrade required": "Mise-à-jour nécessaire", + "Anyone can find and join.": "Tout le monde peut trouver et venir.", + "Only invited people can join.": "Seules les personnes invitées peuvent venir.", + "Private (invite only)": "Privé (sur invitation)", + "This upgrade will allow members of selected spaces access to this room without an invite.": "Cette mise-à-jour permettra aux membres des espaces sélectionnés d’accéder à ce salon sans invitation.", + "Message bubbles": "Message en bulles", + "IRC": "IRC", + "Show all rooms": "Afficher tous les salons", + "Give feedback.": "Écrire un commentaire.", + "Thank you for trying Spaces. Your feedback will help inform the next versions.": "Merci d’essayer les espaces. Vos commentaires permettront d’améliorer les prochaines versions.", + "Spaces feedback": "Commentaires sur les espaces", + "Spaces are a new feature.": "Les espaces sont une fonctionnalité nouvelle.", + "Your camera is still enabled": "Votre caméra est toujours allumée", + "Your camera is turned off": "Votre caméra est éteinte", + "%(sharerName)s is presenting": "%(sharerName)s est à l’écran", + "You are presenting": "Vous êtes à l’écran", + "All rooms you're in will appear in Home.": "Tous les salons dans lesquels vous vous trouvez apparaîtront sur l’Accueil.", + "New layout switcher (with message bubbles)": "Nouveau sélecteur de disposition (avec les messages en bulles)", + "This makes it easy for rooms to stay private to a space, while letting people in the space find and join them. All new rooms in a space will have this option available.": "Cela permet de garder facilement un salon privé dans un espace, tout en laissant la possibilité aux gens de trouver l’espace et de le rejoindre. Tous les nouveaux salons de cet espace auront cette option disponible.", + "Adding spaces has moved.": "L’ajout d’espaces a été déplacé.", + "Search for rooms": "Rechercher des salons", + "Search for spaces": "Rechercher des espaces", + "Create a new space": "Créer un nouvel espace", + "Want to add a new space instead?": "Vous voulez plutôt ajouter un nouvel espace ?", + "Add existing space": "Ajouter un espace existant", + "Share content": "Partager le contenu", + "Application window": "Fenêtre d’application", + "Share entire screen": "Partager l’écran entier", + "Image": "Image", + "Sticker": "Autocollant", + "Decrypting": "Déchiffrement", + "The call is in an unknown state!": "Cet appel est dans un état inconnu !", + "You missed this call": "Vous avez raté cet appel", + "This call has failed": "Cet appel a échoué", + "Unknown failure: %(reason)s)": "Échec inconnu : %(reason)s", + "An unknown error occurred": "Une erreur inconnue s’est produite", + "Their device couldn't start the camera or microphone": "Leur appareil n’a pas pu démarrer la caméra ou le microphone", + "Connection failed": "Connexion échouée", + "Could not connect media": "Impossible de se connecter au média", + "They didn't pick up": "Ils n’ont pas décroché", + "This call has ended": "Cet appel est terminé", + "Call again": "Appeler encore", + "Call back": "Rappeler", + "They declined this call": "Ils ont refusé cet appel", + "You declined this call": "Vous avez refusé cet appel", + "Connected": "Connecté", + "The voice message failed to upload.": "Ce message vocal n’a pas pu être envoyé.", + "Copy Room Link": "Copier le lien du salon", + "You can now share your screen by pressing the \"screen share\" button during a call. You can even do this in audio calls if both sides support it!": "Vous pouvez désormais partager votre écran à l’aide du bouton « partager l’écran » pendant un appel. Il est même possible de le faire pendant un appel audio si les deux parties le prennent en charge !", + "Screen sharing is here!": "Le partage d’écran est arrivé !", + "Access": "Accès", + "People with supported clients will be able to join the room without having a registered account.": "Les personnes utilisant un client pris en charge pourront rejoindre le salon sans compte.", + "We're working on this, but just want to let you know.": "Nous travaillons dessus, mais c'est juste pour vous le faire savoir.", + "Search for rooms or spaces": "Rechercher des salons ou des espaces", + "Unable to copy a link to the room to the clipboard.": "Impossible de copier le lien du salon dans le presse-papier.", + "Unable to copy room link": "Impossible de copier le lien du salon", + "Error downloading audio": "Erreur lors du téléchargement de l’audio", + "Unnamed audio": "Audio sans nom", + "Add space": "Ajouter un espace", + "Please note upgrading will make a new version of the room. All current messages will stay in this archived room.": "Veuillez notez que la mise-à-jour va créer une nouvelle version de ce salon. Tous les messages actuels resteront dans ce salon archivé.", + "Automatically invite members from this room to the new one": "Inviter automatiquement les membres de ce salon dans le nouveau", + "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.": "Ce salon est utilisé pour du contenu toxique ou illégal, ou les modérateurs ont échoué à modérer le contenu toxique ou illégal.\n Cela sera signalé aux administrateurs de %(homeserver)s.", + "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.": "Ce salon est utilisé pour du contenu toxique ou illégal, ou les modérateurs ont échoué à modérer le contenu toxique ou illégal.\nCela sera signalé aux administrateurs de %(homeserver)s. Les administrateurs ne pourront PAS lire le contenu chiffré ce salon.", + "These are likely ones other room admins are a part of.": "Ces autres administrateurs du salon en font probablement partie.", + "Other spaces or rooms you might not know": "Autres espaces ou salons que vous pourriez ne pas connaître", + "Spaces you know that contain this room": "Les espaces connus qui contiennent ce salon", + "Search spaces": "Rechercher des espaces", + "Decide which spaces can access this room. If a space is selected, its members can find and join .": "Choisir quels espaces peuvent accéder à ce salon. Si un espace est sélectionné, ses membres pourront trouver et rejoindre .", + "Select spaces": "Sélectionner des espaces", + "You're removing all spaces. Access will default to invite only": "Vous allez supprimer tous les espaces. L’accès se fera sur invitation uniquement par défaut", + "Are you sure you want to leave ?": "Êtes-vous sûr de quitter ?", + "Leave %(spaceName)s": "Quitter %(spaceName)s", + "You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.": "Vous êtes le seul administrateur de certains salons ou espaces que vous souhaitez quitter. En les quittant, vous les laisserez sans aucun administrateur.", + "You're the only admin of this space. Leaving it will mean no one has control over it.": "Vous êtes le seul administrateur de cet espace. En le quittant, plus personne n’aura le contrôle dessus.", + "You won't be able to rejoin unless you are re-invited.": "Il vous sera impossible de revenir à moins d’y être réinvité.", + "Search %(spaceName)s": "Rechercher %(spaceName)s", + "Leave specific rooms and spaces": "Laisser certains salons et espaces", + "Don't leave any": "Ne rien quitter", + "Leave all rooms and spaces": "Quitter tous les salons et les espaces", + "Want to add an existing space instead?": "Vous voulez plutôt ajouter un espace existant ?", + "Private space (invite only)": "Espace privé (uniquement sur invitation)", + "Space visibility": "Visibilité de l’espace", + "Add a space to a space you manage.": "Ajouter un espace à l’espace que vous gérez.", + "Only people invited will be able to find and join this space.": "Seules les personnes invitées pourront trouver et rejoindre cet espace.", + "Anyone will be able to find and join this space, not just members of .": "Quiconque pourra trouver et rejoindre cet espace, pas seulement les membres de .", + "Anyone in will be able to find and join.": "Tous les membres de pourront trouver et venir.", + "Visible to space members": "Visible pour les membres de l'espace", + "Public room": "Salon public", + "Private room (invite only)": "Salon privé (uniquement sur invitation)", + "Room visibility": "Visibilité du salon", + "Create a room": "Créer un salon", + "Only people invited will be able to find and join this room.": "Seules les personnes invitées pourront trouver et rejoindre ce salon.", + "Anyone will be able to find and join this room.": "Quiconque pourra trouver et rejoindre ce salon.", + "Anyone will be able to find and join this room, not just members of .": "Quiconque pourra trouver et rejoindre ce salon, pas seulement les membres de .", + "You can change this at any time from room settings.": "Vous pouvez changer ceci n’importe quand depuis les paramètres du salon.", + "Everyone in will be able to find and join this room.": "Tout le monde dans pourra trouver et rejoindre ce salon.", + "%(oneUser)schanged the pinned messages for the room %(count)s times.|other": "%(oneUser)s a changé %(count)s fois les messages épinglés du salon.", + "%(severalUsers)schanged the pinned messages for the room %(count)s times.|other": "%(severalUsers)s ont changé %(count)s fois les messages épinglés du salon.", + "Missed call": "Appel manqué", + "Call declined": "Appel rejeté", + "Stop recording": "Arrêter l’enregistrement", + "Send voice message": "Envoyer un message vocal", + "Olm version:": "Version de Olm :", + "Mute the microphone": "Désactiver le microphone", + "Unmute the microphone": "Activer le microphone", + "Dialpad": "Pavé numérique", + "More": "Plus", + "Show sidebar": "Afficher la barre latérale", + "Hide sidebar": "Masquer la barre latérale", + "Start sharing your screen": "Commencer à partager mon écran", + "Stop sharing your screen": "Arrêter de partager mon écran", + "Stop the camera": "Arrêter la caméra", + "Start the camera": "Démarrer la caméra", + "Surround selected text when typing special characters": "Entourer le texte sélectionné lors de la saisie de certains caractères" } diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 856a783497..90c120abb1 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -3540,5 +3540,110 @@ "IRC": "IRC", "New layout switcher (with message bubbles)": "Nova disposición do control (con burbullas con mensaxes)", "Image": "Imaxe", - "Sticker": "Adhesivo" + "Sticker": "Adhesivo", + "To help space members find and join a private room, go to that room's Security & Privacy settings.": "Axudarlle aos membros do espazo a que atopen e se unan a salas privadas, vaite aos axustes de Seguridade e Privacidade desa sala.", + "Help space members find private rooms": "Axudarlle aos membros do espazo a que atopen salas privadas", + "Help people in spaces to find and join private rooms": "Axudarlle ás persoas en espazos que atopen e se unan a salas privadas", + "New in the Spaces beta": "Novo na beta de Espazos", + "Error downloading audio": "Erro ao descargar o audio", + "Please note upgrading will make a new version of the room. All current messages will stay in this archived room.": "Ten en conta que a actualización creará unha nova versión da sala. Tódalas mensaxes actuais permanecerán nesta sala arquivada.", + "Automatically invite members from this room to the new one": "Convidar automáticamente membros desta sala á nova sala", + "These are likely ones other room admins are a part of.": "Probablemente estas son salas das que forman parte outras administradoras da sala.", + "Other spaces or rooms you might not know": "Outros espazos ou salas que poderías coñecer", + "Spaces you know that contain this room": "Espazos que coñeces que conteñen a esta sala", + "Search spaces": "Buscar espazos", + "Decide which spaces can access this room. If a space is selected, its members can find and join .": "Decide que espazos poderán acceder a esta sala. Se un espazo é elexido, os seus membros poderán atopar e unirse a .", + "Select spaces": "Elixe espazos", + "You're removing all spaces. Access will default to invite only": "Vas eliminar tódolos espazos. Por defecto o acceso cambiará a só por convite", + "Room visibility": "Visibilidade da sala", + "Visible to space members": "Visible para membros do espazo", + "Public room": "Sala pública", + "Private room (invite only)": "Sala privada (só con convite)", + "Create a room": "Crear unha sala", + "Only people invited will be able to find and join this room.": "Só as persoas convidadas poderán atopar e unirse a esta sala.", + "Anyone will be able to find and join this room.": "Calquera poderá atopar e unirse a esta sala.", + "Anyone will be able to find and join this room, not just members of .": "Calquera poderá atopar e unirse a esta sala, non só os membros de .", + "You can change this at any time from room settings.": "Podes cambiar isto en calquera momento nos axustes da sala.", + "Everyone in will be able to find and join this room.": "Todas en poderán atopar e unirse a esta sala.", + "Share content": "Compartir contido", + "Application window": "Ventá da aplicación", + "Share entire screen": "Compartir pantalla completa", + "They didn't pick up": "Non respondeu", + "Call again": "Chamar outra vez", + "They declined this call": "Rexeitou esta chamada", + "You declined this call": "Rexeitaches esta chamada", + "The voice message failed to upload.": "Fallou a subida da mensaxe de voz.", + "You can now share your screen by pressing the \"screen share\" button during a call. You can even do this in audio calls if both sides support it!": "Podes compartir a túa pantalla premendo no botón \"compartir pantalla\" durante unha chamada. Incluso podes facelo nas chamadas de audio se as dúas partes teñen soporte!", + "Screen sharing is here!": "Aquí tes a compartición de pantalla!", + "Access": "Acceder", + "People with supported clients will be able to join the room without having a registered account.": "As persoas con clientes habilitados poderán unirse a sala sen ter que posuir unha conta rexistrada.", + "Decide who can join %(roomName)s.": "Decidir quen pode unirse a %(roomName)s.", + "Space members": "Membros do espazo", + "Anyone in a space can find and join. You can select multiple spaces.": "Calquera nun espazo pode atopar e unirse. Podes elexir múltiples espazos.", + "Anyone in %(spaceName)s can find and join. You can select other spaces too.": "Calquera en %(spaceName)s pode atopar e unirse. Podes elexir outros espazos tamén.", + "Spaces with access": "Espazos con acceso", + "Anyone in a space can find and join. Edit which spaces can access here.": "Calquera nun espazo pode atopala e unirse. Editar que espazos poden acceder aquí.", + "Currently, %(count)s spaces have access|other": "Actualmente, %(count)s espazos teñen acceso", + "& %(count)s more|other": "e %(count)s máis", + "Upgrade required": "Actualización requerida", + "Anyone can find and join.": "Calquera pode atopala e unirse.", + "Only invited people can join.": "Só se poden unir persoas con convite.", + "Private (invite only)": "Privada (só con convite)", + "This upgrade will allow members of selected spaces access to this room without an invite.": "Esta actualización permitirá que os membros dos espazos seleccionados teñan acceso á sala sen precisar convite.", + "Your camera is still enabled": "A túa cámara aínda está acendida", + "Your camera is turned off": "A túa cámara está apagada", + "%(sharerName)s is presenting": "%(sharerName)s estase presentando", + "You are presenting": "Estaste a presentar", + "This makes it easy for rooms to stay private to a space, while letting people in the space find and join them. All new rooms in a space will have this option available.": "Esto facilita que as salas permanezan privadas respecto do espazo, mais permitindo que as persoas do espazo as atopen e se unan a elas. Tódalas novas salas do espazo terán esta opción dispoñible.", + "We're working on this, but just want to let you know.": "Estamos a traballar nisto, só queriamos facercho saber.", + "Search for rooms or spaces": "Buscar salas ou espazos", + "Want to add an existing space instead?": "Queres engadir un espazo xa existente?", + "Private space (invite only)": "Espazo privado (só convidadas)", + "Space visibility": "Visibilidade do espazo", + "Add a space to a space you manage.": "Engade un espazo ao espazo que ti xestionas.", + "Only people invited will be able to find and join this space.": "Só as persoas convidadas poderán atopar e unirse a este espazo.", + "Anyone will be able to find and join this space, not just members of .": "Calquera poderá atopar e unirse a este espazo, non só os membros de .", + "Anyone in will be able to find and join.": "Calquera en poderá atopar e unirse.", + "Adding spaces has moved.": "Engadir espazos moveuse.", + "Search for rooms": "Buscar salas", + "Search for spaces": "Buscar espazos", + "Create a new space": "Crear un novo espazo", + "Want to add a new space instead?": "Queres engadir un espazo no seu lugar?", + "Add existing space": "Engadir un espazo existente", + "Add space": "Engadir espazo", + "Give feedback.": "Dar opinión.", + "Thank you for trying Spaces. Your feedback will help inform the next versions.": "Grazas por probar Espazos. A túa opinión vainos axudar as próximas versións.", + "Spaces feedback": "Infórmanos sobre Espazos", + "Spaces are a new feature.": "Espazos é o futuro.", + "Are you sure you want to leave ?": "Tes a certeza de querer saír de ?", + "Leave %(spaceName)s": "Saír de %(spaceName)s", + "You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.": "Es a única administradora dalgunhas salas ou espazos dos que queres sair. Ao sair deles deixaralos sen administración.", + "You're the only admin of this space. Leaving it will mean no one has control over it.": "Ti es a única administradora deste espazo. Ao sair farás que a ninguén teña control sobre el.", + "You won't be able to rejoin unless you are re-invited.": "Non poderás volver a unirte se non te volven a convidar.", + "Search %(spaceName)s": "Buscar %(spaceName)s", + "Leave specific rooms and spaces": "Saír de determinadas salas e espazos", + "Don't leave any": "Non saír de ningunha", + "Leave all rooms and spaces": "Saír de tódalas salas e espazos", + "Decrypting": "Descifrando", + "Show all rooms": "Mostar tódalas salas", + "All rooms you're in will appear in Home.": "Tódalas salas nas que estás aparecerán en Inicio.", + "Send pseudonymous analytics data": "Enviar datos anónimos de uso", + "%(oneUser)schanged the pinned messages for the room %(count)s times.|other": "%(oneUser)s cambiou as mensaxes fixadas da sala %(count)s veces.", + "%(severalUsers)schanged the pinned messages for the room %(count)s times.|other": "%(severalUsers)s cambiaron as mensaxes fixadas da sala %(count)s veces.", + "Missed call": "Chamada perdida", + "Call declined": "Chamada rexeitada", + "Stop recording": "Deter a gravación", + "Send voice message": "Enviar mensaxe de voz", + "Olm version:": "Version olm:", + "Mute the microphone": "Apagar o micrófono", + "Unmute the microphone": "Reactivar o micrófono", + "Dialpad": "Teclado", + "More": "Máis", + "Show sidebar": "Mostrar a barra lateral", + "Hide sidebar": "Agochar barra lateral", + "Start sharing your screen": "Comparte a túa pantalla", + "Stop sharing your screen": "Deixar de compartir a pantalla", + "Stop the camera": "Pechar a cámara", + "Start the camera": "Abrir a cámara", + "Surround selected text when typing special characters": "Rodea o texto seleccionado ao escribir caracteres especiais" } diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 55adba87b2..a616d0cc23 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -77,7 +77,7 @@ "Command error": "Parancs hiba", "Commands": "Parancsok", "Confirm password": "Jelszó megerősítése", - "Create Room": "Szoba készítése", + "Create Room": "Szoba létrehozása", "Cryptography": "Titkosítás", "Current password": "Jelenlegi jelszó", "Custom": "Egyedi", @@ -1318,7 +1318,7 @@ "Use an email address to recover your account": "A felhasználói fiók visszaszerzése e-mail címmel", "Enter email address (required on this homeserver)": "E-mail cím megadása (ezen a matrix szerveren kötelező)", "Doesn't look like a valid email address": "Az e-mail cím nem tűnik érvényesnek", - "Enter password": "Jelszó megadása", + "Enter password": "Adja meg a jelszót", "Password is allowed, but unsafe": "A jelszó engedélyezett, de nem biztonságos", "Nice, strong password!": "Szép, erős jelszó!", "Passwords don't match": "A jelszavak nem egyeznek meg", @@ -1503,7 +1503,7 @@ "View": "Nézet", "Find a room…": "Szoba keresése…", "Find a room… (e.g. %(exampleRoom)s)": "Szoba keresése… (pl.: %(exampleRoom)s)", - "If you can't find the room you're looking for, ask for an invite or Create a new room.": "Ha nem találod a szobát amit keresel kérj egy meghívót vagy Készíts egy új szobát.", + "If you can't find the room you're looking for, ask for an invite or Create a new room.": "Ha nem találod a szobát amit keresel, kérj egy meghívót vagy készíts egy új szobát.", "Explore rooms": "Szobák felderítése", "Verify the link in your inbox": "Ellenőrizd a hivatkozást a bejövő leveleid között", "Complete": "Kiegészít", @@ -1518,12 +1518,12 @@ "e.g. my-room": "pl.: szobam", "Please enter a name for the room": "Kérlek adj meg egy nevet a szobához", "This room is private, and can only be joined by invitation.": "A szoba zárt, csak meghívóval lehet belépni.", - "Create a public room": "Nyilvános szoba készítése", - "Create a private room": "Zárt szoba készítése", + "Create a public room": "Nyilvános szoba létrehozása", + "Create a private room": "Privát szoba létrehozása", "Topic (optional)": "Téma (nem kötelező)", "Make this room public": "A szoba legyen nyilvános", - "Hide advanced": "Haladó elrejtése", - "Show advanced": "Speciális megjelenítése", + "Hide advanced": "Speciális beállítások elrejtése", + "Show advanced": "Speciális beállítások megjelenítése", "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)": "Más szervereken lévő felhasználók belépésének letiltása-csak helyi szoba (Ezt a beállítást később nem lehet megváltoztatni!)", "Close dialog": "Ablak bezárása", "Show previews/thumbnails for images": "Előnézet/bélyegkép mutatása a képekhez", @@ -1736,7 +1736,7 @@ "Show more": "Több megjelenítése", "Recent Conversations": "Legújabb Beszélgetések", "Direct Messages": "Közvetlen Beszélgetések", - "Go": "Menj", + "Go": "Meghívás", "Show info about bridges in room settings": "Híd információk megmutatása a szoba beállításoknál", "This bridge is managed by .": "Ezt a hidat ez a felhasználó kezeli: .", "Suggestions": "Javaslatok", @@ -2113,7 +2113,7 @@ "Liberate your communication": "Kommunikálj szabadon", "Send a Direct Message": "Közvetlen üzenet küldése", "Explore Public Rooms": "Nyilvános szobák felfedezése", - "Create a Group Chat": "Készíts Csoportos Beszélgetést", + "Create a Group Chat": "Készíts csoportos beszélgetést", "Self-verification request": "Ön ellenőrzés kérése", "Cancel replying to a message": "Üzenet válasz megszakítása", "Confirm adding email": "E-mail hozzáadásának megerősítése", @@ -2446,7 +2446,7 @@ "Cross-signing and secret storage are ready for use.": "Az eszközök közti hitelesítés és a biztonsági tároló kész a használatra.", "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Az eszközök közti hitelesítés kész a használatra, de a biztonsági tároló nincs használva a kulcsok mentéséhez.", "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Privát szobák csak meghívóval találhatók meg és meghívóval lehet belépni. A nyilvános szobákat bárki megtalálhatja és be is léphet.", - "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Privát szobák csak meghívóval találhatók meg és meghívóval lehet belépni. A nyilvános szobákat a közösség bármely tagja megtalálhatja és be is léphet.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "A privát szobák csak meghívóval találhatók meg és csak meghívóval lehet belépni. A nyilvános szobákat a közösség bármely tagja megtalálhatja és be is léphet.", "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Beállíthatod, ha a szobát csak egy belső csoport használja majd a matrix szervereden. Ezt később nem lehet megváltoztatni.", "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Ne engedélyezd ezt, ha a szobát külső csapat is használja másik matrix szerverről. Később nem lehet megváltoztatni.", "Block anyone not part of %(serverName)s from ever joining this room.": "A szobába ne léphessenek be azok, akik nem ezen a szerveren vannak: %(serverName)s.", @@ -3373,7 +3373,7 @@ "Kick, ban, or invite people to this room, and make you leave": "Kirúgni, kitiltani vagy meghívni embereket ebbe a szobába és, hogy ön elhagyja a szobát", "Currently joining %(count)s rooms|one": "%(count)s szobába lép be", "Currently joining %(count)s rooms|other": "%(count)s szobába lép be", - "No results for \"%(query)s\"": "Nincs találat ehhez: %(query)s", + "No results for \"%(query)s\"": "Nincs találat erre: %(query)s", "Try different words or check for typos. Some results may not be visible as they're private and you need an invite to join them.": "Próbáljon ki más szavakat vagy keressen elgépelést. Néhány találat azért nem látszik, mert privát és meghívóra van szüksége, hogy csatlakozhasson.", "The user you called is busy.": "A hívott felhasználó foglalt.", "User Busy": "Felhasználó foglalt", @@ -3490,15 +3490,153 @@ "Use Ctrl + F to search timeline": "Ctrl + F az idővonalon való kereséshez", "User %(userId)s is already invited to the room": "%(userId)s felhasználó már kapott meghívót a szobába", "Integration manager": "Integrációs Menedzser", - "Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.": "A %(brand)sod nem használhat ehhez Integrációs Menedzsert. Kérlek vedd fel a kapcsolatot az adminisztrátorral.", + "Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.": "A %(brand)s nem használhat Integrációs Menedzsert. Kérem vegye fel a kapcsolatot az adminisztrátorral.", "Using this widget may share data with %(widgetDomain)s & your integration manager.": "Ennek a kisalkalmazásnak a használata adatot oszthat meg a(z) %(widgetDomain)s oldallal és az Integrációkezelővel.", "Identity server is": "Azonosítási szerver", - "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integrációs Menedzser megkapja a konfigurációt, módosíthat kisalkalmazásokat, szobához meghívót küldhet és a hozzáférési szintet beállíthatja helyetted.", - "Use an integration manager to manage bots, widgets, and sticker packs.": "Használj Integrációs Menedzsert a botok, kisalkalmazások és matrica csomagok kezeléséhez.", - "Use an integration manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Használj Integrációs Menedzsert (%(serverName)s) a botok, kisalkalmazások és matrica csomagok kezeléséhez.", + "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integrációs Menedzser megkapja a konfigurációt, módosíthat kisalkalmazásokat, szobához meghívót küldhet és a hozzáférési szintet állíthat be az ön nevében.", + "Use an integration manager to manage bots, widgets, and sticker packs.": "Használjon Integrációs Menedzsert a botok, kisalkalmazások és matrica csomagok kezeléséhez.", + "Use an integration manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Használjon Integrációs Menedzsert (%(serverName)s) a botok, kisalkalmazások és matrica csomagok kezeléséhez.", "Identity server": "Azonosító szerver", "Identity server (%(server)s)": "Azonosítási kiszolgáló (%(server)s)", "Could not connect to identity server": "Az Azonosítási Szerverhez nem lehet csatlakozni", "Not a valid identity server (status code %(code)s)": "Az Azonosítási Szerver nem érvényes (státusz kód: %(code)s)", - "Identity server URL must be HTTPS": "Az Azonosítási Szerver URL-jének HTTPS-nek kell lennie" + "Identity server URL must be HTTPS": "Az Azonosítási Szerver URL-jének HTTPS-nek kell lennie", + "Unable to copy a link to the room to the clipboard.": "Ennek a szobának a hivatkozását nem sikerül a vágólapra másolni.", + "Unable to copy room link": "A szoba hivatkozása nem másolható", + "Error downloading audio": "Hiba a hang letöltésekor", + "Please note upgrading will make a new version of the room. All current messages will stay in this archived room.": "Vegye figyelembe, hogy a fejlesztés a szoba új verzióját hozza létre Minden jelenlegi üzenet itt marad az archivált szobában.", + "Automatically invite members from this room to the new one": "Tagok automatikus meghívása ebből a szobából az újba", + "Other spaces or rooms you might not know": "Más terek vagy szobák melyről lehet, hogy nem tud", + "Spaces you know that contain this room": "Terek melyről tudja, hogy ezt a szobát tartalmazzák", + "Search spaces": "Terek keresése", + "Decide which spaces can access this room. If a space is selected, its members can find and join .": "Döntse el melyik terek férhetnek hozzá ehhez a szobához. Ha a tér ki van választva a tagsága megtalálhatja és beléphet ebbe a szobába: .", + "Select spaces": "Terek kiválasztása", + "You're removing all spaces. Access will default to invite only": "Minden teret töröl. A hozzáférés alapállapota „csak meghívóval” lesz", + "User Directory": "Felhasználójegyzék", + "Room visibility": "Szoba láthatóság", + "Visible to space members": "Tér tagság számára látható", + "Public room": "Nyilvános szoba", + "Private room (invite only)": "Privát szoba (csak meghívóval)", + "Create a room": "Szoba létrehozása", + "Only people invited will be able to find and join this room.": "Csak a meghívott emberek fogják megtalálni és tudnak belépni a szobába.", + "Anyone will be able to find and join this room, not just members of .": "Bárki megtalálhatja és beléphet a szobába, nem csak tér tagsága.", + "You can change this at any time from room settings.": "A szoba beállításokban ezt bármikor megváltoztathatja.", + "Everyone in will be able to find and join this room.": " téren bárki megtalálhatja és beléphet a szobába.", + "Share content": "Tartalom megosztása", + "Application window": "Alkalmazás ablak", + "Share entire screen": "A teljes képernyő megosztása", + "Image": "Kép", + "Sticker": "Matrica", + "Downloading": "Letöltés", + "The call is in an unknown state!": "A hívás ismeretlen állapotban van!", + "You missed this call": "Elmulasztotta a hívást", + "This call has failed": "Hívás nem sikerült", + "Unknown failure: %(reason)s)": "Ismeretlen hiba: %(reason)s)", + "An unknown error occurred": "Ismeretlen hiba történt", + "Their device couldn't start the camera or microphone": "A másik fél eszköze nem képes használni a kamerát vagy a mikrofont", + "Connection failed": "Kapcsolódás sikertelen", + "Could not connect media": "Média kapcsolat nem hozható létre", + "They didn't pick up": "Nem vették fel", + "This call has ended": "Hívás befejeződött", + "Call again": "Hívás újra", + "Call back": "Visszahívás", + "They declined this call": "Elutasították a hívást", + "You declined this call": "Elutasította ezt a hívást", + "Connected": "Kapcsolódva", + "The voice message failed to upload.": "A hangüzenet feltöltése sikertelen.", + "Copy Room Link": "Szoba hivatkozás másolása", + "You can now share your screen by pressing the \"screen share\" button during a call. You can even do this in audio calls if both sides support it!": "Már megoszthatja a képernyőjét hívás közben a \"képernyő megosztás\" gombra kattintva. Még hanghívás közben is működik ha mind a két fél támogatja.", + "Screen sharing is here!": "Képernyőmegosztás itt van!", + "Access": "Hozzáférés", + "People with supported clients will be able to join the room without having a registered account.": "Emberek támogatott kliensekkel, még regisztrált fiók nélkül is, beléphetnek a szobába.", + "Decide who can join %(roomName)s.": "Döntse el ki léphet be ide: %(roomName)s.", + "Space members": "Tér tagság", + "Anyone in a space can find and join. You can select multiple spaces.": "A téren bárki megtalálhatja és beléphet. Több teret is kiválaszthat egyidejűleg.", + "Anyone in %(spaceName)s can find and join. You can select other spaces too.": "%(spaceName)s téren bárki megtalálhatja és beléphet. Kiválaszthat más tereket is.", + "Spaces with access": "Terek hozzáféréssel", + "Anyone in a space can find and join. Edit which spaces can access here.": "A téren bárki megtalálhatja és beléphet. Szerkessze meg melyik tér férhet hozzá ehhez.", + "Currently, %(count)s spaces have access|other": "Jelenleg %(count)s tér rendelkezik hozzáféréssel", + "& %(count)s more|other": "és még %(count)s", + "Upgrade required": "Fejlesztés szükséges", + "Anyone can find and join.": "Bárki megtalálhatja és beléphet.", + "Only invited people can join.": "Csak a meghívott emberek léphetnek be.", + "Private (invite only)": "Privát (csak meghívóval)", + "This upgrade will allow members of selected spaces access to this room without an invite.": "Ehhez a szobához ez a fejlesztés hozzáférést ad a kijelölt térhez tartozó tagoknak meghívó nélkül is.", + "Message bubbles": "Üzenet buborékok", + "IRC": "IRC", + "There was an error loading your notification settings.": "Az értesítés beállítások betöltésénél hiba történt.", + "Mentions & keywords": "Megemlítések és kulcsszavak", + "Global": "Globális", + "New keyword": "Új kulcsszó", + "Keyword": "Kulcsszó", + "Enable email notifications for %(email)s": "E-mail értesítés engedélyezése ehhez az e-mail címhez: %(email)s", + "Enable for this account": "Engedélyezés ennél a fióknál", + "An error occurred whilst saving your notification preferences.": "Hiba történt az értesítési beállításai mentése közben.", + "Error saving notification preferences": "Hiba az értesítési beállítások mentésekor", + "Messages containing keywords": "Az üzenetek kulcsszavakat tartalmaznak", + "Your camera is still enabled": "Az ön kamerája még be van kapcsolva", + "Your camera is turned off": "Az ön kamerája ki van kapcsolva", + "%(sharerName)s is presenting": "%(sharerName)s tartja a bemutatót", + "You are presenting": "Ön tartja a bemutatót", + "New layout switcher (with message bubbles)": "Új kinézet váltó (üzenet buborékokkal)", + "New in the Spaces beta": "Újdonság a béta Terekben", + "Transfer Failed": "Átadás sikertelen", + "Unable to transfer call": "A hívás átadása nem lehetséges", + "Anyone will be able to find and join this room.": "Bárki megtalálhatja és beléphet ebbe a szobába.", + "This makes it easy for rooms to stay private to a space, while letting people in the space find and join them. All new rooms in a space will have this option available.": "A szobák egyszerűbben maradhatnak privátok a téren kívül, amíg a tér tagsága megtalálhatja és beléphet oda. Minden új szoba a téren rendelkezik ezzel a beállítási lehetőséggel.", + "To help space members find and join a private room, go to that room's Security & Privacy settings.": "Ahhoz hogy segíthessen a tér tagságának privát szobák megtalálásában és a belépésben, lépjen be a szoba Biztonság és adatvédelem beállításaiba.", + "Help space members find private rooms": "Segítsen a tér tagságának privát szobák megtalálásában", + "Help people in spaces to find and join private rooms": "Segítsen a téren az embereknek privát szobák megtalálásába és a belépésben", + "We're working on this, but just want to let you know.": "Dolgozunk rajta, csak szerettük volna tudatni.", + "Search for rooms or spaces": "Szobák vagy terek keresése", + "Want to add an existing space instead?": "Inkább meglévő teret adna hozzá?", + "Private space (invite only)": "Privát tér (csak meghívóval)", + "Space visibility": "Tér láthatósága", + "Add a space to a space you manage.": "Adjon hozzá az ön által kezelt térhez.", + "Only people invited will be able to find and join this space.": "Csak a meghívott emberek fogják megtalálni és tudnak belépni erre a térre.", + "Anyone will be able to find and join this space, not just members of .": "Bárki megtalálhatja és beléphet a térbe, nem csak tér tagsága.", + "Anyone in will be able to find and join.": " téren bárki megtalálhatja és beléphet.", + "Adding spaces has moved.": "Terek hozzáadása elköltözött.", + "Search for rooms": "Szobák keresése", + "Search for spaces": "Terek keresése", + "Create a new space": "Új tér készítése", + "Want to add a new space instead?": "Inkább új teret adna hozzá?", + "Add existing space": "Meglévő tér hozzáadása", + "Add space": "Tér hozzáadása", + "Give feedback.": "Adjon visszajelzést.", + "Thank you for trying Spaces. Your feedback will help inform the next versions.": "Köszönet a Terek használatáért. A visszajelzése segít a következő verzió kialakításában.", + "Spaces feedback": "Visszajelzés a Terekről", + "Spaces are a new feature.": "Terek az új lehetőség.", + "These are likely ones other room admins are a part of.": "Ezek valószínűleg olyanok, amelyeknek más szoba adminok is tagjai.", + "Don't leave any": "Sehonnan ne lépjen ki", + "Leave all rooms and spaces": "Minden szoba és tér elhagyása", + "Show all rooms": "Minden szoba megjelenítése", + "All rooms you're in will appear in Home.": "Minden szoba amibe belépett megjelenik a Kezdő téren.", + "Are you sure you want to leave ?": "Biztos, hogy kilép innen: ?", + "Leave %(spaceName)s": "Kilép innen: %(spaceName)s", + "You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.": "Ön az adminisztrátora néhány szobának vagy térnek amiből ki szeretne lépni. Ha kilép belőlük akkor azok adminisztrátor nélkül maradnak.", + "You're the only admin of this space. Leaving it will mean no one has control over it.": "Ön az egyetlen adminisztrátora a térnek. Ha kilép, senki nem tudja irányítani.", + "You won't be able to rejoin unless you are re-invited.": "Nem fog tudni újra belépni amíg nem hívják meg újra.", + "Search %(spaceName)s": "Keresés: %(spaceName)s", + "Leave specific rooms and spaces": "Kilépés a megadott szobákból és terekből", + "Decrypting": "Visszafejtés", + "Send pseudonymous analytics data": "Pseudo-anonim felhasználási adatok küldése", + "Missed call": "Nem fogadott hívás", + "Call declined": "Hívás elutasítva", + "%(oneUser)schanged the pinned messages for the room %(count)s times.|other": "%(oneUser)s %(count)s alkalommal megváltoztatta a szoba kitűzött üzeneteit.", + "%(severalUsers)schanged the pinned messages for the room %(count)s times.|other": "%(severalUsers)s %(count)s alkalommal megváltoztatta a szoba kitűzött üzeneteit.", + "Olm version:": "Olm verzió:", + "Stop recording": "Felvétel megállítása", + "Send voice message": "Hang üzenet küldése", + "Mute the microphone": "Mikrofon némítása", + "Unmute the microphone": "Mikrofon némításának megszüntetése", + "Dialpad": "Tárcsázó", + "More": "Több", + "Show sidebar": "Oldalsáv megjelenítése", + "Hide sidebar": "Oldalsáv elrejtése", + "Start sharing your screen": "Képernyőmegosztás indítása", + "Stop sharing your screen": "Képernyőmegosztás kikapcsolása", + "Stop the camera": "Kamera kikapcsolása", + "Start the camera": "Kamera bekapcsolása", + "Surround selected text when typing special characters": "Kijelölt szöveg körülvétele speciális karakterek beírásakor" } diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 20116411d5..8d15449973 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -3583,5 +3583,66 @@ "They didn't pick up": "Non ha risposto", "Call again": "Richiama", "They declined this call": "Ha rifiutato questa chiamata", - "You declined this call": "Hai rifiutato questa chiamata" + "You declined this call": "Hai rifiutato questa chiamata", + "We're working on this, but just want to let you know.": "Ci stiamo lavorando, ma volevamo almeno fartelo sapere.", + "Search for rooms or spaces": "Cerca stanze o spazi", + "Add space": "Aggiungi spazio", + "Are you sure you want to leave ?": "Vuoi veramente uscire da ?", + "Leave %(spaceName)s": "Esci da %(spaceName)s", + "You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.": "Sei l'unico amministratore di alcune delle stanze o spazi che vuoi abbandonare. Se esci li lascerai senza amministratori.", + "You're the only admin of this space. Leaving it will mean no one has control over it.": "Sei l'unico amministratore di questo spazio. Se esci nessuno ne avrà il controllo.", + "You won't be able to rejoin unless you are re-invited.": "Non potrai rientrare a meno che non ti invitino di nuovo.", + "Search %(spaceName)s": "Cerca %(spaceName)s", + "Leave specific rooms and spaces": "Esci da stanze e spazi specifici", + "Don't leave any": "Non uscire da nulla", + "Leave all rooms and spaces": "Esci da tutte le stanze e gli spazi", + "Want to add an existing space instead?": "Vuoi piuttosto aggiungere uno spazio esistente?", + "Private space (invite only)": "Spazio privato (solo a invito)", + "Space visibility": "Visibilità spazio", + "Add a space to a space you manage.": "Aggiungi uno spazio ad un altro che gestisci.", + "Only people invited will be able to find and join this space.": "Solo le persone invitate potranno trovare ed entrare in questo spazio.", + "Anyone in will be able to find and join.": "Chiunque in potrà trovare ed entrare.", + "Anyone will be able to find and join this space, not just members of .": "Chiunque potrà trovare ed entrare in questo spazio, non solo i membri di .", + "Anyone will be able to find and join this room.": "Chiunque potrà trovare ed entrare in questa stanza.", + "Adding spaces has moved.": "L'aggiunta di spazi è stata spostata.", + "Search for rooms": "Cerca stanze", + "Search for spaces": "Cerca spazi", + "Create a new space": "Crea un nuovo spazio", + "Want to add a new space instead?": "Vuoi piuttosto aggiungere un nuovo spazio?", + "Add existing space": "Aggiungi spazio esistente", + "Share content": "Condividi contenuto", + "Application window": "Finestra applicazione", + "Share entire screen": "Condividi schermo intero", + "You can now share your screen by pressing the \"screen share\" button during a call. You can even do this in audio calls if both sides support it!": "Ora puoi condividere lo schermo premendo il pulsante \"condivisione schermo\" durante una chiamata. Puoi anche farlo nelle telefonate se entrambe le parti lo supportano!", + "Screen sharing is here!": "È arrivata la condivisione dello schermo!", + "Show all rooms": "Mostra tutte le stanze", + "Give feedback.": "Manda feedback.", + "Thank you for trying Spaces. Your feedback will help inform the next versions.": "Grazie per aver provato gli spazi. Il tuo feedback aiuterà a migliorare le prossime versioni.", + "Spaces feedback": "Feedback sugli spazi", + "Spaces are a new feature.": "Gli spazi sono una nuova funzionalità.", + "Your camera is still enabled": "La tua fotocamera è ancora attiva", + "Your camera is turned off": "La tua fotocamera è spenta", + "%(sharerName)s is presenting": "%(sharerName)s sta presentando", + "You are presenting": "Stai presentando", + "All rooms you're in will appear in Home.": "Tutte le stanze in cui sei appariranno nella pagina principale.", + "Decrypting": "Decifrazione", + "Send pseudonymous analytics data": "Invia dati analitici pseudo-anonimi", + "%(oneUser)schanged the pinned messages for the room %(count)s times.|other": "%(oneUser)sha cambiato i messaggi ancorati della stanza %(count)s volte.", + "%(severalUsers)schanged the pinned messages for the room %(count)s times.|other": "%(severalUsers)shanno cambiato i messaggi ancorati della stanza %(count)s volte.", + "Missed call": "Chiamata persa", + "Call declined": "Chiamata rifiutata", + "Stop recording": "Ferma la registrazione", + "Send voice message": "Invia messaggio vocale", + "Olm version:": "Versione Olm:", + "Mute the microphone": "Spegni il microfono", + "Unmute the microphone": "Accendi il microfono", + "Dialpad": "Tastierino", + "More": "Altro", + "Show sidebar": "Mostra barra laterale", + "Hide sidebar": "Nascondi barra laterale", + "Start sharing your screen": "Avvia la condivisione dello schermo", + "Stop sharing your screen": "Ferma la condivisione dello schermo", + "Stop the camera": "Ferma la fotocamera", + "Start the camera": "Avvia la fotocamera", + "Surround selected text when typing special characters": "Circonda il testo selezionato quando si digitano caratteri speciali" } diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json index 6f18b8e384..14295b0532 100644 --- a/src/i18n/strings/ja.json +++ b/src/i18n/strings/ja.json @@ -2514,5 +2514,9 @@ "Identity server (%(server)s)": "identity サーバー (%(server)s)", "Could not connect to identity server": "identity サーバーに接続できませんでした", "Not a valid identity server (status code %(code)s)": "有効な identity サーバーではありません (ステータスコード %(code)s)", - "Identity server URL must be HTTPS": "identityサーバーのURLは HTTPS スキーマである必要があります" + "Identity server URL must be HTTPS": "identityサーバーのURLは HTTPS スキーマである必要があります", + "Saving...": "保存しています…", + "Failed to save space settings.": "スペースの設定を保存できませんでした。", + "Transfer Failed": "転送に失敗しました", + "Unable to transfer call": "通話が転送できませんでした" } diff --git a/src/i18n/strings/lv.json b/src/i18n/strings/lv.json index 00c140c0a9..daa534f89c 100644 --- a/src/i18n/strings/lv.json +++ b/src/i18n/strings/lv.json @@ -270,7 +270,7 @@ "Cancel": "Atcelt", "Create new room": "Izveidot jaunu istabu", "Custom Server Options": "Iestatāmās servera opcijas", - "Dismiss": "Aizvērt", + "Dismiss": "Aizvērt/atcelt", "You have enabled URL previews by default.": "URL priekšskatījumi pēc noklusējuma jums iriespējoti .", "Upload avatar": "Augšupielādēt avataru (profila attēlu)", "Upload Failed": "Augšupielāde (nosūtīšana) neizdevās", @@ -773,7 +773,7 @@ "e.g. %(exampleValue)s": "piemēram %(exampleValue)s", "e.g. ": "piemēram ", "Your device resolution": "Jūsu iekārtas izšķirtspēja", - "Sign In": "Pierakstīties", + "Sign In": "Ierakstīties", "Whether or not you're logged in (we don't record your username)": "Esat vai neesat pieteicies (mēs nesaglabājam jūsu lietotājvārdu)", "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Neatkarīgi no tā, vai izmantojat funkciju \"breadcrumbs\" (avatari virs istabu saraksta)", "Every page you use in the app": "Katra lapa, ko lietojat lietotnē", @@ -1234,7 +1234,7 @@ "Reject & Ignore user": "Noraidīt un ignorēt lietotāju", "Do you want to chat with %(user)s?": "Vai vēlaties sarakstīties ar %(user)s?", "This homeserver doesn't offer any login flows which are supported by this client.": "Šis bāzes serveris neatbalsta nevienu pierakstīšanās metodi, kuru atbalstītu šis klients.", - "Explore rooms": "Pārlūkot istabas", + "Explore rooms": "Pārlūkot telpas", "Confirm Security Phrase": "Apstipriniet slepeno frāzi", "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Nodrošinieties pret piekļuves zaudēšanu šifrētām ziņām un datiem, dublējot šifrēšanas atslēgas savā serverī.", "Use a secret phrase only you know, and optionally save a Security Key to use for backup.": "Izmantojiet tikai jums zināmu slepeno frāzi un pēc izvēles saglabājiet drošības atslēgu, lai to izmantotu dublēšanai.", @@ -1586,5 +1586,418 @@ "Integration manager": "Integrācija pārvaldnieks", "Identity server is": "Indentifikācijas serveris ir", "Identity server": "Identitāšu serveris", - "Could not connect to identity server": "Neizdevās pieslēgties identitāšu serverim" + "Could not connect to identity server": "Neizdevās pieslēgties identitāšu serverim", + "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Jūs varat reģistrēties, taču dažas funkcijas nebūs pieejamas, kamēr nebūs pieejams identitāšu serveris. Ja arī turpmāk redzat šo brīdinājumu, lūdzu, pārbaudiet konfigurāciju vai sazinieties ar servera administratoru.", + "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Jūs varat atstatīt paroli, taču dažas funkcijas/opcijas nebūs pieejamas, kamēr nebūs pieejams identitāšu serveris. Ja arī turpmāk redzat šo brīdinājumu, lūdzu, pārbaudiet konfigurāciju vai sazinieties ar servera administratoru.", + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Jūs varat ierakstīties, taču dažas funkcijas nebūs pieejamas, kamēr nebūs pieejams identitāšu serveris. Ja arī turpmāk redzat šo brīdinājumu, lūdzu, pārbaudiet konfigurāciju vai sazinieties ar servera administratoru.", + "Cannot reach identity server": "Neizdodas sasniegt identitāšu serveri", + "Ask your %(brand)s admin to check your config for incorrect or duplicate entries.": "Paprasiet %(brand)s administratoram pārbaudīt, vai jūsu konfigurācijas failā nav nepareizu vai dublējošos ierakstu.", + "See %(msgtype)s messages posted to your active room": "Redzēt jūsu aktīvajā telpā izliktās %(msgtype)s ziņas", + "See %(msgtype)s messages posted to this room": "Redzēt šajā telpā izliktās %(msgtype)s ziņas", + "Send %(msgtype)s messages as you in your active room": "Sūtīt %(msgtype)s ziņas savā vārdā savā aktīvajā telpā", + "Send %(msgtype)s messages as you in this room": "Sūtīt %(msgtype)s ziņas savā vārdā šajā telpā", + "See general files posted to your active room": "Redzēt jūsu aktīvajā telpā izliktos failus", + "See general files posted to this room": "Redzēt šajā telpā izliktos failus", + "Send general files as you in your active room": "Sūtīt failus savā vārdā jūsu aktīvajā telpā", + "Send general files as you in this room": "Sūtīt failus savā vārdā šajā telpā", + "See videos posted to your active room": "Redzēt video, kuri izlikti jūsu aktīvajā telpā", + "See videos posted to this room": "Redzēt video, kuri izlikti šajā telpā", + "Send videos as you in your active room": "Sūtīt video savā vārdā savā aktīvajā telpā", + "Send videos as you in this room": "Sūtīt video savā vārdā šajā telpā", + "See images posted to your active room": "Redzēt attēlus, kuri izlikti jūsu aktīvajā telpā", + "See images posted to this room": "Redzēt attēlus, kuri izlikti šajā telpā", + "Send images as you in your active room": "Sūtīt attēlus savā vārdā savā aktīvajā telpā", + "Send images as you in this room": "Sūtīt attēlus savā vārdā šajā telpā", + "See emotes posted to your active room": "Redzēt emocijas, kuras izvietotas jūsu aktīvajā telpā", + "See emotes posted to this room": "Redzēt emocijas, kuras izvietotas šajā telpā", + "Send emotes as you in your active room": "Nosūtīt emocijas savā vārdā uz savu aktīvo telpu", + "Send emotes as you in this room": "Nosūtīt emocijas savā vārdā uz šo telpu", + "See text messages posted to your active room": "Redzēt teksta ziņas, kuras izvietotas jūsu aktīvajā telpā", + "See text messages posted to this room": "Redzēt teksta ziņas, kas izvietotas šajā telpā", + "Send text messages as you in your active room": "Sūtīt teksta ziņas savā vārdā jūsu aktīvajā telpā", + "Send text messages as you in this room": "Sūtīt teksta ziņas savā vārdā šajā telpā", + "See messages posted to your active room": "Redzēt ziņas, kas izvietotas jūsu aktīvajā telpā", + "See messages posted to this room": "Redzēt ziņas, kas izvietotas šajā telpā", + "Send messages as you in your active room": "Sūtiet ziņas savā vārdā jūsu aktīvajā telpā", + "Send messages as you in this room": "Sūtīt ziņas savā vārdā šajā telpā", + "The %(capability)s capability": "%(capability)s iespējas", + "See %(eventType)s events posted to your active room": "Redzēt, kad %(eventType)s notikumi izvietoti jūsu aktīvajā telpā", + "Send %(eventType)s events as you in your active room": "Sūtīt %(eventType)s notikumus savā vārdā savā aktīvajā telpā", + "See %(eventType)s events posted to this room": "Redzēt %(eventType)s notikumus, kas izvietoti šajā telpā", + "Send %(eventType)s events as you in this room": "Sūtiet %(eventType)s notikumus jūsu vārdā šajā telpā", + "with state key %(stateKey)s": "ar stāvokļa/statusa atslēgu %(stateKey)s", + "with an empty state key": "ar tukšu stāvokļa/statusa atslēgu", + "See when anyone posts a sticker to your active room": "Redzēt, kad kāds izvieto stikeri jūsu aktīvajā telpā", + "Send stickers to your active room as you": "Nosūtiet uzlīmes savā vārdā uz savu aktīvo telpu", + "See when a sticker is posted in this room": "Redzēt, kad šajā telpā parādās stikers", + "Send stickers to this room as you": "Nosūtīt stikerus savā vārdā uz šo telpu", + "See when people join, leave, or are invited to your active room": "Redzēt, kad cilvēki ienāk/pievienojas, pamet/atvienojas vai ir uzaicināti uz jūsu aktīvo telpu", + "Kick, ban, or invite people to this room, and make you leave": "Izspert, liegt vai uzaicināt cilvēkus uz šo telpu un likt jums aiziet", + "Kick, ban, or invite people to your active room, and make you leave": "Izspert, liegt vai uzaicināt cilvēkus uz jūsu aktīvo telpu un likt jums aiziet", + "See when people join, leave, or are invited to this room": "Redzēt, kad cilvēki ienāk/pievienojas, pamet/atvienojas vai ir uzaicināti uz šo telpu", + "See when the avatar changes in your active room": "Redzēt, kad notiek jūsu aktīvās istabas avatara izmaiņas", + "Change the avatar of your active room": "Mainīt jūsu aktīvās telpas avataru", + "See when the avatar changes in this room": "Redzēt, kad notiek šīs istabas avatara izmaiņas", + "Change the avatar of this room": "Mainīt šīs istabas avataru", + "See when the name changes in your active room": "Redzēt, kad notiek aktīvās telpas nosaukuma izmaiņas", + "Change the name of your active room": "Mainīt jūsu aktīvās telpas nosaukumu", + "See when the name changes in this room": "Redzēt, kad mainās šīs telpas nosaukums", + "Change the name of this room": "Mainīt šīs telpas nosaukumu", + "See when the topic changes in this room": "Redzēt, kad mainās šīs telpas temats", + "See when the topic changes in your active room": "Redzēt, kad mainās pašreizējā tērziņa temats", + "Change the topic of your active room": "Nomainīt jūsu aktīvās istabas tematu", + "Change the topic of this room": "Nomainīt šīs telpas tematu", + "Change which room, message, or user you're viewing": "Nomainīt telpu, ziņu vai lietotāju, kurš ir fokusā (kuru jūs skatiet)", + "Change which room you're viewing": "Nomainīt telpu, kuru jūs skatiet", + "Send stickers into your active room": "Iesūtīt stikerus jūsu aktīvajā telpā", + "Send stickers into this room": "Šajā telpā iesūtīt stikerus", + "Remain on your screen while running": "Darbības laikā paliek uz ekrāna", + "Remain on your screen when viewing another room, when running": "Darbības laikā paliek uz ekrāna, kad tiek skatīta cita telpa", + "Dark": "Tumša", + "Light": "Gaiša", + "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s pārjaunoja lieguma noteikumu šablonu %(oldGlob)s uz šablonu %(newGlob)s dēļ %(reason)s", + "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s aizstāja noteikumu, kas piekļuvi liedza serveriem, kas atbilst pazīmei %(oldGlob)s, ar atbilstošu pazīmei %(newGlob)s dēļ %(reason)s", + "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s aizstāja noteikumu, kurš liedza %(oldGlob)s pazīmei atbilstošas telpas ar jaunu noteikumu, kurš liedz %(newGlob)s dēļ %(reason)s", + "%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s aizstāja noteikumu, kurš aizliedza lietotājus %(oldGlob)s ar jaunu noteikumu, kurš aizliedz %(newGlob)s dēļ %(reason)s", + "%(senderName)s has updated the widget layout": "%(senderName)s ir aktualizējis vidžeta/logrīka izkārtojumu", + "%(senderName)s changed the pinned messages for the room.": "%(senderName)s mainīja telpas piekabinātās ziņas.", + "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s modernizēja šo telpu.", + "%(senderName)s kicked %(targetName)s": "%(senderName)s izspēra %(targetName)s", + "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s izspēra %(targetName)s: %(reason)s", + "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s atsauca %(targetName)s paredzēto uzaicinājumu", + "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s atsauca %(targetName)s paredzēto uzaicinājumu: %(reason)s", + "%(senderName)s unbanned %(targetName)s": "%(senderName)s noņēma liegumu/atbanoja %(targetName)s", + "%(targetName)s left the room": "%(targetName)s pameta/atvienojās no telpas", + "%(targetName)s left the room: %(reason)s": "%(targetName)s pameta/atvienojās no telpas: %(reason)s", + "%(targetName)s rejected the invitation": "%(targetName)s noraidīja uzaicinājumu", + "%(targetName)s joined the room": "%(targetName)s ienāca (pievienojās) telpā", + "%(senderName)s made no change": "%(senderName)s neizdarīja izmaiņas", + "%(senderName)s set a profile picture": "%(senderName)s iestatīja profila attēlu", + "%(senderName)s changed their profile picture": "%(senderName)s nomainīja savu profila attēlu", + "%(senderName)s removed their profile picture": "%(senderName)s dzēsa savu profila attēlu", + "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s dzēsa savu redzamo vārdu (%(oldDisplayName)s)", + "%(senderName)s set their display name to %(displayName)s": "%(senderName)s iestatīja %(displayName)s kā savu redzamo vārdu", + "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s nomainīja savu redzamo vārdu uz %(displayName)s", + "%(senderName)s banned %(targetName)s": "%(senderName)s aizliedza/nobanoja %(targetName)s", + "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s aizliedza/nobanoja %(targetName)s: %(reason)s", + "%(senderName)s invited %(targetName)s": "%(senderName)s uzaicināja %(targetName)s", + "%(targetName)s accepted an invitation": "%(targetName)s pieņēma uzaicinājumu", + "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s pieņēma uzaicinājumu uz %(displayName)s", + "Converts the DM to a room": "Pārvērst DM par telpu", + "Converts the room to a DM": "Pārvērst telpu par DM", + "Places the call in the current room on hold": "Iepauzē sarunu šajā telpā", + "Takes the call in the current room off hold": "Šajā telpā iepauzētās sarunas atpauzēšana", + "Sends a message to the given user": "Nosūtīt ziņu dotajam lietotājam", + "Opens chat with the given user": "Izveidot tērziņu ar doto lietotāju", + "Send a bug report with logs": "Nosūtīt kļūdas ziņojumu ar žurnāliem/logiem", + "Displays information about a user": "Parāda lietotāja informāciju", + "Displays list of commands with usages and descriptions": "Parāda komandu sarakstu ar pielietojumiem un aprakstiem", + "Sends the given emote coloured as a rainbow": "Nosūta šo emociju iekrāsotu varavīksnes krāsās", + "Sends the given message coloured as a rainbow": "Nosūta šo ziņu iekrāsotu varavīksnes krāsās", + "Forces the current outbound group session in an encrypted room to be discarded": "Piespiedu kārtā atmet/izbeidz pašreizējo izejošo grupas sesiju šifrētajā telpā", + "The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.": "Jūsu iesniegtā parakstīšanas atslēga atbilst parakstīšanas atslēgai, kuru saņēmāt no %(userId)s sesijas %(deviceId)s. Sesija atzīmēta kā verificēta.", + "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "BRĪDINĀJUMS: ATSLĒGU VERIFIKĀCIJA NEIZDEVĀS! Parakstīšanas atslēga lietotājam %(userId)s un sesijai %(deviceId)s ir \"%(fprint)s\", kura neatbilst norādītajai atslēgai \"%(fingerprint)s\". Tas var nozīmēt, ka jūsu saziņa tiek pārtverta!", + "Verifies a user, session, and pubkey tuple": "Verificē lietotāju, sesiju un publiskās atslēgas", + "WARNING: Session already verified, but keys do NOT MATCH!": "BRĪDINĀJUMS: Sesija jau ir verificēta, bet atslēgas NESAKRĪT!", + "Unknown (user, session) pair:": "Nezināms (lietotājs, sesija) pāris:", + "You cannot modify widgets in this room.": "Jūs šajā telpā nevarat mainīt vidžetus/logrīkus.", + "Please supply a https:// or http:// widget URL": "Lūdzu ievadiet logrīka URL https:// vai http:// formā", + "Please supply a widget URL or embed code": "Ievadiet vidžeta/logrīka URL vai ievietojiet kodu", + "Adds a custom widget by URL to the room": "Pievieno telpai individuālu/pielāgotu logrīku/vidžetu ar URL-adresi", + "Command failed": "Neizdevās izpildīt komandu", + "Joins room with given address": "Pievienojas telpai ar šādu adresi", + "Use an identity server to invite by email. Manage in Settings.": "Izmantojiet identitātes serveri, lai uzaicinātu pa e-pastu. Pārvaldība pieejama Iestatījumos.", + "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Izmantojiet identitātes serveri, lai uzaicinātu pa e-pastu. Noklikšķiniet uz Turpināt, lai izmantotu noklusējuma identitātes serveri (%(defaultIdentityServerName)s) vai nomainītu to Iestatījumos.", + "Use an identity server": "Izmantot identitāšu serveri", + "Sets the room name": "Iestata telpas nosaukumu", + "Gets or sets the room topic": "Nolasa vai iestata telpas tematu", + "Changes your avatar in all rooms": "Maina jūsu avataru visām telpām", + "Changes your avatar in this current room only": "Maina jūsu avataru tikai šajā telpā", + "Changes the avatar of the current room": "Maina šīs telpas avataru", + "Changes your display nickname in the current room only": "Maina rādāmo pseidonīmu/segvārdu tikai šai telpai", + "Upgrades a room to a new version": "Modernizē telpu uz Jauno versiju", + "Sends a message as html, without interpreting it as markdown": "Nosūta ziņu kā HTML, to neinterpretējot kā Markdown", + "Sends a message as plain text, without interpreting it as markdown": "Nosūta ziņu kā vienkāršu tekstu, to neinterpretējot kā Markdown", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Pievieno ( ͡° ͜ʖ ͡°) pirms vienkārša teksta ziņas", + "Prepends ┬──┬ ノ( ゜-゜ノ) to a plain-text message": "Pievieno ┬──┬ ノ( ゜-゜ノ) pirms vienkārša teksta ziņas", + "Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message": "Pievieno (╯°□°)╯︵ ┻━┻ pirms vienkārša teksta ziņas", + "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Pievieno ¯\\_(ツ)_/¯ pirms vienkārša teksta ziņas", + "Sends the given message as a spoiler": "Nosūta norādīto ziņu kā spoileri", + "Effects": "Efekti", + "Messages": "Ziņas", + "Setting up keys": "Atslēgu iestatīšana", + "We sent the others, but the below people couldn't be invited to ": "Pārējiem uzaicinājumi tika nosūtīti, bet zemāk norādītos cilvēkus uz nevarēja uzaicināt", + "Some invites couldn't be sent": "Dažus uzaicinājumus nevarēja nosūtīt", + "Zimbabwe": "Zimbabve", + "Zambia": "Zambija", + "Yemen": "Jemena", + "Western Sahara": "Rietumsahāra", + "Wallis & Futuna": "Volisa & Futuna", + "Vietnam": "Vjetnama", + "Venezuela": "Venecuēla", + "Vatican City": "Vatikāns", + "Vanuatu": "Vanuatu", + "Uzbekistan": "Uzbekistāna", + "Uruguay": "Urugvaja", + "United Arab Emirates": "Apvienotie arābu emirāti", + "Ukraine": "Ukraina", + "Uganda": "Uganda", + "U.S. Virgin Islands": "ASV Virdžīnu salas", + "Tuvalu": "Tuvalu", + "Turks & Caicos Islands": "Tērksas un Kaikosas salas", + "Turkmenistan": "Turkmenistāna", + "Turkey": "Turcija", + "Tunisia": "Tunisija", + "Trinidad & Tobago": "Trinidāda & Tobago", + "Tonga": "Tonga", + "Tokelau": "Tokelau", + "Togo": "Togo", + "Timor-Leste": "Austrumtimora", + "Thailand": "Taizeme", + "Tanzania": "Tanzānija", + "Tajikistan": "Tadžikistāna", + "Taiwan": "Taivana", + "São Tomé & Príncipe": "Santome un Prinsipi", + "Syria": "Sīrija", + "Switzerland": "Šveice", + "Sweden": "Zviedrija", + "Swaziland": "Svazilenda - Esvatini", + "Svalbard & Jan Mayen": "Svalbāra un Jans Mejens", + "Suriname": "Sirinama", + "Sudan": "Sudāna", + "St. Vincent & Grenadines": "Sentvinsenta un Grenadīnas", + "St. Pierre & Miquelon": "Sentpjērs un Mikelons", + "St. Martin": "Sen-Marten", + "St. Lucia": "Sentlūsija", + "St. Kitts & Nevis": "Sentkitsa un Nevisa", + "St. Helena": "Svētās Helēnas sala", + "St. Barthélemy": "Sen-bartelemi", + "Sri Lanka": "Šrilanka", + "Spain": "Spānija", + "South Sudan": "Dienvidsudāna", + "South Korea": "Dienvidkoreja", + "South Georgia & South Sandwich Islands": "Dienviddžordžija un Dienvidsendviču salas", + "South Africa": "Dienvidāfrika", + "Somalia": "Somālija", + "Solomon Islands": "Solomona salas", + "Slovenia": "Slovēnija", + "Slovakia": "Slovākija", + "Sint Maarten": "Sintmartēna", + "Singapore": "Singapūra", + "Sierra Leone": "Sjerra-leone", + "Seychelles": "Seišeļu salas", + "Serbia": "Serbija", + "Senegal": "Senegāla", + "Saudi Arabia": "Saudu Arābija", + "San Marino": "Sanmarino", + "Samoa": "Samoa", + "Réunion": "Rejunjona", + "Rwanda": "Ruanda", + "Russia": "Krievija", + "Romania": "Rumānija", + "Qatar": "Katāra", + "Puerto Rico": "Puertoriko", + "Portugal": "Portugāle", + "Poland": "Polija", + "Pitcairn Islands": "Pitkērnas salas", + "Peru": "Peru", + "Paraguay": "Paragvaja", + "Papua New Guinea": "Papua Jaungvineja", + "Panama": "Panama", + "Palestine": "Palestīna", + "Palau": "Palau", + "Pakistan": "Pakistāna", + "Oman": "Omāna", + "Norway": "Norvēģija", + "Northern Mariana Islands": "Ziemeļu Marianas salas", + "North Korea": "Ziemeļkoreja", + "Norfolk Island": "Norfolka sala", + "Niue": "Niuve", + "Nigeria": "Nigērija", + "Niger": "Nigēra", + "Nicaragua": "Nikaragva", + "New Zealand": "Jaunzelande", + "New Caledonia": "Jaunkaledonija", + "Netherlands": "Nīderlande", + "Nepal": "Nepāla", + "Nauru": "Nauru", + "Namibia": "Namībija", + "Myanmar": "Mjanma", + "Mozambique": "Mozambika", + "Morocco": "Maroka", + "Montserrat": "Monserata", + "Montenegro": "Montenegro", + "Mongolia": "Mongolija", + "Monaco": "Monako", + "Moldova": "Moldova", + "Micronesia": "Mikronēzija", + "Mexico": "Meksika", + "Mayotte": "Majotta", + "Mauritius": "Mauritānija", + "Mauritania": "Mauritānija", + "Martinique": "Martinika", + "Marshall Islands": "Maršala salas", + "Malta": "Malta", + "Mali": "Mali", + "Maldives": "Maldaivu salas", + "Malaysia": "Malaizija", + "Liechtenstein": "Lihtenšteina", + "Libya": "Lībija", + "Liberia": "Libērija", + "Lesotho": "Lesoto", + "Laos": "Laosa", + "Kyrgyzstan": "Kirgiztāna", + "Kuwait": "Kuveita", + "Kosovo": "Kosova", + "Kiribati": "Kiribati", + "Kenya": "Kenija", + "Kazakhstan": "Kazahstāna", + "Jordan": "Jordāna", + "Jersey": "Džersija", + "Japan": "Japāna", + "Jamaica": "Jamaika", + "Italy": "Itālija", + "Israel": "Izraēla", + "Isle of Man": "Menas sala", + "Ireland": "Īrija", + "Iraq": "Irāka", + "Iran": "Irāna", + "Indonesia": "Indonēzija", + "India": "Indija", + "Iceland": "Islande", + "Hungary": "Ungārija", + "Hong Kong": "Honkonga", + "Honduras": "Hondurasa", + "Heard & McDonald Islands": "Herda & McDonalda salas", + "Haiti": "Haiti", + "Guyana": "Gajana", + "Guinea-Bissau": "Gvineja-bissau", + "Guinea": "Gvineja", + "Guernsey": "Gērnsija", + "Guatemala": "Gvatemala", + "Guam": "Guama", + "Guadeloupe": "Gvadelope", + "Grenada": "Grenāda", + "Greenland": "Grenlande", + "Greece": "Grieķija", + "Gibraltar": "Gibraltārs", + "Ghana": "Gana", + "Germany": "Vācija", + "Georgia": "Gruzija", + "Gambia": "Gambija", + "Gabon": "Gabona", + "French Southern Territories": "Franču Dienvidu teritorijas", + "French Polynesia": "Franču polinēzija", + "French Guiana": "Franču gviāna", + "France": "Francija", + "Finland": "Somija", + "Fiji": "Fidži", + "Faroe Islands": "Farēru salas", + "Falkland Islands": "Folklandu salas", + "Ethiopia": "Etiopija", + "Estonia": "Igaunija", + "Eritrea": "Eritreja", + "Equatorial Guinea": "Ekvatoriālā gvineja", + "El Salvador": "Salvadora", + "Egypt": "Ēģipte", + "Ecuador": "Ekvadora", + "Dominican Republic": "Dominikānas republika", + "Dominica": "Dominika", + "Djibouti": "Džibuti", + "Côte d’Ivoire": "Ziloņkaula krasts", + "Czech Republic": "Čehija", + "Cyprus": "Kipra", + "Curaçao": "Kurasao", + "Cuba": "Kuba", + "Croatia": "Horvātija", + "Costa Rica": "Kostarika", + "Cook Islands": "Kuka salas", + "Congo - Kinshasa": "Kongo - Kinšasa (Kongo demokrātiskā republika)", + "Congo - Brazzaville": "Kongo - Brazaville", + "Comoros": "Komoras salas", + "Colombia": "Kolumbija", + "Cocos (Keeling) Islands": "Kokosa (Kīlinga) salas", + "Christmas Island": "Ziemassvētku sala", + "China": "Ķīna", + "Chile": "Čīle", + "Chad": "Čada", + "Central African Republic": "Centrālāfrikas republika", + "Cayman Islands": "Kaimanu salas", + "Caribbean Netherlands": "Nīderlandes Karību salas (Bonaire, Sint Eustatius un Saba)", + "Cape Verde": "Keipverde", + "Canada": "Kanāda", + "Cameroon": "Kamerūna", + "Cambodia": "Kambodža", + "Burundi": "Burundi", + "Burkina Faso": "Burkinafaso", + "Bulgaria": "Bulgārija", + "Brunei": "Bruneja", + "British Virgin Islands": "Britu Virdžīnu salas", + "British Indian Ocean Territory": "Britu Indijas okeāna teritorija", + "Brazil": "Brazīlija", + "Bouvet Island": "Buvē sala", + "Botswana": "Botsvana", + "Bosnia": "Bosnija", + "Bolivia": "Bolīvija", + "Bhutan": "Butāna", + "Bermuda": "Bermudas", + "Benin": "Benīna", + "Belize": "Belīze", + "Belgium": "Beļģija", + "Belarus": "Baltkrievija", + "Barbados": "Barbadosa", + "Bahrain": "Bahreina", + "Bahamas": "Bahamas", + "Azerbaijan": "Azerbaidžāna", + "Austria": "Austrija", + "Australia": "Austrālija", + "Aruba": "Aruba", + "Armenia": "Armēnija", + "Argentina": "Argentīna", + "Antigua & Barbuda": "Antigua & Barbuda", + "Antarctica": "Antarktika", + "Anguilla": "Anguilla", + "Angola": "Angola", + "Andorra": "Andora", + "Åland Islands": "Ālandu salas", + "Your homeserver rejected your log in attempt. This could be due to things just taking too long. Please try again. If this continues, please contact your homeserver administrator.": "Jūsu bāzes/mājas serveris noraidīja jūsu ierakstīšanās mēģinājumu. Tas varētu būt saistīts ar to, ka ierakstīšanās prasīja pārāk ilgu laiku. Lūdzu mēģiniet vēlreiz. Ja tas tā turpinās, lūdzu, sazinieties ar bāzes/mājas servera administratoru.", + "Your homeserver was unreachable and was not able to log you in. Please try again. If this continues, please contact your homeserver administrator.": "Jūsu bāzes/mājas serveris nebija sasniedzams un jums nebija iespējams ieraksīties. Lūdzu, mēģiniet vēlreiz. Ja situācija nemainās, lūdzu, sazinieties ar bāzes/mājas servera administratoru.", + "Try again": "Mēģiniet vēlreiz", + "We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.": "Mēs lūdzām tīmekļa pārlūkprogrammai atcerēties, kuru bāzes/mājas serveri izmantojat, lai ļautu jums pierakstīties, bet diemžēl jūsu pārlūkprogramma to ir aizmirsusi. Dodieties uz ierakstīšanās lapu un mēģiniet vēlreiz.", + "We couldn't log you in": "Neizdevās jūs ierakstīt sistēmā", + "Trust": "Uzticamība", + "Only continue if you trust the owner of the server.": "Turpiniet tikai gadījumā, ja uzticaties servera īpašniekam.", + "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Šai darbībai ir nepieciešama piekļuve noklusējuma identitātes serverim , lai validētu e-pasta adresi vai tālruņa numuru, taču serverim nav pakalpojumu sniegšanas noteikumu.", + "Identity server has no terms of service": "Identitātes serverim nav pakalpojumu sniegšanas noteikumu", + "Failed to transfer call": "Neizdevās pārsūtīt/pāradresēt zvanu", + "Transfer Failed": "Pāradresēšana/pārsūtīšana neizdevās", + "Unable to transfer call": "Neizdevās pārsūtīt zvanu", + "There was an error looking up the phone number": "Meklējot tālruņa numuru, radās kļūda", + "Unable to look up phone number": "Nevar atrast tālruņa numuru", + "No other application is using the webcam": "Neviena cita lietotne neizmanto kameru", + "Permission is granted to use the webcam": "Piešķirta atļauja izmantot kameru", + "A microphone and webcam are plugged in and set up correctly": "Mikrofons un kamera ir pievienoti un pareizi konfigurēti", + "Call failed because webcam or microphone could not be accessed. Check that:": "Zvans neizdevās, jo nevarēja piekļūt kamerai vai mikrofonam. Pārbaudiet, vai:", + "Unable to access webcam / microphone": "Nevar piekļūt kamerai / mikrofonam", + "Call failed because microphone could not be accessed. Check that a microphone is plugged in and set up correctly.": "Zvans neizdevās, jo nebija piekļuves mikrofonam. Pārliecinieties, vai mikrofons ir pievienots un pareizi konfigurēts.", + "Unable to access microphone": "Nav pieejas mikrofonam", + "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Varat arī iemēģināt izmantot publisko serveri vietnē turn.matrix.org , taču tas nebūs tik droši, un šajā serverī nonāks jūsu IP adrese. To var arī pārvaldīt iestatījumos.", + "The call was answered on another device.": "Uz zvanu tika atbildēts no citas ierīces.", + "Answered Elsewhere": "Atbildēja citur", + "The call could not be established": "Savienojums nevarēja tikt izveidots", + "The user you called is busy.": "Lietotājs, kuram zvanāt, ir aizņemts.", + "User Busy": "Lietotājs aizņemts", + "Your user agent": "Jūsu lietotāja-aģents", + "Whether you're using %(brand)s as an installed Progressive Web App": "Vai izmantojiet %(brand)s kā instalētu progresīvo tīmekļa lietotni", + "Whether you're using %(brand)s on a device where touch is the primary input mechanism": "Vai izmantojat %(brand)s ierīcē, kurā skārnienjūtīgs ekrāns ir galvenais ievades mehānisms", + "This room is used for important messages from the Homeserver, so you cannot leave it.": "Šī telpa tiek izmantota svarīgiem ziņojumiem no bāzes servera, tāpēc jūs nevarat to atstāt.", + "Can't leave Server Notices room": "Nevar iziet no servera Paziņojumu telpas", + "Unexpected server error trying to leave the room": "Mēģinot atstāt telpu radās negaidīta servera kļūme", + "Unable to connect to Homeserver. Retrying...": "Nevar izveidot savienojumu ar bāzes/mājas serveri. Mēģinam vēlreiz ...", + "Please contact your service administrator to continue using the service.": "Lūdzu sazinieties ar savu administratoru, lai turpinātu izmantot pakalpojumu.", + "This homeserver has exceeded one of its resource limits.": "Šis bāzes/mājas serveris ir pārsniedzis vienu no tā resursu ierobežojumiem.", + "This homeserver has been blocked by its administrator.": "Šo bāzes/mājas serveri ir bloķējis tā administrators.", + "This homeserver has hit its Monthly Active User limit.": "Šis bāzes/mājas serveris ir sasniedzis ikmēneša aktīvo lietotāju ierobežojumu.", + "Unexpected error resolving identity server configuration": "Negaidīta kļūda identitātes servera konfigurācijā", + "Unexpected error resolving homeserver configuration": "Negaidīta kļūme mājas servera konfigurācijā" } diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index 54cd4c6f66..1dc7cd04f4 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -28,7 +28,7 @@ "Change Password": "Wachtwoord veranderen", "%(senderName)s changed their profile picture.": "%(senderName)s heeft een nieuwe profielfoto ingesteld.", "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s heeft het machtsniveau van %(powerLevelDiffText)s gewijzigd.", - "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s heeft de gespreksnaam gewijzigd naar %(roomName)s.", + "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s heeft de kamernaam gewijzigd naar %(roomName)s.", "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s heeft het onderwerp gewijzigd naar ‘%(topic)s’.", "Changes your display nickname": "Verandert uw weergavenaam", "Click here to fix": "Klik hier om dit op te lossen", @@ -127,7 +127,7 @@ "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Geen verbinding met de homeserver - controleer uw verbinding, zorg ervoor dat het SSL-certificaat van de homeserver vertrouwd is en dat er geen browserextensies verzoeken blokkeren.", "Cryptography": "Cryptografie", "Current password": "Huidig wachtwoord", - "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s heeft de gespreksnaam verwijderd.", + "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s heeft de kamernaam verwijderd.", "Create Room": "Gesprek aanmaken", "/ddg is not a command": "/ddg is geen opdracht", "Deactivate Account": "Account Sluiten", @@ -166,7 +166,7 @@ "Failed to unban": "Ontbannen mislukt", "Failed to upload profile picture!": "Uploaden van profielfoto is mislukt!", "Failed to verify email address: make sure you clicked the link in the email": "Kan het e-mailadres niet verifiëren: zorg ervoor dat je de koppeling in de e-mail hebt aangeklikt", - "Failure to create room": "Aanmaken van gesprek is mislukt", + "Failure to create room": "Aanmaken van kamer is mislukt", "Favourites": "Favorieten", "Fill screen": "Scherm vullen", "Filter room members": "Gespreksleden filteren", @@ -192,7 +192,7 @@ "%(senderName)s invited %(targetName)s.": "%(senderName)s heeft %(targetName)s uitgenodigd.", "Invited": "Uitgenodigd", "Invites": "Uitnodigingen", - "Invites user with given id to current room": "Nodigt de gebruiker met de gegeven ID uit in het huidige gesprek", + "Invites user with given id to current room": "Nodigt een persoon met de gegeven ID uit in de huidige kamer", "Sign in with": "Inloggen met", "Join as voice or video.": "Deelnemen met spraak of video.", "Join Room": "Gesprek toetreden", @@ -200,15 +200,15 @@ "Jump to first unread message.": "Spring naar het eerste ongelezen bericht.", "Labs": "Labs", "Last seen": "Laatst gezien", - "Leave room": "Gesprek verlaten", + "Leave room": "Kamer verlaten", "%(targetName)s left the room.": "%(targetName)s heeft het gesprek verlaten.", "Logout": "Uitloggen", "Low priority": "Lage prioriteit", - "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s heeft de toekomstige gespreksgeschiedenis zichtbaar gemaakt voor alle gespreksleden, vanaf het moment dat ze uitgenodigd zijn.", - "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s heeft de toekomstige gespreksgeschiedenis zichtbaar gemaakt voor alle gespreksleden, vanaf het moment dat ze toegetreden zijn.", - "%(senderName)s made future room history visible to all room members.": "%(senderName)s heeft de toekomstige gespreksgeschiedenis zichtbaar gemaakt voor alle gespreksleden.", - "%(senderName)s made future room history visible to anyone.": "%(senderName)s heeft de toekomstige gespreksgeschiedenis zichtbaar gemaakt voor iedereen.", - "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s heeft de toekomstige gespreksgeschiedenis zichtbaar gemaakt voor onbekend (%(visibility)s).", + "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s heeft de toekomstige kamergeschiedenis zichtbaar gemaakt voor alle leden, vanaf het moment dat ze uitgenodigd zijn.", + "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s heeft de toekomstige kamergeschiedenis zichtbaar gemaakt voor alle leden, vanaf het moment dat ze toegetreden zijn.", + "%(senderName)s made future room history visible to all room members.": "%(senderName)s heeft de toekomstige kamergeschiedenis zichtbaar gemaakt voor alle leden.", + "%(senderName)s made future room history visible to anyone.": "%(senderName)s heeft de toekomstige kamergeschiedenis zichtbaar gemaakt voor iedereen.", + "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s heeft de toekomstige kamergeschiedenis zichtbaar gemaakt voor onbekend (%(visibility)s).", "Manage Integrations": "Integraties beheren", "Missing room_id in request": "room_id ontbreekt in verzoek", "Missing user_id in request": "user_id ontbreekt in verzoek", @@ -226,7 +226,7 @@ "%(brand)s does not have permission to send you notifications - please check your browser settings": "%(brand)s heeft geen toestemming u meldingen te sturen - controleer uw browserinstellingen", "%(brand)s was not given permission to send notifications - please try again": "%(brand)s kreeg geen toestemming u meldingen te sturen - probeer het opnieuw", "%(brand)s version:": "%(brand)s-versie:", - "Room %(roomId)s not visible": "Gesprek %(roomId)s is niet zichtbaar", + "Room %(roomId)s not visible": "Kamer %(roomId)s is niet zichtbaar", "Room Colour": "Gesprekskleur", "%(roomName)s does not exist.": "%(roomName)s bestaat niet.", "%(roomName)s is not accessible at this time.": "%(roomName)s is op dit moment niet toegankelijk.", @@ -237,7 +237,7 @@ "Seen by %(userName)s at %(dateTime)s": "Gezien door %(userName)s om %(dateTime)s", "Send Reset Email": "E-mail voor opnieuw instellen versturen", "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s heeft een afbeelding gestuurd.", - "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s heeft %(targetDisplayName)s in het gesprek uitgenodigd.", + "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s heeft %(targetDisplayName)s in deze kamer uitgenodigd.", "Server error": "Serverfout", "Server may be unavailable, overloaded, or search timed out :(": "De server is misschien onbereikbaar of overbelast, of het zoeken duurde te lang :(", "Server may be unavailable, overloaded, or you hit a bug.": "De server is misschien onbereikbaar of overbelast, of je bent een bug tegengekomen.", @@ -245,7 +245,7 @@ "Session ID": "Sessie-ID", "%(senderName)s kicked %(targetName)s.": "%(senderName)s heeft %(targetName)s het gesprek uitgestuurd.", "Kick": "Uit het gesprek sturen", - "Kicks user with given id": "Stuurt de gebruiker met de gegeven ID uit het gesprek", + "Kicks user with given id": "Stuurt de persoon met de gegeven ID uit de kamer", "%(senderName)s set a profile picture.": "%(senderName)s heeft een profielfoto ingesteld.", "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s heeft %(displayName)s als weergavenaam aangenomen.", "Show timestamps in 12 hour format (e.g. 2:30pm)": "Tijd in 12-uursformaat tonen (bv. 2:30pm)", @@ -260,7 +260,7 @@ "The email address linked to your account must be entered.": "Het aan uw account gekoppelde e-mailadres dient ingevoerd worden.", "The remote side failed to pick up": "De andere kant heeft niet opgenomen", "This room has no local addresses": "Dit gesprek heeft geen lokale adressen", - "This room is not recognised.": "Dit gesprek wordt niet herkend.", + "This room is not recognised.": "Deze kamer wordt niet herkend.", "This doesn't appear to be a valid email address": "Het ziet er niet naar uit dat dit een geldig e-mailadres is", "This phone number is already in use": "Dit telefoonnummer is al in gebruik", "This room": "Dit gesprek", @@ -277,11 +277,11 @@ "Unable to enable Notifications": "Kan meldingen niet inschakelen", "unknown caller": "onbekende beller", "Unmute": "Niet dempen", - "Unnamed Room": "Naamloos gesprek", + "Unnamed Room": "Naamloze Kamer", "Uploading %(filename)s and %(count)s others|zero": "%(filename)s wordt geüpload", "Uploading %(filename)s and %(count)s others|one": "%(filename)s en %(count)s ander worden geüpload", "Uploading %(filename)s and %(count)s others|other": "%(filename)s en %(count)s andere worden geüpload", - "Upload avatar": "Avatar uploaden", + "Upload avatar": "Afbeelding uploaden", "Upload Failed": "Uploaden mislukt", "Upload file": "Bestand uploaden", "Upload new:": "Upload er een nieuwe:", @@ -372,9 +372,9 @@ "Idle": "Afwezig", "Offline": "Offline", "Check for update": "Controleren op updates", - "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s heeft de gespreksavatar aangepast naar ", - "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s heeft de gespreksavatar verwijderd.", - "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s heeft de avatar van %(roomName)s veranderd", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s heeft de kamerafbeelding aangepast naar ", + "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s heeft de kamerafbeelding verwijderd.", + "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s heeft de afbeelding van %(roomName)s veranderd", "Username available": "Gebruikersnaam beschikbaar", "Username not available": "Gebruikersnaam niet beschikbaar", "Something went wrong!": "Er is iets misgegaan!", @@ -393,14 +393,14 @@ "Delete widget": "Widget verwijderen", "Edit": "Bewerken", "Enable automatic language detection for syntax highlighting": "Automatische taaldetectie voor zinsbouwmarkeringen inschakelen", - "Publish this room to the public in %(domain)s's room directory?": "Dit gesprek vermelden in de openbare gesprekkencatalogus van %(domain)s?", + "Publish this room to the public in %(domain)s's room directory?": "Deze kamer vermelden in de publieke kamersgids van %(domain)s?", "AM": "AM", "PM": "PM", "The maximum permitted number of widgets have already been added to this room.": "Het maximum aan toegestane widgets voor dit gesprek is al bereikt.", "To get started, please pick a username!": "Kies eerst een gebruikersnaam!", "Unable to create widget.": "Kan widget niet aanmaken.", - "You are not in this room.": "U maakt geen deel uit van dit gesprek.", - "You do not have permission to do that in this room.": "U bent niet bevoegd dat in dit gesprek te doen.", + "You are not in this room.": "U maakt geen deel uit van deze kamer.", + "You do not have permission to do that in this room.": "U heeft geen rechten om dat in deze kamer te doen.", "Example": "Voorbeeld", "Create": "Aanmaken", "Featured Rooms:": "Prominente gesprekken:", @@ -417,24 +417,24 @@ "Call Failed": "Oproep mislukt", "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Let op: elke persoon die u toevoegt aan een gemeenschap zal publiek zichtbaar zijn voor iedereen die de gemeenschaps-ID kent", "Invite new community members": "Nodig nieuwe gemeenschapsleden uit", - "Which rooms would you like to add to this community?": "Welke gesprekken wilt u toevoegen aan deze gemeenschap?", + "Which rooms would you like to add to this community?": "Welke kamers wilt u toevoegen aan deze gemeenschap?", "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Widgets verwijderen geldt voor alle deelnemers aan dit gesprek. Weet u zeker dat u deze widget wilt verwijderen?", "Delete Widget": "Widget verwijderen", "Who would you like to add to this community?": "Wie wil je toevoegen aan deze gemeenschap?", "Invite to Community": "Uitnodigen tot gemeenschap", - "Show these rooms to non-members on the community page and room list?": "Deze gesprekken tonen aan niet-leden op de gemeenschapspagina en openbare gesprekkenlijst?", - "Add rooms to the community": "Voeg gesprekken toe aan de gemeenschap", + "Show these rooms to non-members on the community page and room list?": "Deze kamers tonen aan niet-leden op de gemeenschapspagina en openbare kamerlijst?", + "Add rooms to the community": "Voeg kamers toe aan de gemeenschap", "Add to community": "Toevoegen aan gemeenschap", "Failed to invite the following users to %(groupId)s:": "Uitnodigen van volgende gebruikers tot %(groupId)s is mislukt:", "Failed to invite users to community": "Uitnodigen van gebruikers tot de gemeenschap is mislukt", "Failed to invite users to %(groupId)s": "Uitnodigen van gebruikers tot %(groupId)s is mislukt", - "Failed to add the following rooms to %(groupId)s:": "Toevoegen van volgende gesprekken aan %(groupId)s is mislukt:", + "Failed to add the following rooms to %(groupId)s:": "Toevoegen van de volgende kamers aan %(groupId)s is mislukt:", "Restricted": "Beperkte toegang", "Ignored user": "Genegeerde gebruiker", "You are now ignoring %(userId)s": "U negeert nu %(userId)s", "Unignored user": "Niet-genegeerde gebruiker", "You are no longer ignoring %(userId)s": "U negeert %(userId)s niet meer", - "%(senderName)s changed the pinned messages for the room.": "%(senderName)s heeft de vastgeprikte boodschappen voor het gesprek gewijzigd.", + "%(senderName)s changed the pinned messages for the room.": "%(senderName)s heeft de vastgeprikte boodschappen voor de kamer gewijzigd.", "Send": "Versturen", "Message Pinning": "Bericht vastprikken", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s %(day)s %(monthName)s %(fullYear)s", @@ -554,10 +554,10 @@ "%(severalUsers)schanged their name %(count)s times|one": "%(severalUsers)s hebben hun naam gewijzigd", "%(oneUser)schanged their name %(count)s times|other": "%(oneUser)s is %(count)s maal van naam veranderd", "%(oneUser)schanged their name %(count)s times|one": "%(oneUser)s is van naam veranderd", - "%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)s hebben hun avatar %(count)s keer gewijzigd", - "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)s hebben hun avatar gewijzigd", - "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)s is %(count)s maal van profielfoto veranderd", - "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)s is van profielfoto veranderd", + "%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)s hebben hun afbeelding %(count)s keer gewijzigd", + "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)s hebben hun afbeelding gewijzigd", + "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)s is %(count)s maal van afbeelding veranderd", + "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)s is van afbeelding veranderd", "%(items)s and %(count)s others|other": "%(items)s en %(count)s andere", "%(items)s and %(count)s others|one": "%(items)s en één ander", "collapse": "dichtvouwen", @@ -627,7 +627,7 @@ "Notify the whole room": "Laat dit aan het hele groepsgesprek weten", "Room Notification": "Groepsgespreksmelding", "The information being sent to us to help make %(brand)s better includes:": "De informatie die naar ons wordt verstuurd om %(brand)s te verbeteren bevat:", - "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Waar deze pagina identificeerbare informatie bevat, zoals een gespreks-, gebruikers- of groeps-ID, zullen deze gegevens verwijderd worden voordat ze naar de server gestuurd worden.", + "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Waar deze pagina identificeerbare informatie bevat, zoals een kamer-, persoon- of groep-ID, zullen deze gegevens verwijderd worden voordat ze naar de server gestuurd worden.", "The platform you're on": "Het platform dat u gebruikt", "The version of %(brand)s": "De versie van %(brand)s", "Your language of choice": "De door jou gekozen taal", @@ -653,7 +653,7 @@ "Code": "Code", "Unable to join community": "Kan niet toetreden tot de gemeenschap", "Unable to leave community": "Kan de gemeenschap niet verlaten", - "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Veranderingen aan uw gemeenschapsnaam en -avatar zullen mogelijk niet gezien worden door anderen tot maximaal 30 minuten.", + "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Veranderingen aan uw gemeenschapsnaam en -afbeelding zullen mogelijk niet gezien worden door anderen tot maximaal 30 minuten.", "Join this community": "Toetreden tot deze gemeenschap", "Who can join this community?": "Wie kan er tot deze gemeenschap toetreden?", "Everyone": "Iedereen", @@ -813,7 +813,7 @@ "A call is currently being placed!": "Er wordt al een oproep gemaakt!", "A call is already in progress!": "Er is al een gesprek actief!", "Permission Required": "Toestemming vereist", - "You do not have permission to start a conference call in this room": "Je hebt geen toestemming in dit groepsgesprek een vergadergesprek te starten", + "You do not have permission to start a conference call in this room": "U heeft geen rechten in deze kamer om een vergadering te starten", "This event could not be displayed": "Deze gebeurtenis kon niet weergegeven worden", "Demote yourself?": "Uzelf degraderen?", "Demote": "Degraderen", @@ -847,23 +847,23 @@ "Unable to load! Check your network connectivity and try again.": "Laden mislukt! Controleer je netwerktoegang en probeer het nogmaals.", "Failed to invite users to the room:": "Kon de volgende gebruikers hier niet uitnodigen:", "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Plakt ¯\\_(ツ)_/¯ vóór een bericht zonder opmaak", - "Upgrades a room to a new version": "Upgrade het gesprek naar de nieuwe versie", - "Changes your display nickname in the current room only": "Stelt uw weergavenaam alleen in het huidige gesprek in", - "Gets or sets the room topic": "Verkrijgt het onderwerp van het gesprek of stelt het in", - "This room has no topic.": "Dit gesprek heeft geen onderwerp.", - "Sets the room name": "Stelt de gespreksnaam in", - "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s heeft dit gesprek geüpgraded.", - "%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s heeft het gesprek toegankelijk gemaakt voor iedereen die de koppeling kent.", - "%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s heeft het gesprek enkel op uitnodiging toegankelijk gemaakt.", + "Upgrades a room to a new version": "Upgrade deze kamer naar een nieuwere versie", + "Changes your display nickname in the current room only": "Stelt uw weergavenaam alleen in de huidige kamer in", + "Gets or sets the room topic": "Verkrijgt het onderwerp van de kamer of stelt het in", + "This room has no topic.": "Deze kamer heeft geen onderwerp.", + "Sets the room name": "Stelt de kamernaam in", + "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s heeft deze kamer geüpgraded.", + "%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s heeft de kamer toegankelijk gemaakt voor iedereen die het adres weet.", + "%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s heeft de kamer enkel op uitnodiging toegankelijk gemaakt.", "%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s heeft de toegangsregel veranderd naar ‘%(rule)s’", - "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s heeft gasten toegestaan het gesprek te betreden.", - "%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s heeft gasten de toegang tot het gesprek ontzegd.", + "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s heeft gasten toegestaan de kamer te betreden.", + "%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s heeft gasten de toegang tot de kamer ontzegd.", "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s heeft de toegangsregel voor gasten op ‘%(rule)s’ ingesteld", - "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s heeft badges voor %(groups)s in dit gesprek ingeschakeld.", - "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s heeft badges voor %(groups)s in dit gesprek uitgeschakeld.", - "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s heeft badges in dit gesprek voor %(newGroups)s in-, en voor %(oldGroups)s uitgeschakeld.", - "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s heeft %(address)s als hoofdadres voor dit gesprek ingesteld.", - "%(senderName)s removed the main address for this room.": "%(senderName)s heeft het hoofdadres voor dit gesprek verwijderd.", + "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s heeft badges voor %(groups)s in deze kamer ingeschakeld.", + "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s heeft badges voor %(groups)s in deze kamer uitgeschakeld.", + "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s heeft badges in deze kamer voor %(newGroups)s in-, en voor %(oldGroups)s uitgeschakeld.", + "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s heeft %(address)s als hoofdadres voor deze kamer ingesteld.", + "%(senderName)s removed the main address for this room.": "%(senderName)s heeft het hoofdadres voor deze kamer verwijderd.", "%(displayName)s is typing …": "%(displayName)s is aan het typen…", "%(names)s and %(count)s others are typing …|other": "%(names)s en %(count)s anderen zijn aan het typen…", "%(names)s and %(count)s others are typing …|one": "%(names)s en nog iemand zijn aan het typen…", @@ -913,11 +913,11 @@ "Enable Emoji suggestions while typing": "Emoticons voorstellen tijdens het typen", "Show a placeholder for removed messages": "Verwijderde berichten vulling tonen", "Show join/leave messages (invites/kicks/bans unaffected)": "Berichten over toe- en uittredingen tonen (dit heeft geen effect op uitnodigingen, berispingen of verbanningen)", - "Show avatar changes": "Veranderingen van avatar tonen", + "Show avatar changes": "Veranderingen van afbeelding tonen", "Show display name changes": "Veranderingen van weergavenamen tonen", "Show read receipts sent by other users": "Door andere gebruikers verstuurde leesbevestigingen tonen", "Show a reminder to enable Secure Message Recovery in encrypted rooms": "Herinnering tonen om veilig berichtherstel in te schakelen in versleutelde gesprekken", - "Show avatars in user and room mentions": "Vermelde gebruikers- of gesprekkenavatars tonen", + "Show avatars in user and room mentions": "Vermelde personen- of kamerafbeelding tonen", "Enable big emoji in chat": "Grote emoji in gesprekken inschakelen", "Send typing notifications": "Typmeldingen versturen", "Enable Community Filter Panel": "Gemeenschapsfilterpaneel inschakelen", @@ -1014,7 +1014,7 @@ "Backup version: ": "Back-upversie: ", "Algorithm: ": "Algoritme: ", "Chat with %(brand)s Bot": "Met %(brand)s-robot chatten", - "Forces the current outbound group session in an encrypted room to be discarded": "Dwingt tot verwerping van de huidige uitwaartse groepssessie in een versleuteld gesprek", + "Forces the current outbound group session in an encrypted room to be discarded": "Dwingt tot verwerping van de huidige uitwaartse groepssessie in een versleutelde kamer", "Start using Key Backup": "Begin sleutelback-up te gebruiken", "Add an email address to configure email notifications": "Voeg een e-mailadres toe om e-mailmeldingen in te stellen", "Unable to verify phone number.": "Kan telefoonnummer niet verifiëren.", @@ -1057,7 +1057,7 @@ "Developer options": "Ontwikkelaarsopties", "Open Devtools": "Ontwikkelgereedschap openen", "Room Addresses": "Gespreksadressen", - "Change room avatar": "Gespreksavatar wijzigen", + "Change room avatar": "Kamerafbeelding wijzigen", "Change room name": "Gespreksnaam wijzigen", "Change main address for the room": "Hoofdadres voor het gesprek wijzigen", "Change history visibility": "Zichtbaarheid van geschiedenis wijzigen", @@ -1095,7 +1095,7 @@ "Main address": "Hoofdadres", "Error updating flair": "Fout bij bijwerken van badge", "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "Er is een fout opgetreden bij het bijwerken van de badge voor dit gesprek. Wellicht ondersteunt de server dit niet, of er is een tijdelijke fout opgetreden.", - "Room avatar": "Gespreksavatar", + "Room avatar": "Kamerafbeelding", "Room Name": "Gespreksnaam", "Room Topic": "Gespreksonderwerp", "This room is a continuation of another conversation.": "Dit gesprek is een voortzetting van een ander gesprek.", @@ -1135,7 +1135,7 @@ "The room upgrade could not be completed": "Het upgraden van het gesprek kon niet voltooid worden", "Upgrade this room to version %(version)s": "Upgrade dit gesprek tot versie %(version)s", "Upgrade Room Version": "Gespreksversie upgraden", - "Create a new room with the same name, description and avatar": "Een nieuw gesprek aanmaken met dezelfde naam, beschrijving en avatar", + "Create a new room with the same name, description and avatar": "Een nieuw kamer aanmaken met dezelfde naam, beschrijving en afbeelding", "Update any local room aliases to point to the new room": "Alle lokale gespreksbijnamen naar het nieuwe gesprek laten verwijzen", "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Mensen verhinderen aan de oude versie van het gesprek bij te dragen en daar een bericht te plaatsen dat de gebruikers verwijst naar het nieuwe gesprek", "Put a link back to the old room at the start of the new room so people can see old messages": "Bovenaan het nieuwe gesprek naar het oude verwijzen, om oude berichten te lezen", @@ -1234,10 +1234,10 @@ "Recovery Method Removed": "Herstelmethode verwijderd", "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Als u de herstelmethode niet heeft verwijderd, is het mogelijk dat er een aanvaller toegang tot uw account probeert te verkrijgen. Wijzig onmiddellijk uw wachtwoord en stel bij instellingen een nieuwe herstelmethode in.", "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Let op: gesprekken bijwerken voegt gespreksleden niet automatisch toe aan de nieuwe versie van het gesprek. Er komt in het oude gesprek een koppeling naar het nieuwe, waarop gespreksleden moeten klikken om aan het nieuwe gesprek deel te nemen.", - "Adds a custom widget by URL to the room": "Voegt met een URL een aangepaste widget toe aan het gesprek", + "Adds a custom widget by URL to the room": "Voegt met een URL een aangepaste widget toe aan de kamer", "Please supply a https:// or http:// widget URL": "Voer een https://- of http://-widget-URL in", - "You cannot modify widgets in this room.": "U kunt de widgets in dit gesprek niet aanpassen.", - "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s heeft de uitnodiging aan %(targetDisplayName)s toe te treden tot het gesprek ingetrokken.", + "You cannot modify widgets in this room.": "U kunt de widgets in deze kamer niet aanpassen.", + "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s heeft de uitnodiging aan %(targetDisplayName)s toe te treden tot deze kamer ingetrokken.", "Upgrade this room to the recommended room version": "Upgrade dit gesprek naar de aanbevolen gespreksversie", "This room is running room version , which this homeserver has marked as unstable.": "Dit gesprek draait op groepsgespreksversie , die door deze homeserver als onstabiel is gemarkeerd.", "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgraden zal de huidige versie van dit gesprek sluiten, en onder dezelfde naam een geüpgraded versie starten.", @@ -1255,7 +1255,7 @@ "The homeserver may be unavailable or overloaded.": "De homeserver is mogelijk onbereikbaar of overbelast.", "You have %(count)s unread notifications in a prior version of this room.|other": "U heeft %(count)s ongelezen meldingen in een vorige versie van dit gesprek.", "You have %(count)s unread notifications in a prior version of this room.|one": "U heeft %(count)s ongelezen meldingen in een vorige versie van dit gesprek.", - "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Of u de icoontjes voor recente gesprekken (boven de gesprekkenlijst) al dan niet gebruikt", + "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Of u al dan niet de afbeeldingen voor recent bekeken kamers (boven de kamerlijst) gebruikt", "Replying With Files": "Beantwoorden met bestanden", "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "Het is momenteel niet mogelijk met een bestand te antwoorden. Wil je dit bestand uploaden zonder te antwoorden?", "The file '%(fileName)s' failed to upload.": "Het bestand ‘%(fileName)s’ kon niet geüpload worden.", @@ -1279,9 +1279,9 @@ "Upload %(count)s other files|one": "%(count)s overig bestand versturen", "Cancel All": "Alles annuleren", "Upload Error": "Fout bij versturen van bestand", - "The server does not support the room version specified.": "De server ondersteunt deze versie van gesprekken niet.", + "The server does not support the room version specified.": "De server ondersteunt deze versie van kamers niet.", "Name or Matrix ID": "Naam of Matrix-ID", - "Changes your avatar in this current room only": "Verandert uw avatar enkel in het huidige gesprek", + "Changes your avatar in this current room only": "Verandert uw afbeelding alleen in de huidige kamer", "Unbans user with given ID": "Ontbant de gebruiker met de gegeven ID", "Sends the given message coloured as a rainbow": "Verstuurt het gegeven bericht in regenboogkleuren", "Sends the given emote coloured as a rainbow": "Verstuurt de gegeven emoticon in regenboogkleuren", @@ -1379,7 +1379,7 @@ "%(severalUsers)smade no changes %(count)s times|one": "%(severalUsers)s hebben niets gewijzigd", "%(oneUser)smade no changes %(count)s times|other": "%(oneUser)s heeft %(count)s keer niets gewijzigd", "%(oneUser)smade no changes %(count)s times|one": "%(oneUser)s heeft niets gewijzigd", - "Changes your avatar in all rooms": "Verandert uw avatar in alle gesprekken", + "Changes your avatar in all rooms": "Verandert uw afbeelding in alle kamer", "Removing…": "Bezig met verwijderen…", "Clear all data": "Alle gegevens wissen", "Your homeserver doesn't seem to support this feature.": "Uw homeserver biedt geen ondersteuning voor deze functie.", @@ -1446,7 +1446,7 @@ "Remove %(phone)s?": "%(phone)s verwijderen?", "Sends a message as plain text, without interpreting it as markdown": "Verstuurt een bericht als platte tekst, zonder het als markdown te interpreteren", "You do not have the required permissions to use this command.": "U beschikt niet over de vereiste machtigingen om deze opdracht uit te voeren.", - "Changes the avatar of the current room": "Wijzigt de afbeelding van het huidige gesprek", + "Changes the avatar of the current room": "Wijzigt de afbeelding van de huidige kamer", "Use an identity server": "Gebruik een identiteitsserver", "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Gebruik een identiteitsserver om uit te nodigen via e-mail. Klik op ‘Doorgaan’ om de standaardidentiteitsserver (%(defaultIdentityServerName)s) te gebruiken, of beheer de server in de instellingen.", "Use an identity server to invite by email. Manage in Settings.": "Gebruik een identiteitsserver om uit te nodigen via e-mail. Beheer de server in de instellingen.", @@ -1513,8 +1513,8 @@ "View": "Bekijken", "Find a room…": "Zoek een gesprek…", "Find a room… (e.g. %(exampleRoom)s)": "Zoek een gesprek… (bv. %(exampleRoom)s)", - "If you can't find the room you're looking for, ask for an invite or Create a new room.": "Als u het gesprek dat u zoekt niet kunt vinden, vraag dan een uitnodiging of maak een nieuw gesprek aan.", - "Explore rooms": "Gesprekken ontdekken", + "If you can't find the room you're looking for, ask for an invite or Create a new room.": "Als u de kamer die u zoekt niet kunt vinden, vraag dan een uitnodiging of maak een nieuwe kamer aan.", + "Explore rooms": "Kamers ontdekken", "Show previews/thumbnails for images": "Miniaturen voor afbeeldingen tonen", "Clear cache and reload": "Cache wissen en herladen", "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "U staat op het punt 1 bericht door %(user)s te verwijderen. Dit kan niet ongedaan gemaakt worden. Wilt u doorgaan?", @@ -1570,20 +1570,20 @@ "%(senderName)s placed a video call.": "%(senderName)s doet een video-oproep.", "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s doet een video-oproep, maar uw browser ondersteunt dat niet", "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s heeft de banregel voor gebruikers die met %(glob)s stroken verwijderd", - "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s heeft de banregel voor gesprekken die met %(glob)s stroken verwijderd", + "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s heeft de banregel voor kamers met %(glob)s verwijderd", "%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s heeft de banregel voor servers die met %(glob)s stroken verwijderd", "%(senderName)s removed a ban rule matching %(glob)s": "%(senderName)s heeft een banregel die met %(glob)s strookt verwijderd", "%(senderName)s updated an invalid ban rule": "%(senderName)s heeft een ongeldige banregel bijgewerkt", "%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s": "%(senderName)s heeft de regel bijgewerkt die gebruikers die met %(glob)s sporen verbant vanwege %(reason)s", - "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s heeft de regel bijgewerkt die gesprekken die met %(glob)s sporen verbant vanwege %(reason)s", + "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s heeft de regel bijgewerkt die kamers met %(glob)s verbant vanwege %(reason)s", "%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s heeft de regel bijgewerkt die servers die met %(glob)s sporen verbant vanwege %(reason)s", "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s": "%(senderName)s heeft een banregel vanwege %(reason)s die met %(glob)s spoort bijgewerkt", "%(senderName)s created a rule banning users matching %(glob)s for %(reason)s": "%(senderName)s heeft geregeld dat gebruikers die met %(glob)s sporen verbannen worden vanwege %(reason)s", - "%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s heeft geregeld dat gesprekken die met %(glob)s sporen verbannen worden vanwege %(reason)s", + "%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s heeft geregeld dat kamers met %(glob)s verbannen worden vanwege %(reason)s", "%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s heeft geregeld dat servers die met %(glob)s sporen verbannen worden vanwege %(reason)s", "%(senderName)s created a ban rule matching %(glob)s for %(reason)s": "%(senderName)s heeft geregeld dat alles wat met %(glob)s spoort verbannen wordt vanwege %(reason)s", "%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s heeft het patroon van een banregel voor gebruikers wegens %(reason)s aangepast van %(oldGlob)s tot %(newGlob)s", - "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s heeft het patroon van een banregel voor gesprekken wegens %(reason)s aangepast van %(oldGlob)s tot %(newGlob)s", + "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s heeft het patroon van een banregel voor kamers wegens %(reason)s aangepast van %(oldGlob)s tot %(newGlob)s", "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s heeft het patroon van een banregel voor servers wegens %(reason)s aangepast van %(oldGlob)s tot %(newGlob)s", "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s heeft het patroon van een banregel wegens %(reason)s aangepast van %(oldGlob)s tot %(newGlob)s", "The message you are trying to send is too large.": "Uw bericht is te lang om te versturen.", @@ -1858,7 +1858,7 @@ "Cancel search": "Zoeken annuleren", "Any of the following data may be shared:": "De volgende gegevens worden mogelijk gedeeld:", "Your display name": "Uw weergavenaam", - "Your avatar URL": "De URL van uw profielfoto", + "Your avatar URL": "De URL van uw afbeelding", "Your user ID": "Uw gebruikers-ID", "Your theme": "Uw thema", "%(brand)s URL": "%(brand)s-URL", @@ -1971,7 +1971,7 @@ "Not currently indexing messages for any room.": "Er worden momenteel voor geen enkel gesprek berichten geïndexeerd.", "%(doneRooms)s out of %(totalRooms)s": "%(doneRooms)s van %(totalRooms)s", "Where you’re logged in": "Waar u bent ingelogd", - "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Beheer hieronder de namen van uw sessies en meld ze af. Of verifieer ze in uw gebruikersprofiel.", + "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Beheer hieronder de namen van uw sessies, verwijder ze of verifieer ze in op uw profiel.", "Use Single Sign On to continue": "Ga verder met eenmalige aanmelding", "Confirm adding this email address by using Single Sign On to prove your identity.": "Bevestig je identiteit met je eenmalige aanmelding om dit e-mailadres toe te voegen.", "Single Sign On": "Eenmalige aanmelding", @@ -1987,23 +1987,23 @@ "Sends a message as html, without interpreting it as markdown": "Stuurt een bericht als HTML, zonder markdown toe te passen", "Failed to set topic": "Kon onderwerp niet instellen", "Command failed": "Opdracht mislukt", - "Could not find user in room": "Kon die deelnemer aan het gesprek niet vinden", + "Could not find user in room": "Kan die persoon in de kamer niet vinden", "Please supply a widget URL or embed code": "Gelieve een widgetURL of in te bedden code te geven", "Send a bug report with logs": "Stuur een bugrapport met logs", - "%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.": "%(senderDisplayName)s heeft het gesprek %(oldRoomName)s hernoemd tot %(newRoomName)s.", - "%(senderName)s added the alternative addresses %(addresses)s for this room.|other": "%(senderName)s heeft dit gesprek de nevenadressen %(addresses)s toegekend.", - "%(senderName)s added the alternative addresses %(addresses)s for this room.|one": "%(senderName)s heeft dit gesprek het nevenadres %(addresses)s toegekend.", - "%(senderName)s removed the alternative addresses %(addresses)s for this room.|other": "%(senderName)s heeft de nevenadressen %(addresses)s voor dit gesprek geschrapt.", - "%(senderName)s removed the alternative addresses %(addresses)s for this room.|one": "%(senderName)s heeft het nevenadres %(addresses)s voor dit gesprek geschrapt.", - "%(senderName)s changed the alternative addresses for this room.": "%(senderName)s heeft de nevenadressen voor dit gesprek gewijzigd.", - "%(senderName)s changed the main and alternative addresses for this room.": "%(senderName)s heeft hoofd- en nevenadressen voor dit gesprek gewijzigd.", - "%(senderName)s changed the addresses for this room.": "%(senderName)s heeft de adressen voor dit gesprek gewijzigd.", + "%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.": "%(senderDisplayName)s heeft de kamer %(oldRoomName)s hernoemd tot %(newRoomName)s.", + "%(senderName)s added the alternative addresses %(addresses)s for this room.|other": "%(senderName)s heeft dit kamer de nevenadressen %(addresses)s toegekend.", + "%(senderName)s added the alternative addresses %(addresses)s for this room.|one": "%(senderName)s heeft deze kamer het nevenadres %(addresses)s toegekend.", + "%(senderName)s removed the alternative addresses %(addresses)s for this room.|other": "%(senderName)s heeft de nevenadressen %(addresses)s voor deze kamer geschrapt.", + "%(senderName)s removed the alternative addresses %(addresses)s for this room.|one": "%(senderName)s heeft het nevenadres %(addresses)s voor deze kamer geschrapt.", + "%(senderName)s changed the alternative addresses for this room.": "%(senderName)s heeft de nevenadressen voor deze kamer gewijzigd.", + "%(senderName)s changed the main and alternative addresses for this room.": "%(senderName)s heeft hoofd- en nevenadressen voor deze kamer gewijzigd.", + "%(senderName)s changed the addresses for this room.": "%(senderName)s heeft de adressen voor deze kamer gewijzigd.", "You signed in to a new session without verifying it:": "U heeft zich bij een nog niet geverifieerde sessie aangemeld:", "Verify your other session using one of the options below.": "Verifieer uw andere sessie op een van onderstaande wijzen.", "Manually Verify by Text": "Handmatig middels een tekst", "Interactively verify by Emoji": "Interactief middels emojis", "Support adding custom themes": "Sta maatwerkthema's toe", - "Opens chat with the given user": "Start een gesprek met die gebruiker", + "Opens chat with the given user": "Start een chat met die persoon", "Sends a message to the given user": "Zendt die gebruiker een bericht", "Font scaling": "Lettergrootte", "Verify all your sessions to ensure your account & messages are safe": "Controleer al uw sessies om zeker te zijn dat uw account & berichten veilig zijn", @@ -2011,9 +2011,9 @@ "Confirm your account deactivation by using Single Sign On to prove your identity.": "Bevestig de deactivering van uw account door gebruik te maken van eenmalige aanmelding om uw identiteit te bewijzen.", "Are you sure you want to deactivate your account? This is irreversible.": "Weet u zeker dat u uw account wil sluiten? Dit is onomkeerbaar.", "Confirm account deactivation": "Bevestig accountsluiting", - "Room name or address": "Gespreksnaam of -adres", - "Joins room with given address": "Neem aan het gesprek met dat adres deel", - "Unrecognised room address:": "Gespreksadres niet herkend:", + "Room name or address": "Kamernaam of -adres", + "Joins room with given address": "Neem aan de kamer met dat adres deel", + "Unrecognised room address:": "Kameradres niet herkend:", "Help us improve %(brand)s": "Help ons %(brand)s nog beter te maken", "Send anonymous usage data which helps us improve %(brand)s. This will use a cookie.": "Stuur anonieme gebruiksinformatie waarmee we %(brand)s kunnen verbeteren. Dit plaatst een cookie.", "I want to help": "Ik wil helpen", @@ -2296,16 +2296,16 @@ "Belarus": "Wit-Rusland", "Barbados": "Barbados", "Bangladesh": "Bangladesh", - "See when the name changes in your active room": "Zien wanneer de naam in uw actieve gesprek veranderd", - "Change the name of your active room": "Verander de naam van uw actieve gesprek", - "See when the name changes in this room": "Zien wanneer de naam in dit gesprek veranderd", - "Change the name of this room": "Verander de naam van dit gesprek", - "See when the topic changes in your active room": "Zien wanneer het onderwerp veranderd van uw actieve gesprek", - "Change the topic of your active room": "Verander het onderwerp van uw actieve gesprek", - "See when the topic changes in this room": "Zien wanneer het onderwerp van dit gesprek veranderd", - "Change the topic of this room": "Verander het onderwerp van dit gesprek", - "Change which room, message, or user you're viewing": "Verander welk gesprek, bericht of welke gebruiker u ziet", - "Change which room you're viewing": "Verander welk gesprek u ziet", + "See when the name changes in your active room": "Zien wanneer de naam in uw actieve kamer veranderd", + "Change the name of your active room": "Verander de naam van uw actieve kamer", + "See when the name changes in this room": "Zien wanneer de naam in deze kamer veranderd", + "Change the name of this room": "Verander de naam van deze kamer", + "See when the topic changes in your active room": "Zien wanneer het onderwerp veranderd van uw actieve kamer", + "Change the topic of your active room": "Verander het onderwerp van uw actieve kamer", + "See when the topic changes in this room": "Zien wanneer het onderwerp van deze kamer veranderd", + "Change the topic of this room": "Verander het onderwerp van deze kamer", + "Change which room, message, or user you're viewing": "Verander welk kamer, bericht of welke persoon u ziet", + "Change which room you're viewing": "Verander welke kamer u ziet", "(connection failed)": "(verbinden mislukt)", "Places the call in the current room on hold": "De huidige oproep in de wacht zetten", "Effects": "Effecten", @@ -2323,12 +2323,12 @@ "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Privégesprekken zijn alleen zichtbaar en toegankelijk met een uitnodiging. Openbare gesprekken zijn zichtbaar en toegankelijk voor iedereen in deze gemeenschap.", "This room is public": "Dit gesprek is openbaar", "Show previews of messages": "Voorvertoning van berichten inschakelen", - "Show message previews for reactions in all rooms": "Toon berichtvoorbeelden voor reacties in alle gesprekken", + "Show message previews for reactions in all rooms": "Berichtvoorbeelden voor reacties in alle kamers tonen", "Explore public rooms": "Verken openbare gesprekken", "Leave Room": "Gesprek verlaten", "Room options": "Gesprekopties", "Start a conversation with someone using their name, email address or username (like ).": "Start een gesprek met iemand door hun naam, emailadres of gebruikersnaam (zoals ) te typen.", - "Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.": "Berichten hier zijn eind-tot-eind versleuteld. Verifieer %(displayName)s op hun profiel - klik op hun avatar.", + "Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.": "Berichten hier zijn eind-tot-eind versleuteld. Verifieer %(displayName)s op hun profiel - klik op hun afbeelding.", "%(creator)s created this DM.": "%(creator)s maakte deze DM.", "Switch to dark mode": "Naar donkere modus wisselen", "Switch to light mode": "Naar lichte modus wisselen", @@ -2605,22 +2605,22 @@ "with an empty state key": "met een lege statussleutel", "See when anyone posts a sticker to your active room": "Zien wanneer iemand een sticker in uw actieve gesprek verstuurd", "Send stickers to your active room as you": "Stuur stickers naar uw actieve gesprek als uzelf", - "See when a sticker is posted in this room": "Zien wanneer stickers in dit gesprek zijn verstuurd", - "Send stickers to this room as you": "Stuur stickers in dit gesprek als uzelf", - "See when the avatar changes in your active room": "Zien wanneer de avatar in uw actieve gesprek veranderd", - "Change the avatar of your active room": "Wijzig de avatar van uw actieve gesprek", - "See when the avatar changes in this room": "Zien wanneer de avatar in dit gesprek veranderd", - "Change the avatar of this room": "Wijzig de gespreksavatar", - "Send stickers into your active room": "Stuur stickers in uw actieve gesprek", - "Send stickers into this room": "Stuur stickers in dit gesprek", + "See when a sticker is posted in this room": "Zien wanneer stickers in deze kamer zijn verstuurd", + "Send stickers to this room as you": "Stuur stickers in deze kamer als uzelf", + "See when the avatar changes in your active room": "Zien wanneer de afbeelding in uw actieve kamer veranderd", + "Change the avatar of your active room": "Wijzig de afbeelding van uw actieve kamer", + "See when the avatar changes in this room": "Zien wanneer de afbeelding in deze kamer veranderd", + "Change the avatar of this room": "Wijzig de kamerafbeelding", + "Send stickers into your active room": "Stuur stickers in uw actieve kamer", + "Send stickers into this room": "Stuur stickers in deze kamer", "Remain on your screen while running": "Blijft op uw scherm terwijl het beschikbaar is", - "Remain on your screen when viewing another room, when running": "Blijft op uw scherm wanneer u een andere gesprek bekijkt, zolang het beschikbaar is", + "Remain on your screen when viewing another room, when running": "Blijft op uw scherm wanneer u een andere kamer bekijkt, zolang het bezig is", "(their device couldn't start the camera / microphone)": "(hun toestel kon de camera / microfoon niet starten)", - "🎉 All servers are banned from participating! This room can no longer be used.": "🎉 Alle servers zijn verbannen van deelname! Dit gesprek kan niet langer gebruikt worden.", - "%(senderDisplayName)s changed the server ACLs for this room.": "%(senderDisplayName)s veranderde de server ACL's voor dit gesprek.", - "%(senderDisplayName)s set the server ACLs for this room.": "%(senderDisplayName)s stelde de server ACL's voor dit gesprek in.", - "Converts the room to a DM": "Verandert dit groepsgesprek in een DM", - "Converts the DM to a room": "Verandert deze DM in een groepsgesprek", + "🎉 All servers are banned from participating! This room can no longer be used.": "🎉 Alle servers zijn verbannen van deelname! Deze kamer kan niet langer gebruikt worden.", + "%(senderDisplayName)s changed the server ACLs for this room.": "%(senderDisplayName)s veranderde de server ACL's voor deze kamer.", + "%(senderDisplayName)s set the server ACLs for this room.": "%(senderDisplayName)s stelde de server ACL's voor deze kamer in.", + "Converts the room to a DM": "Verandert deze kamer in een directe chat", + "Converts the DM to a room": "Verandert deze directe chat in een kamer", "Takes the call in the current room off hold": "De huidige oproep in huidige gesprek in de wacht zetten", "São Tomé & Príncipe": "Sao Tomé en Principe", "Swaziland": "Swaziland", @@ -2661,8 +2661,8 @@ "Go to Home View": "Ga naar welkomscherm", "Activate selected button": "Geselecteerde knop activeren", "Close dialog or context menu": "Dialoogvenster of contextmenu sluiten", - "Previous/next room or DM": "Vorige/volgende gesprek", - "Previous/next unread room or DM": "Vorige/volgende ongelezen gesprek", + "Previous/next room or DM": "Vorige/volgende kamer of directe chat", + "Previous/next unread room or DM": "Vorige/volgende ongelezen kamer of directe chat", "Clear room list filter field": "Gespreklijst filter wissen", "Expand room list section": "Gespreklijst selectie uitvouwen", "Collapse room list section": "Gespreklijst selectie invouwen", @@ -2759,7 +2759,7 @@ "Filter rooms and people": "Gespreken en personen filteren", "Explore rooms in %(communityName)s": "Ontdek de gesprekken van %(communityName)s", "delete the address.": "het adres verwijderen.", - "Delete the room address %(alias)s and remove %(name)s from the directory?": "Het gespreksadres %(alias)s en %(name)s uit de catalogus verwijderen?", + "Delete the room address %(alias)s and remove %(name)s from the directory?": "Het kameradres %(alias)s en %(name)s uit de gids verwijderen?", "You have no visible notifications.": "U hebt geen zichtbare meldingen.", "You’re all caught up": "U bent helemaal bij", "Self-verification request": "Verzoek om zelfverificatie", @@ -2846,7 +2846,7 @@ "a new cross-signing key signature": "een nieuwe kruiselings ondertekenen ondertekening", "a new master key signature": "een nieuwe hoofdsleutel ondertekening", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Dit zal ze niet uitnodigen voor %(communityName)s. Als u iemand wilt uitnodigen voor %(communityName)s, klik hier", - "Failed to transfer call": "Gesprek niet doorverbonden", + "Failed to transfer call": "Oproep niet doorverbonden", "A call can only be transferred to a single user.": "Een oproep kan slechts naar één gebruiker worden doorverbonden.", "Learn more in our , and .": "Meer informatie vindt u in onze , en .", "Continuing temporarily allows the %(hostSignupBrand)s setup process to access your account to fetch verified email addresses. This data is not stored.": "Door tijdelijk door te gaan, krijgt het installatieproces van %(hostSignupBrand)s toegang tot uw account om geverifieerde e-mailadressen op te halen. Deze gegevens worden niet opgeslagen.", @@ -2887,7 +2887,7 @@ "Screens": "Schermen", "Share your screen": "Uw scherm delen", "Submit logs": "Logs versturen", - "Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.": "Berichten in dit gesprek zijn eind-tot-eind-versleuteld. Als personen deelnemen, kan u ze verifiëren in hun profiel, tik hiervoor op hun avatar.", + "Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.": "Berichten in deze kamer zijn eind-tot-eind-versleuteld. Als personen deelnemen, kan u ze verifiëren in hun profiel, tik hiervoor op hun afbeelding.", "In encrypted rooms, verify all users to ensure it’s secure.": "Controleer alle gebruikers in versleutelde gesprekken om er zeker van te zijn dat het veilig is.", "Verify all users in a room to ensure it's secure.": "Controleer alle gebruikers in een gesprek om er zeker van te zijn dat het veilig is.", "%(count)s people|one": "%(count)s persoon", @@ -3086,8 +3086,8 @@ "Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. Requires compatible homeserver for some features.": "Spaces prototype. Niet compatibel met Gemeenschappen, Gemeenschappen v2 en Aangepaste Labels. Vereist een geschikte homeserver voor sommige functies.", "This homeserver has been blocked by it's administrator.": "Deze homeserver is geblokkeerd door zijn beheerder.", "This homeserver has been blocked by its administrator.": "Deze homeserver is geblokkeerd door uw beheerder.", - "Already in call": "Al in gesprek", - "You're already in a call with this person.": "U bent al in gesprek met deze persoon.", + "Already in call": "Al in de oproep", + "You're already in a call with this person.": "U bent al een oproep met deze persoon.", "Verify this login to access your encrypted messages and prove to others that this login is really you.": "Verifieer deze login om toegang te krijgen tot uw versleutelde berichten en om anderen te bewijzen dat deze login echt van u is.", "Verify with another session": "Verifieer met een andere sessie", "We'll create rooms for each of them. You can add more later too, including already existing ones.": "We zullen voor elk een gesprek maken. U kunt er later meer toevoegen, inclusief al bestaande gesprekken.", @@ -3143,7 +3143,7 @@ "Let's create a room for each of them.": "Laten we voor elk een los gesprek maken.", "What are some things you want to discuss in %(spaceName)s?": "Wat wilt u allemaal bespreken in %(spaceName)s?", "Verification requested": "Verificatieverzocht", - "Avatar": "Avatar", + "Avatar": "Afbeelding", "Verify other login": "Verifieer andere login", "You most likely do not want to reset your event index store": "U wilt waarschijnlijk niet uw gebeurtenisopslag-index resetten", "Reset event store?": "Gebeurtenisopslag resetten?", @@ -3258,10 +3258,10 @@ "Go to my space": "Ga naar mijn space", "sends space invaders": "verstuur space invaders", "Sends the given message with a space themed effect": "Verstuur het bericht met een space-thema-effect", - "See when people join, leave, or are invited to your active room": "Zie wanneer personen deelnemen, vertrekken of worden uitgenodigd in uw actieve gesprek", - "Kick, ban, or invite people to your active room, and make you leave": "Verwijder, verban of nodig personen uit voor uw actieve gesprek en uzelf laten vertrekken", - "See when people join, leave, or are invited to this room": "Zie wanneer personen deelnemen, vertrekken of worden uitgenodigd voor dit gesprek", - "Kick, ban, or invite people to this room, and make you leave": "Verwijder, verban of verwijder personen uit dit gesprek en uzelf laten vertrekken", + "See when people join, leave, or are invited to your active room": "Zie wanneer personen deelnemen, vertrekken of worden uitgenodigd in uw actieve kamer", + "Kick, ban, or invite people to your active room, and make you leave": "Verwijder, verban of nodig personen uit voor uw actieve kamer en uzelf laten vertrekken", + "See when people join, leave, or are invited to this room": "Zie wanneer personen deelnemen, vertrekken of worden uitgenodigd voor deze kamer", + "Kick, ban, or invite people to this room, and make you leave": "Verwijder, verban of verwijder personen uit deze kamer en uzelf laten vertrekken", "Currently joining %(count)s rooms|one": "Momenteel aan het toetreden tot %(count)s gesprek", "Currently joining %(count)s rooms|other": "Momenteel aan het toetreden tot %(count)s gesprekken", "Try different words or check for typos. Some results may not be visible as they're private and you need an invite to join them.": "Probeer andere woorden of controleer op typefouten. Sommige resultaten zijn mogelijk niet zichtbaar omdat ze privé zijn of u een uitnodiging nodig heeft om deel te nemen.", @@ -3288,7 +3288,7 @@ "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "Uw privéberichten zijn normaal gesproken versleuteld, maar dit gesprek niet. Meestal is dit te wijten aan een niet-ondersteund apparaat of methode die wordt gebruikt, zoals e-mailuitnodigingen. Versleuting inschakelen in instellingen.", "[number]": "[number]", "To view %(spaceName)s, you need an invite": "Om %(spaceName)s te bekijken heeft u een uitnodiging nodig", - "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "U kunt op elk moment op een avatar klikken in het filterpaneel om alleen de gesprekken en personen te zien die geassocieerd zijn met die gemeenschap.", + "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "U kunt op elk moment op een afbeelding klikken in het filterpaneel om alleen de kamers en personen te zien die geassocieerd zijn met die gemeenschap.", "Move down": "Omlaag", "Move up": "Omhoog", "Report": "Melden", @@ -3344,18 +3344,18 @@ "Show notification badges for People in Spaces": "Toon meldingsbadge voor personen in spaces", "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.": "Indien uitgeschakeld, kunt u nog steeds directe gesprekken toevoegen aan persoonlijke spaces. Indien ingeschakeld, ziet u automatisch iedereen die lid is van de space.", "Show people in spaces": "Toon personen in spaces", - "Show all rooms in Home": "Toon alle gesprekken in Home", + "Show all rooms in Home": "Alle gesprekken in Thuis tonen", "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Meld aan moderators prototype. In gesprekken die moderatie ondersteunen, kunt u met de `melden` knop misbruik melden aan de gesprekmoderators", - "%(senderName)s changed the pinned messages for the room.": "%(senderName)s heeft de vastgeprikte berichten voor het gesprek gewijzigd.", + "%(senderName)s changed the pinned messages for the room.": "%(senderName)s heeft de vastgeprikte berichten voor de kamer gewijzigd.", "%(senderName)s kicked %(targetName)s": "%(senderName)s heeft %(targetName)s verwijderd", "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s heeft %(targetName)s verbannen: %(reason)s", "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s heeft de uitnodiging van %(targetName)s ingetrokken", "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s heeft de uitnodiging van %(targetName)s ingetrokken: %(reason)s", "%(senderName)s unbanned %(targetName)s": "%(senderName)s heeft %(targetName)s ontbannen", - "%(targetName)s left the room": "%(targetName)s heeft het gesprek verlaten", - "%(targetName)s left the room: %(reason)s": "%(targetName)s heeft het gesprek verlaten: %(reason)s", + "%(targetName)s left the room": "%(targetName)s heeft de kamer verlaten", + "%(targetName)s left the room: %(reason)s": "%(targetName)s heeft de kamer verlaten: %(reason)s", "%(targetName)s rejected the invitation": "%(targetName)s heeft de uitnodiging geweigerd", - "%(targetName)s joined the room": "%(targetName)s is tot het gesprek toegetreden", + "%(targetName)s joined the room": "%(targetName)s is tot de kamer toegetreden", "%(senderName)s made no change": "%(senderName)s maakte geen wijziging", "%(senderName)s set a profile picture": "%(senderName)s profielfoto is ingesteld", "%(senderName)s changed their profile picture": "%(senderName)s profielfoto is gewijzigd", @@ -3472,5 +3472,66 @@ "Decide which spaces can access this room. If a space is selected, its members can find and join .": "Kies welke spaces toegang hebben tot dit gesprek. Als een space is geselecteerd kunnen deze leden vinden en aan deelnemen.", "Select spaces": "Spaces selecteren", "You're removing all spaces. Access will default to invite only": "U verwijderd alle spaces. De toegang zal standaard alleen op uitnodiging zijn", - "Room visibility": "Gesprekszichtbaarheid" + "Room visibility": "Gesprekszichtbaarheid", + "Anyone will be able to find and join this room.": "Iedereen kan de kamer vinden en aan deelnemen.", + "Share content": "Deel inhoud", + "Application window": "Deel een app", + "Share entire screen": "Deel uw gehele scherm", + "You can now share your screen by pressing the \"screen share\" button during a call. You can even do this in audio calls if both sides support it!": "U kunt uw schermdelen door te klikken op schermdelen-knop tijdens een oproep. U kunt dit zelfs doen tijdens een audiogesprek als de ontvanger het ook ondersteund!", + "Screen sharing is here!": "Schermdelen is hier!", + "Your camera is still enabled": "Uw camera is nog ingeschakeld", + "Your camera is turned off": "Uw camera staat uit", + "%(sharerName)s is presenting": "%(sharerName)s is aan het presenteren", + "You are presenting": "U bent aan het presenteren", + "Thank you for trying Spaces. Your feedback will help inform the next versions.": "Dankuwel voor het gebruiken van Spaces. Uw feedback helpt ons volgende versies te maken.", + "Spaces feedback": "Spaces feedback", + "Spaces are a new feature.": "Spaces zijn een nieuwe functie.", + "All rooms you're in will appear in Home.": "Alle kamers waar u in bent zullen in Home verschijnen.", + "Send pseudonymous analytics data": "Pseudonieme analytische gegevens verzenden", + "We're working on this, but just want to let you know.": "We zijn er nog mee bezig en wilde het u even laten weten.", + "Search for rooms or spaces": "Zoek naar kamers of spaces", + "Add space": "Space toevoegen", + "Are you sure you want to leave ?": "Weet u zeker dat u wilt verlaten?", + "Leave %(spaceName)s": "%(spaceName)s verlaten", + "You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.": "U bent de enige beheerder van sommige kamers of spaces die u wilt verlaten. Door deze te verlaten hebben ze geen beheerder meer.", + "You're the only admin of this space. Leaving it will mean no one has control over it.": "U bent de enige beheerder van deze space. Door te verlaten zal niemand er meer controle over hebben.", + "You won't be able to rejoin unless you are re-invited.": "U kunt niet opnieuw deelnemen behalve als u opnieuw wordt uitgenodigd.", + "Search %(spaceName)s": "Zoek %(spaceName)s", + "Leave specific rooms and spaces": "Verlaat specifieke kamers en spaces", + "Don't leave any": "Blijf in alle", + "Leave all rooms and spaces": "Verlaat alle kamers en spaces", + "Want to add an existing space instead?": "Een bestaande space toevoegen?", + "Private space (invite only)": "Privé space (alleen op uitnodiging)", + "Space visibility": "Space zichtbaarheid", + "Add a space to a space you manage.": "Voeg een space toe aan een space die u beheerd.", + "Only people invited will be able to find and join this space.": "Alleen uitgenodigde personen kunnen deze space vinden en aan deelnemen.", + "Anyone will be able to find and join this space, not just members of .": "Iedereen zal in staat zijn om deze space te vinden en aan deel te nemen, niet alleen leden van .", + "Anyone in will be able to find and join.": "Iedereen in zal in staat zijn om te zoeken en deel te nemen.", + "Adding spaces has moved.": "Spaces toevoegen is verplaatst.", + "Search for rooms": "Naar kamers zoeken", + "Search for spaces": "Naar spaces zoeken", + "Create a new space": "Maak een nieuwe space", + "Want to add a new space instead?": "Een nieuwe space toevoegen?", + "Add existing space": "Bestaande space toevoegen", + "Decrypting": "Ontsleutelen", + "Show all rooms": "Alle kamers tonen", + "Give feedback.": "Feedback geven.", + "Missed call": "Oproep gemist", + "Call declined": "Oproep geweigerd", + "Surround selected text when typing special characters": "Geselecteerde tekst omsluiten bij het typen van speciale tekens", + "%(severalUsers)schanged the pinned messages for the room %(count)s times.|other": "%(severalUsers)s wijzigden de vastgeprikte berichten voor de kamer %(count)s keer.", + "%(oneUser)schanged the pinned messages for the room %(count)s times.|other": "%(oneUser)s wijzigde de vastgeprikte berichten voor de kamer %(count)s keren.", + "Stop recording": "Opname stoppen", + "Send voice message": "Spraakbericht versturen", + "Olm version:": "Olm-versie:", + "Mute the microphone": "Microfoon uitschakelen", + "Unmute the microphone": "Microfoon inschakelen", + "Dialpad": "Toetsen", + "More": "Meer", + "Show sidebar": "Zijbalk weergeven", + "Hide sidebar": "Zijbalk verbergen", + "Start sharing your screen": "Schermdelen starten", + "Stop sharing your screen": "Schermdelen stoppen", + "Stop the camera": "Camera stoppen", + "Start the camera": "Camera starten" } diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index 7b45b30f4b..d5de4d2aac 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -3122,5 +3122,127 @@ "Identity server (%(server)s)": "Servidor de identidade (%(server)s)", "Could not connect to identity server": "Não foi possível conectar-se ao servidor de identidade", "Not a valid identity server (status code %(code)s)": "Servidor de identidade inválido (código de status %(code)s)", - "Identity server URL must be HTTPS": "O link do servidor de identidade deve começar com HTTPS" + "Identity server URL must be HTTPS": "O link do servidor de identidade deve começar com HTTPS", + "%(senderName)s banned %(targetName)s": "%(senderName)s baniu %(targetName)s", + "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s baniu %(targetName)s: %(reason)s", + "%(senderName)s invited %(targetName)s": "%(senderName)s convidou %(targetName)s", + "%(targetName)s accepted an invitation": "%(targetName)s aceitou o convite", + "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s aceitou o convite para %(displayName)s", + "Sends the given message as a spoiler": "Envia esta mensagem como spoiler", + "Some invites couldn't be sent": "Alguns convites não puderam ser enviados", + "We sent the others, but the below people couldn't be invited to ": "Nós enviamos aos outros, mas as pessoas abaixo não puderam ser convidadas para ", + "Transfer Failed": "A Transferência Falhou", + "Already in call": "Já em um chamada", + "Unable to transfer call": "Não foi possível transferir chamada", + "The user you called is busy.": "O usuário que você chamou está ocupado.", + "User Busy": "Usuário Ocupado", + "Accept on your other login…": "Aceite no seu outro login…", + "This space has no local addresses": "Este espaço não tem endereços locais", + "Delete recording": "Deletar a gravação", + "Stop the recording": "Parar a gravação", + "Record a voice message": "Gravar uma mensagem de voz", + "No microphone found": "Nenhum microfone encontrado", + "Unable to access your microphone": "Não foi possível acessar seu microfone", + "Copy Room Link": "Copiar o Link da Sala", + "Invite People": "Convidar Pessoas", + "Quick actions": "Ações rápidas", + "Screen sharing is here!": "Compartilhamento de tela está aqui!", + "View message": "Ver mensagem", + "End-to-end encryption isn't enabled": "Criptografia de ponta-a-ponta não está habilitada", + "Invite to just this room": "Convidar apenas a esta sala", + "%(seconds)ss left": "%(seconds)s restantes", + "Failed to send": "Falhou a enviar", + "Access": "Acesso", + "Decide who can join %(roomName)s.": "Decida quem pode entrar em %(roomName)s.", + "Space members": "Membros do espaço", + "Spaces with access": "Espaço com acesso", + "& %(count)s more|other": "e %(count)s mais", + "Upgrade required": "Atualização necessária", + "Anyone can find and join.": "Todos podem encontrar e entrar.", + "Only invited people can join.": "Apenas pessoas convidadas podem entrar.", + "Private (invite only)": "Privado (convite apenas)", + "Change server ACLs": "Mudar o ACL do servidor", + "You have no ignored users.": "Você não tem usuários ignorados.", + "Space information": "Informações do espaço", + "Images, GIFs and videos": "Imagens, GIFs e vídeos", + "Code blocks": "Blocos de código", + "To view all keyboard shortcuts, click here.": "Para ver todos os atalhos de teclado, clique aqui.", + "Keyboard shortcuts": "Teclas de atalho do teclado", + "Warn before quitting": "Avisar antes de sair", + "Access Token": "Símbolo de acesso", + "Message bubbles": "Balões de mensagem", + "IRC": "IRC", + "Mentions & keywords": "Menções e palavras-chave", + "Global": "Global", + "New keyword": "Nova palavra-chave", + "Keyword": "Palavra-chave", + "Enable for this account": "Habilitar para esta conta", + "Error saving notification preferences": "Erro ao salvar as preferências de notificações", + "Messages containing keywords": "Mensagens contendo palavras-chave", + "Collapse": "Colapsar", + "Expand": "Expandir", + "Recommended for public spaces.": "Recomendado para espaços públicos.", + "Preview Space": "Previsualizar o Espaço", + "Invite only": "Convidar apenas", + "Visibility": "Visibilidade", + "This may be useful for public spaces.": "Isso pode ser útil para espaços públicos.", + "Enable guest access": "Habilitar acesso a convidados", + "Invite with email or username": "Convidar com email ou nome de usuário", + "Show all rooms": "Mostrar todas as salas", + "You can change these anytime.": "Você pode mudá-los a qualquer instante.", + "Address": "Endereço", + "e.g. my-space": "e.g. meu-espaco", + "Give feedback.": "Enviar feedback.", + "Spaces feedback": "Feedback dos espaços", + "Spaces are a new feature.": "Espaços são uma nova funcionalidade.", + "Please enter a name for the space": "Por favor entre o nome do espaço", + "Your camera is still enabled": "Sua câmera ainda está habilitada", + "Your camera is turned off": "Sua câmera está desligada", + "%(sharerName)s is presenting": "%(sharerName)s está apresentando", + "You are presenting": "Você está apresentando", + "Connecting": "Conectando", + "unknown person": "pessoa desconhecida", + "sends space invaders": "envia os invasores do espaço", + "All rooms you're in will appear in Home.": "Todas as salas que você estiver presente aparecerão no Início.", + "Show all rooms in Home": "Mostrar todas as salas no Início", + "Use Ctrl + F to search timeline": "Use Ctrl + F para pesquisar a linha de tempo", + "Use Command + F to search timeline": "Use Command + F para pesquisar a linha de tempo", + "Send pseudonymous analytics data": "Enviar dados analíticos de pseudônimos", + "Show options to enable 'Do not disturb' mode": "Mostrar opções para habilitar o modo 'Não perturbe'", + "Your feedback will help make spaces better. The more detail you can go into, the better.": "Seu feedback ajudará a fazer os espaços melhores. Quanto mais detalhes você puder dar, melhor.", + "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta disponível para a web, desktop e Android. Obrigado por tentar o beta.", + "Spaces are a new way to group rooms and people.": "Espaços são uma nova forma para agrupar salas e pessoas.", + "To help space members find and join a private room, go to that room's Security & Privacy settings.": "Ajudar membros do espaço a encontrar e entrar uma sala privada, vá para as configurações de Segurança e Privacidade da sala.", + "Help people in spaces to find and join private rooms": "Ajude pessoas dos espaços a encontrarem e entrarem em salas privadas", + "Help space members find private rooms": "Ajude os membros do espaço a encontrarem salas privadas", + "New in the Spaces beta": "Novo nos Espaços beta", + "Check your devices": "Confira seus dispositivos", + "%(deviceId)s from %(ip)s": "%(deviceId)s de %(ip)s", + "Silence call": "Silenciar chamado", + "Sound on": "Som ligado", + "Review to ensure your account is safe": "Revise para assegurar que sua conta está segura", + "You have unverified logins": "Você tem logins não verificados", + "User %(userId)s is already invited to the room": "O usuário %(userId)s já foi convidado para a sala", + "See when people join, leave, or are invited to your active room": "Ver quando as pessoas entram, saem, ou são convidadas para sua sala ativa", + "Kick, ban, or invite people to your active room, and make you leave": "Expulsar, banir ou convidar pessoas para sua sala ativa, e fazer você sair", + "See when people join, leave, or are invited to this room": "Ver quando as pessoas entrarem, sairem ou são convidadas para esta sala", + "Kick, ban, or invite people to this room, and make you leave": "Expulsar, banir, ou convidar pessoas para esta sala, e fazer você sair", + "%(senderName)s changed the pinned messages for the room.": "%(senderName)s mudou a mensagem fixada da sala.", + "%(senderName)s kicked %(targetName)s": "%(senderName)s expulsou %(targetName)s", + "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s expulsou %(targetName)s: %(reason)s", + "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s recusou o convite de %(targetName)s", + "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s recusou o convite de %(targetName)s: %(reason)s", + "%(senderName)s unbanned %(targetName)s": "%(senderName)s desbaniu %(targetName)s", + "%(targetName)s left the room": "%(targetName)s saiu da sala", + "%(targetName)s left the room: %(reason)s": "%(targetName)s saiu da sala: %(reason)s", + "%(targetName)s rejected the invitation": "%(targetName)s rejeitou o convite", + "%(targetName)s joined the room": "%(targetName)s entrou na sala", + "%(senderName)s made no change": "%(senderName)s não fez mudanças", + "%(senderName)s set a profile picture": "%(senderName)s definiu sua foto de perfil", + "%(senderName)s changed their profile picture": "%(senderName)s mudou sua foto de perfil", + "%(senderName)s removed their profile picture": "%(senderName)s removeu sua foto de perfil", + "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s removeu seu nome de exibição (%(oldDisplayName)s)", + "%(senderName)s set their display name to %(displayName)s": "%(senderName)s definiu seu nome de exibição para %(displayName)s", + "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s mudou seu nome de exibição para %(displayName)s", + "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "Você pode sair do beta a qualquer momento nas configurações ou tocando em um emblema beta, como o mostrado acima." } diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index e562ce074b..789a38811f 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -1483,7 +1483,7 @@ "Verify the link in your inbox": "Проверьте ссылку в вашем почтовом ящике(папка \"Входящие\")", "Complete": "Выполнено", "Revoke": "Отмена", - "Share": "Делиться", + "Share": "Поделиться", "Discovery options will appear once you have added an email above.": "Параметры поиска по электронной почты появятся после добавления её выше.", "Unable to revoke sharing for phone number": "Не удалось отменить общий доступ к номеру телефона", "Unable to share phone number": "Не удается предоставить общий доступ к номеру телефона", @@ -1544,7 +1544,7 @@ "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Задайте адрес электронной почты для восстановления учетной записи. Чтобы знакомые могли вас найти, задайте адрес почты.", "Enter your custom homeserver URL What does this mean?": "Введите ссылку на другой домашний сервер Что это значит?", "Enter your custom identity server URL What does this mean?": "Введите ссылку на другой сервер идентификации Что это значит?", - "%(creator)s created and configured the room.": "%(creator)s создал и настроил комнату.", + "%(creator)s created and configured the room.": "%(creator)s создал(а) и настроил(а) комнату.", "Preview": "Заглянуть", "View": "Просмотр", "Find a room…": "Поиск комнат…", @@ -3229,7 +3229,355 @@ "Use an integration manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Используйте менеджер интеграций %(serverName)s для управления ботами, виджетами и стикерами.", "Identity server": "Сервер идентификаций", "Identity server (%(server)s)": "Сервер идентификации (%(server)s)", - "Could not connect to identity server": "Не смог подключиться к серверу идентификации", + "Could not connect to identity server": "Не удалось подключиться к серверу идентификации", "Not a valid identity server (status code %(code)s)": "Неправильный Сервер идентификации (код статуса %(code)s)", - "Identity server URL must be HTTPS": "URL-адрес сервера идентификации должен быть HTTPS" + "Identity server URL must be HTTPS": "URL-адрес сервера идентификации должен быть HTTPS", + "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "Общение с %(transferTarget)s. Перевод на %(transferee)s", + "[number]": "[номер]", + "Enter your Security Phrase a second time to confirm it.": "Введите секретную фразу второй раз, чтобы подтвердить ее.", + "Space Autocomplete": "Автозаполнение пространства", + "Without verifying, you won’t have access to all your messages and may appear as untrusted to others.": "Без проверки вы не сможете получить доступ ко всем своим сообщениям и можете показаться другим людям недоверенным.", + "Verify your identity to access encrypted messages and prove your identity to others.": "Проверьте свою личность, чтобы получить доступ к зашифрованным сообщениям и доказать свою личность другим.", + "Use another login": "Используйте другой логин", + "Please choose a strong password": "Пожалуйста, выберите надежный пароль", + "Currently joining %(count)s rooms|one": "Сейчас вы присоединяетесь к %(count)s комнате", + "Currently joining %(count)s rooms|other": "Сейчас вы присоединяетесь к %(count)s комнатам", + "You can add more later too, including already existing ones.": "Позже можно добавить и другие, в том числе уже существующие.", + "Let's create a room for each of them.": "Давайте создадим для каждого из них отдельную комнату.", + "What are some things you want to discuss in %(spaceName)s?": "Какие вещи вы хотите обсуждать в %(spaceName)s?", + "This is an experimental feature. For now, new users receiving an invite will have to open the invite on to actually join.": "Это экспериментальная функция. Пока что новые пользователи, получившие приглашение, должны будут открыть приглашение на , чтобы присоединиться.", + "We're working on this, but just want to let you know.": "Мы работаем над этим, но просто хотим, чтобы вы знали.", + "Teammates might not be able to view or join any private rooms you make.": "Члены команды могут не иметь возможности просматривать или присоединяться к созданным вами личным комнатам.", + "Go to my space": "В моё пространство", + "Search for rooms or spaces": "Поиск комнат или пространств", + "Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.": "Выберите комнаты или разговоры для добавления. Это просто место для вас, никто не будет проинформирован. Вы можете добавить больше позже.", + "What do you want to organise?": "Что вы хотели бы организовать?", + "To view %(spaceName)s, you need an invite": "Для просмотра %(spaceName)s необходимо приглашение", + "To join %(spaceName)s, turn on the Spaces beta": "Чтобы присоединиться к %(spaceName)s, включите Бета пространства", + "To view %(spaceName)s, turn on the Spaces beta": "Для просмотра %(spaceName)s включите Бета пространства", + "Search names and descriptions": "Искать имена и описания", + "Select a room below first": "Сначала выберите комнату ниже", + "You can select all or individual messages to retry or delete": "Вы можете выбрать все или отдельные сообщения для повторной попытки или удаления", + "Retry all": "Повторить все", + "Delete all": "Удалить все", + "Some of your messages have not been sent": "Некоторые из ваших сообщений не были отправлены", + "Filter all spaces": "Отфильтровать все пространства", + "Try different words or check for typos. Some results may not be visible as they're private and you need an invite to join them.": "Попробуйте использовать другие слова или проверьте опечатки. Некоторые результаты могут быть не видны, так как они приватные и для участия в них необходимо приглашение.", + "No results for \"%(query)s\"": "Нет результатов для \"%(query)s\"", + "Communities are changing to Spaces": "Сообщества изменены на Пространства", + "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "Вы можете в любой момент нажать на аватар в панели фильтров, чтобы увидеть только комнаты и людей, связанных с этим сообществом.", + "Verification requested": "Запрос на проверку отправлен", + "Unable to copy a link to the room to the clipboard.": "Не удалось скопировать ссылку на комнату в буфер обмена.", + "Unable to copy room link": "Не удалось скопировать ссылку на комнату", + "You are the only person here. If you leave, no one will be able to join in the future, including you.": "Вы здесь единственный человек. Если вы уйдете, никто не сможет присоединиться в будущем, включая вас.", + "Error downloading audio": "Ошибка загрузки аудио", + "Unnamed audio": "Безымянное аудио", + "Avatar": "Аватар", + "Join the beta": "Присоединиться к бета-версии", + "Leave the beta": "Покинуть бета-версию", + "Beta": "Бета", + "Tap for more info": "Нажмите для получения дополнительной информации", + "Spaces is a beta feature": "Пространства - это бета функция", + "Move down": "Опустить", + "Move up": "Поднять", + "Manage & explore rooms": "Управление и список комнат", + "Add space": "Добавить простанство", + "Report": "Сообщить", + "Collapse reply thread": "Свернуть ответы", + "Show preview": "Предпросмотр", + "View source": "Исходный код", + "Forward": "Переслать", + "If you reset everything, you will restart with no trusted sessions, no trusted users, and might not be able to see past messages.": "Если вы сбросите все настройки, вы перезагрузитесь без доверенных сессий, без доверенных пользователей и, возможно, не сможете просматривать прошлые сообщения.", + "Only do this if you have no other device to complete verification with.": "Делайте это только в том случае, если у вас нет другого устройства для завершения проверки.", + "Reset everything": "Сбросить все", + "Forgotten or lost all recovery methods? Reset all": "Забыли или потеряли все методы восстановления? Сбросить все", + "Verify other login": "Подтвердить другой вход", + "Settings - %(spaceName)s": "Настройки - %(spaceName)s", + "Reset event store": "Сброс хранилища событий", + "If you do, please note that none of your messages will be deleted, but the search experience might be degraded for a few moments whilst the index is recreated": "Если вы это сделаете, обратите внимание, что ни одно из ваших сообщений не будет удалено, но работа поиска может быть ухудшена на несколько мгновений, пока индекс не будет воссоздан", + "You most likely do not want to reset your event index store": "Скорее всего, вы не захотите сбрасывать индексное хранилище событий", + "Reset event store?": "Сбросить хранилище событий?", + "Please note upgrading will make a new version of the room. All current messages will stay in this archived room.": "При обновлении будет создана новая версия комнаты. Все текущие сообщения останутся в этой архивной комнате.", + "Automatically invite members from this room to the new one": "Автоматическое приглашение участников из этой комнаты в новую комнату", + "Report the entire room": "Сообщить обо всей комнате", + "Spam or propaganda": "Спам или пропаганда", + "Illegal Content": "Незаконный контент", + "Toxic Behaviour": "Токсичное поведение", + "Disagree": "Я не согласен с содержанием", + "Please pick a nature and describe what makes this message abusive.": "Пожалуйста, выберите характер и опишите, что делает это сообщение оскорбительным.", + "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.": "Любая другая причина. Пожалуйста, опишите проблему.\nОб этом будет сообщено модераторам комнаты.", + "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.": "Эта комната посвящена незаконному или токсичному контенту, или модераторы не справляются с модерацией незаконного или токсичного контента.\n Об этом будет сообщено администраторам %(homeserver)s.", + "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.": "Эта комната посвящена незаконному или токсичному контенту, или модераторы не справляются с модерацией незаконного или токсичного контента.\nОб этом будет сообщено администраторам %(homeserver)s. Администраторы НЕ смогут прочитать зашифрованное содержимое этой комнаты.", + "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.": "Этот пользователь спамит комнату рекламой, ссылками на рекламу или пропагандой.\nОб этом будет сообщено модераторам комнаты.", + "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.": "Этот пользователь демонстрирует незаконное поведение, например, домогается до людей или угрожает насилием.\nОб этом будет сообщено модераторам комнаты, которые могут передать дело в юридические органы.", + "This user is displaying toxic behaviour, for instance by insulting other users or sharing adult-only content in a family-friendly room or otherwise violating the rules of this room.\nThis will be reported to the room moderators.": "Этот пользователь демонстрирует токсичное поведение, например, оскорбляет других пользователей или делится контентом только для взрослых в комнате, предназначенной для семейного отдыха, или иным образом нарушает правила этой комнаты.\nОб этом будет сообщено модераторам комнаты.", + "What this user is writing is wrong.\nThis will be reported to the room moderators.": "То, что пишет этот пользователь, неправильно.\nОб этом будет сообщено модераторам комнаты.", + "These are likely ones other room admins are a part of.": "Это, скорее всего, те, в которых участвуют другие администраторы комнат.", + "Other spaces or rooms you might not know": "Другие пространства или комнаты, которые вы могли не знать", + "Spaces you know that contain this room": "Пространства, которые вы знаете, уже содержат эту комнату", + "Search spaces": "Поиск пространств", + "Decide which spaces can access this room. If a space is selected, its members can find and join .": "Определите, какие пространства могут получить доступ к этой комнате. Если пространство выбрано, его члены могут найти и присоединиться к .", + "Select spaces": "Выберите пространства", + "You're removing all spaces. Access will default to invite only": "Вы удаляете все пространства. Доступ будет по умолчанию только по приглашениям", + "Leave specific rooms and spaces": "Покинуть определенные комнаты и пространства", + "Are you sure you want to leave ?": "Вы уверены, что хотите покинуть ?", + "Leave %(spaceName)s": "Покинуть %(spaceName)s", + "You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.": "Вы являетесь единственным администратором некоторых комнат или пространств, которые вы хотите покинуть. Покинув их, вы оставите их без администраторов.", + "You're the only admin of this space. Leaving it will mean no one has control over it.": "Вы единственный администратор этого пространства. Если вы его оставите, это будет означать, что никто не имеет над ним контроля.", + "You won't be able to rejoin unless you are re-invited.": "Вы сможете присоединиться только после повторного приглашения.", + "Search %(spaceName)s": "Поиск %(spaceName)s", + "Don't leave any": "Не оставлять ничего", + "Leave all rooms and spaces": "Покинуть все комнаты и пространства", + "User Directory": "Каталог пользователей", + "Consult first": "Сначала спросить", + "Invited people will be able to read old messages.": "Приглашенные люди смогут читать старые сообщения.", + "Or send invite link": "Или отправьте ссылку на приглашение", + "If you can't see who you’re looking for, send them your invite link below.": "Если вы не видите того, кого ищете, отправьте ему свое приглашение по ссылке ниже.", + "Some suggestions may be hidden for privacy.": "Некоторые предложения могут быть скрыты в целях конфиденциальности.", + "We couldn't create your DM.": "Мы не смогли создать ваш диалог.", + "You may contact me if you have any follow up questions": "Вы можете связаться со мной, если у вас возникнут какие-либо последующие вопросы", + "Your platform and username will be noted to help us use your feedback as much as we can.": "Ваша платформа и имя пользователя будут отмечены, чтобы мы могли максимально использовать ваш отзыв.", + "Thank you for your feedback, we really appreciate it.": "Спасибо за ваш отзыв, мы очень ценим его.", + "Search for rooms or people": "Поиск комнат или людей", + "Message preview": "Просмотр сообщения", + "Forward message": "Переслать сообщение", + "Open link": "Открыть ссылку", + "Sent": "Отправлено", + "Sending": "Отправка", + "You don't have permission to do this": "У вас нет на это разрешения", + "Adding...": "Добавление…", + "Want to add an existing space instead?": "Хотите добавить существующее пространство?", + "Private space (invite only)": "Приватное пространство (только по приглашению)", + "Space visibility": "Видимость пространства", + "Add a space to a space you manage.": "Добавьте пространство в пространство, которым вы управляете.", + "Only people invited will be able to find and join this space.": "Только приглашенные люди смогут найти и присоединиться к этому пространству.", + "Anyone will be able to find and join this space, not just members of .": "Любой сможет найти и присоединиться к этому пространству, а не только члены .", + "Anyone in will be able to find and join.": "Любой человек в сможет найти и присоединиться.", + "Visible to space members": "Видимая для участников пространства", + "Public room": "Публичная комната", + "Private room (invite only)": "Приватная комната (только по приглашению)", + "Room visibility": "Видимость комнаты", + "Create a room": "Создать комнату", + "Only people invited will be able to find and join this room.": "Только приглашенные люди смогут найти и присоединиться к этой комнате.", + "Anyone will be able to find and join this room.": "Любой желающий сможет найти эту комнату и присоединиться к ней.", + "This upgrade will allow members of selected spaces access to this room without an invite.": "Это обновление позволит участникам выбранных пространств получить доступ в эту комнату без приглашения.", + "Anyone will be able to find and join this room, not just members of .": "Любой сможет найти и присоединиться к этой комнате, а не только участники .", + "You can change this at any time from room settings.": "Вы можете изменить это в любое время из настроек комнаты.", + "Everyone in will be able to find and join this room.": "Все в смогут найти и присоединиться к этой комнате.", + "To leave the beta, visit your settings.": "Чтобы выйти из бета-версии, зайдите в настройки.", + "%(featureName)s beta feedback": "%(featureName)s бета-отзыв", + "Adding spaces has moved.": "Добавление пространств перемещено.", + "Search for rooms": "Поиск комнат", + "Want to add a new room instead?": "Хотите добавить новую комнату?", + "Add existing rooms": "Добавить существующие комнаты", + "Adding rooms... (%(progress)s out of %(count)s)|one": "Добавление комнаты…", + "Adding rooms... (%(progress)s out of %(count)s)|other": "Добавление комнат… (%(progress)s из %(count)s)", + "Not all selected were added": "Не все выбранные добавлены", + "Search for spaces": "Поиск пространств", + "Create a new space": "Создать новое пространство", + "Want to add a new space instead?": "Хотите добавить новое пространство?", + "Add existing space": "Добавить существующее пространство", + "You are not allowed to view this server's rooms list": "Вам не разрешено просматривать список комнат этого сервера", + "Please provide an address": "Пожалуйста, укажите адрес", + "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)s изменил разрешения сервера", + "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)s изменил разрешения сервера %(count)s раз", + "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)s изменили разрешения сервера", + "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)s изменили разрешения сервера %(count)s раз", + "Zoom in": "Увеличить", + "Zoom out": "Уменьшить", + "%(count)s people you know have already joined|one": "%(count)s человек, которого вы знаете, уже присоединился", + "%(count)s people you know have already joined|other": "%(count)s человек, которых вы знаете, уже присоединились", + "%(count)s members including %(commaSeparatedMembers)s|one": "%(commaSeparatedMembers)s", + "%(count)s members including %(commaSeparatedMembers)s|other": "%(count)s участников, включая %(commaSeparatedMembers)s", + "Including %(commaSeparatedMembers)s": "Включая %(commaSeparatedMembers)s", + "View all %(count)s members|one": "Посмотреть 1 участника", + "View all %(count)s members|other": "Просмотреть всех %(count)s участников", + "Share content": "Поделиться содержимым", + "Application window": "Окно приложения", + "Share entire screen": "Поделиться всем экраном", + "Message search initialisation failed, check your settings for more information": "Инициализация поиска сообщений не удалась, проверьте ваши настройки для получения дополнительной информации", + "Error - Mixed content": "Ошибка - Смешанное содержание", + "Error loading Widget": "Ошибка загрузки виджета", + "Add reaction": "Добавить реакцию", + "Error processing voice message": "Ошибка при обработке голосового сообщения", + "Image": "Изображение", + "Sticker": "Стикер", + "Error processing audio message": "Ошибка обработки звукового сообщения", + "Decrypting": "Расшифровка", + "The call is in an unknown state!": "Вызов в неизвестном состоянии!", + "Unknown failure: %(reason)s)": "Неизвестная ошибка: %(reason)s", + "An unknown error occurred": "Произошла неизвестная ошибка", + "Their device couldn't start the camera or microphone": "Их устройство не может запустить камеру или микрофон", + "Connection failed": "Ошибка соединения", + "Could not connect media": "Сбой подключения", + "Missed call": "Пропущенный вызов", + "Call back": "Перезвонить", + "Call declined": "Вызов отклонён", + "Connected": "Подключено", + "Pinned messages": "Прикреплённые сообщения", + "If you have permissions, open the menu on any message and select Pin to stick them here.": "Если у вас есть разрешения, откройте меню на любом сообщении и выберите Прикрепить, чтобы поместить их сюда.", + "Nothing pinned, yet": "Пока ничего не прикреплено", + "Accept on your other login…": "Примите под другим логином…", + "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "Установите адреса для этого пространства, чтобы пользователи могли найти это пространство через ваш домашний сервер (%(localDomain)s)", + "To publish an address, it needs to be set as a local address first.": "Чтобы опубликовать адрес, его сначала нужно установить как локальный адрес.", + "Published addresses can be used by anyone on any server to join your room.": "Опубликованные адреса могут быть использованы любым человеком на любом сервере, чтобы присоединиться к вашей комнате.", + "Published addresses can be used by anyone on any server to join your space.": "Опубликованные адреса могут быть использованы любым человеком на любом сервере для присоединения к вашему пространству.", + "This space has no local addresses": "У этого пространства нет локальных адресов", + "Stop recording": "Остановить запись", + "Send voice message": "Отправить голосовое сообщение", + "We didn't find a microphone on your device. Please check your settings and try again.": "Мы не нашли микрофон на вашем устройстве. Пожалуйста, проверьте настройки и повторите попытку.", + "No microphone found": "Микрофон не найден", + "We were unable to access your microphone. Please check your browser settings and try again.": "Мы не смогли получить доступ к вашему микрофону. Пожалуйста, проверьте настройки браузера и повторите попытку.", + "Unable to access your microphone": "Не удалось получить доступ к микрофону", + "Copy Room Link": "Скопировать ссылку на комнату", + "%(count)s results in all spaces|one": "%(count)s результат по всем пространствам", + "%(count)s results in all spaces|other": "%(count)s результатов по всем пространствам", + "Quick actions": "Быстрые действия", + "You can now share your screen by pressing the \"screen share\" button during a call. You can even do this in audio calls if both sides support it!": "Теперь вы можете поделиться своим экраном, нажав кнопку \"Поделиться экраном\" во время вызова. Это можно делать даже в аудиовызовах, если оба собеседника поддерживают эту функцию!", + "Screen sharing is here!": "Совместное использование экрана здесь!", + "View message": "Посмотреть сообщение", + "End-to-end encryption isn't enabled": "Сквозное шифрование не включено", + "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "Ваши личные сообщения обычно шифруются, но эта комната не шифруется. Обычно это связано с использованием неподдерживаемого устройства или метода, например, приглашение по электронной почте. Включите шифрование в настройках.", + "Invite to just this room": "Пригласить только в эту комнату", + "%(seconds)ss left": "%(seconds)s осталось", + "Show %(count)s other previews|one": "Показать %(count)s другой предварительный просмотр", + "Show %(count)s other previews|other": "Показать %(count)s других предварительных просмотров", + "Failed to send": "Не удалось отправить", + "Access": "Доступ", + "People with supported clients will be able to join the room without having a registered account.": "Люди с поддерживаемыми клиентами смогут присоединиться к комнате, не имея зарегистрированной учётной записи.", + "Decide who can join %(roomName)s.": "Укажите, кто может присоединиться к %(roomName)s.", + "Space members": "Участники пространства", + "Anyone in a space can find and join. You can select multiple spaces.": "Любой человек в пространстве может найти и присоединиться. Вы можете выбрать несколько пространств.", + "Anyone in %(spaceName)s can find and join. You can select other spaces too.": "Любой человек в %(spaceName)s может найти и присоединиться. Вы можете выбрать и другие пространства.", + "Spaces with access": "Пространства с доступом", + "Anyone in a space can find and join. Edit which spaces can access here.": "Любой человек в пространстве может найти и присоединиться. Укажите здесь, какие пространства могут получить доступ.", + "Currently, %(count)s spaces have access|other": "В настоящее время %(count)s пространств имеют доступ", + "& %(count)s more|other": "и %(count)s ещё", + "Upgrade required": "Требуется обновление", + "Anyone can find and join.": "Любой желающий может найти и присоединиться.", + "Only invited people can join.": "Присоединиться могут только приглашенные люди.", + "Private (invite only)": "Приватное (только по приглашению)", + "Change server ACLs": "Изменить серверные разрешения", + "Space information": "Информация о пространстве", + "You have no ignored users.": "У вас нет игнорируемых пользователей.", + "Images, GIFs and videos": "Изображения, GIF и видео", + "Code blocks": "Блоки кода", + "Displaying time": "Отображение времени", + "To view all keyboard shortcuts, click here.": "Чтобы просмотреть все сочетания клавиш, нажмите здесь.", + "Keyboard shortcuts": "Горячие клавиши", + "Warn before quitting": "Предупредить перед выходом", + "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "Чувствуете себя экспериментатором? Лаборатории - это лучший способ получить информацию раньше, протестировать новые функции и помочь сформировать их до того, как они будут запущены. Узнайте больше.", + "Your access token gives full access to your account. Do not share it with anyone.": "Ваш токен доступа даёт полный доступ к вашей учётной записи. Не передавайте его никому.", + "Access Token": "Токен доступа", + "Message bubbles": "Пузыри сообщений", + "IRC": "IRC", + "There was an error loading your notification settings.": "Произошла ошибка при загрузке настроек уведомлений.", + "Mentions & keywords": "Упоминания и ключевые слова", + "Global": "Глобально", + "New keyword": "Новое ключевое слово", + "Keyword": "Ключевое слово", + "Enable email notifications for %(email)s": "Включить уведомления по электронной почте для %(email)s", + "Enable for this account": "Включить для этого аккаунта", + "An error occurred whilst saving your notification preferences.": "При сохранении ваших настроек уведомлений произошла ошибка.", + "Error saving notification preferences": "Ошибка при сохранении настроек уведомлений", + "Messages containing keywords": "Сообщения с ключевыми словами", + "Message search initialisation failed": "Инициализация поиска сообщений не удалась", + "Collapse": "Свернуть", + "Expand": "Развернуть", + "Recommended for public spaces.": "Рекомендуется для публичных пространств.", + "Allow people to preview your space before they join.": "Дайте людям возможность предварительно ознакомиться с вашим пространством, прежде чем они присоединятся к нему.", + "Preview Space": "Предварительный просмотр пространства", + "only invited people can view and join": "только приглашенные люди могут просматривать и присоединяться", + "anyone with the link can view and join": "каждый, кто имеет ссылку, может посмотреть и присоединиться", + "Decide who can view and join %(spaceName)s.": "Определите, кто может просматривать и присоединяться к %(spaceName)s.", + "Visibility": "Видимость", + "This may be useful for public spaces.": "Это может быть полезно для публичных пространств.", + "Guests can join a space without having an account.": "Гости могут присоединиться к пространству, не имея учётной записи.", + "Enable guest access": "Включить гостевой доступ", + "Failed to update the history visibility of this space": "Не удалось обновить видимость истории этого пространства", + "Failed to update the guest access of this space": "Не удалось обновить гостевой доступ к этому пространству", + "Failed to update the visibility of this space": "Не удалось обновить видимость этого пространства", + "Show all rooms": "Показать все комнаты", + "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Пространства являются новыми способами группировки комнат и людей. Чтобы присоединиться к существующему пространству, вам понадобится приглашение.", + "Address": "Адрес", + "e.g. my-space": "например, my-space", + "Give feedback.": "Дать отзыв.", + "Thank you for trying Spaces. Your feedback will help inform the next versions.": "Спасибо, что попробовали пространства. Ваши отзывы помогут при разработке следующих версий.", + "Spaces feedback": "Отзыв о пространствах", + "Spaces are a new feature.": "Пространства - это новая функция.", + "Please enter a name for the space": "Пожалуйста, введите название пространства", + "Your camera is still enabled": "Ваша камера всё ещё включена", + "Your camera is turned off": "Ваша камера выключена", + "%(sharerName)s is presenting": "%(sharerName)s показывает", + "You are presenting": "Вы показываете", + "unknown person": "Неизвестное лицо", + "Mute the microphone": "Заглушить микрофон", + "Unmute the microphone": "Включить звук микрофона", + "Dialpad": "Панель набора номера", + "More": "Больше", + "Show sidebar": "Показать боковую панель", + "Hide sidebar": "Скрыть боковую панель", + "Start sharing your screen": "Начать делиться экраном", + "Stop sharing your screen": "Перестать делиться экраном", + "Stop the camera": "Остановить камеру", + "Start the camera": "Запуск камеры", + "sends space invaders": "отправляет космических захватчиков", + "Sends the given message with a space themed effect": "Отправляет заданное сообщение с космическим тематическим эффектом", + "All rooms you're in will appear in Home.": "Все комнаты, в которых вы находитесь, будут отображаться в Главной.", + "Show all rooms in Home": "Показать все комнаты в Главной", + "Surround selected text when typing special characters": "Обводить выделенный текст при вводе специальных символов", + "Use Ctrl + F to search timeline": "Используйте Ctrl + F для поиска по временной шкале", + "Use Command + F to search timeline": "Используйте Command + F для поиска по временной шкале", + "New layout switcher (with message bubbles)": "Новый переключатель макета (с пузырями сообщений)", + "Send pseudonymous analytics data": "Отправка псевдонимных аналитических данных", + "Show options to enable 'Do not disturb' mode": "Показать опции для включения режима \"Не беспокоить\"", + "Your feedback will help make spaces better. The more detail you can go into, the better.": "Ваши отзывы помогут сделать пространства лучше. Чем больше деталей вы сможете описать, тем лучше.", + "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Бета-версия доступна для веб-версии, настольных компьютеров и Android. Некоторые функции могут быть недоступны на вашем домашнем сервере.", + "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "Вы можете выйти из бета-версии в любое время из настроек или нажав на значок бета-версии, как показано выше.", + "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s будет перезагружен с включёнными пространствами. Сообщества и пользовательские теги будут скрыты.", + "Beta available for web, desktop and Android. Thank you for trying the beta.": "Бета-версия доступна для веб-версии, настольных компьютеров и Android. Спасибо, что попробовали бета-версию.", + "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "Если вы уйдёте, %(brand)s перезагрузится с отключёнными пространствами. Сообщества и пользовательские теги снова станут видимыми.", + "Spaces are a new way to group rooms and people.": "Пространства - это новый способ группировки комнат и людей.", + "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Прототип \"Сообщить модераторам\". В комнатах, поддерживающих модерацию, кнопка `сообщить` позволит вам сообщать о злоупотреблениях модераторам комнаты", + "This makes it easy for rooms to stay private to a space, while letting people in the space find and join them. All new rooms in a space will have this option available.": "Это позволяет комнатам оставаться приватными для пространства, в то же время позволяя людям в пространстве находить их и присоединяться к ним. Все новые комнаты в пространстве будут иметь эту опцию.", + "To help space members find and join a private room, go to that room's Security & Privacy settings.": "Чтобы помочь участникам пространства найти и присоединиться к приватной комнате, перейдите в настройки безопасности и конфиденциальности этой комнаты.", + "Help space members find private rooms": "Помогите участникам пространства найти приватные комнаты", + "Help people in spaces to find and join private rooms": "Помогите людям в пространствах находить приватные комнаты и присоединяться к ним", + "New in the Spaces beta": "Новое в бета-версии пространств", + "Silence call": "Тихий вызов", + "Sound on": "Звук включен", + "Review to ensure your account is safe": "Проверьте, чтобы убедиться, что ваша учетная запись в безопасности", + "User %(userId)s is already invited to the room": "Пользователь %(userId)s уже приглашен(а) в комнату", + "See when people join, leave, or are invited to your active room": "Просмотрите, когда люди присоединяются, уходят или приглашают в вашу активную комнату", + "Kick, ban, or invite people to your active room, and make you leave": "Выгонять, блокировать или приглашать людей в вашу активную комнату и заставлять покинуть ее", + "See when people join, leave, or are invited to this room": "Посмотрите, когда люди присоединяются, покидают или приглашают в эту комнату", + "Kick, ban, or invite people to this room, and make you leave": "Выгнать, заблокировать или пригласить людей в эту комнату и заставить покинуть ее", + "%(senderName)s changed the pinned messages for the room.": "%(senderName)s изменил(а) прикреплённые сообщения для комнаты.", + "%(senderName)s kicked %(targetName)s": "%(senderName)s выгнал(а) %(targetName)s", + "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s выгнал(а) %(targetName)s: %(reason)s", + "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s отозвал(а) приглашение %(targetName)s", + "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s отозвал(а) приглашение %(targetName)s: %(reason)s", + "%(senderName)s unbanned %(targetName)s": "%(senderName)s разблокировал(а) %(targetName)s", + "%(targetName)s left the room": "%(targetName)s покинул(а) комнату", + "%(targetName)s left the room: %(reason)s": "%(targetName)s покинул(а) комнату: %(reason)s", + "%(targetName)s rejected the invitation": "%(targetName)s отклонил(а) приглашение", + "%(targetName)s joined the room": "%(targetName)s присоединился(-ась) к комнате", + "%(senderName)s made no change": "%(senderName)s не сделал(а) изменений", + "%(senderName)s set a profile picture": "%(senderName)s установил(а) фотографию профиля", + "%(senderName)s changed their profile picture": "%(senderName)s изменил(а) фотографию своего профиля", + "%(senderName)s removed their profile picture": "%(senderName)s удалил(а) фотографию своего профиля", + "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s удалил(а) своё отображаемое имя (%(oldDisplayName)s)", + "%(senderName)s set their display name to %(displayName)s": "%(senderName)s установил(а) своё отображаемое имя на %(displayName)s", + "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s изменил(а) своё отображаемое имя на %(displayName)s", + "%(senderName)s banned %(targetName)s": "%(senderName)s заблокировал(а) %(targetName)s", + "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s заблокировал(а) %(targetName)s: %(reason)s", + "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s принял(а) приглашение для %(displayName)s", + "%(targetName)s accepted an invitation": "%(targetName)s принял(а) приглашение", + "Some invites couldn't be sent": "Некоторые приглашения не могут быть отправлены", + "We sent the others, but the below people couldn't be invited to ": "Мы отправили остальных, но нижеперечисленные люди не могут быть приглашены в ", + "Transfer Failed": "Перевод не удался", + "Unable to transfer call": "Не удалось перевести звонок", + "Olm version:": "Версия Olm:", + "%(oneUser)schanged the pinned messages for the room %(count)s times.|other": "%(oneUser)s изменил(а) прикреплённые сообщения в комнате %(count)s раз.", + "%(severalUsers)schanged the pinned messages for the room %(count)s times.|other": "%(severalUsers)s изменили прикреплённые сообщения в комнате %(count)s раз." } diff --git a/src/i18n/strings/si.json b/src/i18n/strings/si.json index 0fc3f38ca7..5b9031fc05 100644 --- a/src/i18n/strings/si.json +++ b/src/i18n/strings/si.json @@ -4,7 +4,7 @@ "Use Single Sign On to continue": "ඉදිරියට යාමට තනි පුරනය වීම භාවිතා කරන්න", "Confirm adding this email address by using Single Sign On to prove your identity.": "ඔබගේ අනන්‍යතාවය සනාථ කිරීම සඳහා තනි පුරනය භාවිතා කිරීමෙන් මෙම විද්‍යුත් තැපැල් ලිපිනය එක් කිරීම තහවුරු කරන්න.", "Confirm": "තහවුරු කරන්න", - "Add Email Address": "විද්‍යුත් තැපැල් ලිපිනය එක් කරන්න", + "Add Email Address": "වි-තැපැල් ලිපිනය එකතු කරන්න", "Sign In": "පිවිසෙන්න", "Dismiss": "ඉවතලන්න", "Explore rooms": "කාමර බලන්න", diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index 45f76a4a61..97fdc998dc 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -3568,5 +3568,70 @@ "To help space members find and join a private room, go to that room's Security & Privacy settings.": "Që të ndihmoni anëtarë hapësirash të gjejnë dhe hyjnë në një dhomë private, kaloni te rregullimet e Sigurisë & Privatësisë së dhomës.", "Help space members find private rooms": "Ndihmoni anëtarë hapësirash të gjejnë dhoma private", "Help people in spaces to find and join private rooms": "Ndihmoni persona në hapësira të gjejnë dhe hyjnë në dhoma private", - "New in the Spaces beta": "E re në Hapësira beta" + "New in the Spaces beta": "E re në Hapësira beta", + "You're removing all spaces. Access will default to invite only": "Po hiqni krejt hapësirat. Hyrja do të kthehet te parazgjedhja, pra vetëm me ftesa", + "Anyone will be able to find and join this room.": "Gjithkush do të jetë në gjendje të gjejë dhe hyjë në këtë dhomë.", + "Share content": "Ndani lëndë", + "Application window": "Dritare aplikacioni", + "Share entire screen": "Nda krejt ekranin", + "They didn't pick up": "S’iu përgjigjën", + "Call again": "Thirre prapë", + "They declined this call": "E hodhën poshtë këtë thirrje", + "You declined this call": "E hodhët poshtë këtë thirrje", + "You can now share your screen by pressing the \"screen share\" button during a call. You can even do this in audio calls if both sides support it!": "Tani mund t’u tregoni të tjerëve ekranin tuaj duke shtypur butonin “ndarje ekrani” gjatë thirrjes. Këtë mund ta bëni edhe në thirrje audio, nëse mbulohet nga të dy palët!", + "Screen sharing is here!": "Ndarja e ekranit me të tjerë erdhi!", + "Your camera is still enabled": "Kamera juaj është ende e aktivizuar", + "Your camera is turned off": "Kamera juaj është e fikur", + "%(sharerName)s is presenting": "%(sharerName)s përfaqëson", + "You are presenting": "Përfaqësoni", + "We're working on this, but just want to let you know.": "Po merremi me këtë, thjesht donim t’jua u bënim të ditur.", + "Search for rooms or spaces": "Kërkoni për dhoma ose hapësira", + "Add space": "Shtoni hapësirë", + "Are you sure you want to leave ?": "Jeni i sigurt se doni të braktiset ?", + "Leave %(spaceName)s": "Braktise %(spaceName)s", + "You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.": "Jeni përgjegjësi i vetëm i disa dhomave apo hapësirave që dëshironi t’i braktisni. Braktisja e tyre do t’i lërë pa ndonjë përgjegjës.", + "You're the only admin of this space. Leaving it will mean no one has control over it.": "Jeni përgjegjësi i vetëm i kësaj hapësire. Braktisja e saj do të thotë se askush s’ka kontroll mbi të.", + "You won't be able to rejoin unless you are re-invited.": "S’do të jeni në gjendje të rihyni, para se të riftoheni.", + "Search %(spaceName)s": "Kërko te %(spaceName)s", + "Leave specific rooms and spaces": "Braktis dhoma dhe hapësira specifike", + "Don't leave any": "Mos braktis ndonjë", + "Leave all rooms and spaces": "Braktisi krejt dhomat dhe hapësirat", + "Want to add an existing space instead?": "Në vend të kësaj, mos dëshironi të shtoni një hapësirë ekzistuese?", + "Private space (invite only)": "Hapësirë private (vetëm me ftesa)", + "Space visibility": "Dukshmëri hapësire", + "Add a space to a space you manage.": "Shtoni një hapësirë te një hapësirë që administroni.", + "Only people invited will be able to find and join this space.": "Vetëm personat e ftuar do të jenë në gjendje të gjejnë dhe hyjnë në këtë hapësirë.", + "Anyone will be able to find and join this space, not just members of .": "Cilido do të jetë në gjendje ta gjejë dhe hyjë në këtë hapësirë, jo thjesht anëtarët e .", + "Anyone in will be able to find and join.": "Cilido te do të jetë në gjendje ta gjejë dhe hyjë.", + "Adding spaces has moved.": "Shtimi i hapësirave është lëvizur.", + "Search for rooms": "Kërkoni për dhoma", + "Search for spaces": "Kërkoni për hapësira", + "Create a new space": "Krijoni një hapësirë të re", + "Want to add a new space instead?": "Në vend të kësaj, doni të shtoni një hapësirë të re?", + "Add existing space": "Shtoni hapësirë ekzistuese", + "Decrypting": "Po shfshehtëzohet", + "Show all rooms": "Shfaq krejt dhomat", + "Give feedback.": "Jepni përshtypjet.", + "Thank you for trying Spaces. Your feedback will help inform the next versions.": "Faleminderit që provoni Hapësirat. Përshtypjet tuaja do të ndihmojnë përmirësimin e versioneve të ardhshëm.", + "Spaces feedback": "Përshtypje mbi hapësirat", + "Spaces are a new feature.": "Hapësirat janë një veçori e re.", + "All rooms you're in will appear in Home.": "Krejt dhomat ku gjendeni, do të shfaqen te Home.", + "Send pseudonymous analytics data": "Dërgo të dhëna analitike pseudonimike", + "%(oneUser)schanged the pinned messages for the room %(count)s times.|other": "%(oneUser)sndryshoi mesazhet e fiksuar për dhomën %(count)s herë.", + "%(severalUsers)schanged the pinned messages for the room %(count)s times.|other": "%(severalUsers)sndryshuan mesazhet e fiksuara për dhomën %(count)s herë.", + "Missed call": "Thirrje e humbur", + "Call declined": "Thirrja u hodh poshtë", + "Stop recording": "Ndale regjistrimin", + "Send voice message": "Dërgoni mesazh zanor", + "Olm version:": "Version Olm:", + "Mute the microphone": "Heshto mikrofonin", + "Unmute the microphone": "Hiq heshtimin e mikrofonit", + "More": "Më tepër", + "Show sidebar": "Shfaqe anështyllën", + "Hide sidebar": "Fshihe anështyllën", + "Start sharing your screen": "Nisni ndarjen e ekranit tuaj", + "Stop sharing your screen": "Reshtni dhënien e ekranit tuaj", + "Stop the camera": "Ndale kamerën", + "Start the camera": "Nise kamerën", + "Surround selected text when typing special characters": "Rrethoje tekstin e përzgjedhur, kur shtypen shenja speciale" } diff --git a/src/i18n/strings/uk.json b/src/i18n/strings/uk.json index 753f748850..c79df5a5ed 100644 --- a/src/i18n/strings/uk.json +++ b/src/i18n/strings/uk.json @@ -698,7 +698,7 @@ "Verify your other session using one of the options below.": "Перевірте інший сеанс за допомогою одного із варіантів знизу.", "%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s (%(userId)s) починає новий сеанс без його підтвердження:", "Ask this user to verify their session, or manually verify it below.": "Попросіть цього користувача підтвердити сеанс, або підтвердьте його власноруч унизу.", - "Not Trusted": "Недовірене", + "Not Trusted": "Не довірений", "Manually Verify by Text": "Ручна перевірка за допомогою тексту", "Interactively verify by Emoji": "Інтерактивно звірити за допомогою емодзі", "Done": "Зроблено", @@ -1641,5 +1641,10 @@ "Identity server (%(server)s)": "Сервер ідентифікації (%(server)s)", "Could not connect to identity server": "Не вдалося під'єднатись до сервера ідентифікації", "There was an error looking up the phone number": "Сталася помилка під час пошуку номеру телефону", - "Unable to look up phone number": "Неможливо знайти номер телефону" + "Unable to look up phone number": "Неможливо знайти номер телефону", + "Not trusted": "Не довірений", + "Trusted": "Довірений", + "This backup is trusted because it has been restored on this session": "Ця резервна копія є надійною, оскільки її було відновлено під час цього сеансу", + "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Індивідуально перевіряйте кожен сеанс, який використовується користувачем, щоб позначити його довіреним, не довіряючи пристроям перехресного підписування.", + "To be secure, do this in person or use a trusted way to communicate.": "Для забезпечення безпеки зробіть це особисто або скористайтесь надійним способом зв'язку." } diff --git a/src/i18n/strings/zh_Hans.json b/src/i18n/strings/zh_Hans.json index 2472ac479e..384a4c09ba 100644 --- a/src/i18n/strings/zh_Hans.json +++ b/src/i18n/strings/zh_Hans.json @@ -43,13 +43,13 @@ "Guests cannot join this room even if explicitly invited.": "即使有人主动邀请,游客也不能加入此聊天室。", "Hangup": "挂断", "Historical": "历史", - "Homeserver is": "主服务器是", + "Homeserver is": "主服务器地址", "Identity Server is": "身份认证服务器是", "I have verified my email address": "我已经验证了我的邮箱地址", "Import E2E room keys": "导入聊天室端到端加密密钥", "Incorrect verification code": "验证码错误", "Invalid Email Address": "邮箱地址格式错误", - "Invalid file%(extra)s": "无效文件%(extra)s", + "Invalid file%(extra)s": "无效文件 %(extra)s", "Return to login screen": "返回登录页面", "%(brand)s does not have permission to send you notifications - please check your browser settings": "%(brand)s 没有通知发送权限 - 请检查你的浏览器设置", "%(brand)s was not given permission to send notifications - please try again": "%(brand)s 没有通知发送权限 - 请重试", @@ -245,14 +245,14 @@ "Access Token:": "访问令牌:", "Cannot add any more widgets": "无法添加更多小挂件", "Delete widget": "删除挂件", - "Define the power level of a user": "定义一位用户的滥权等级", + "Define the power level of a user": "定义一位用户的特权等级", "Enable automatic language detection for syntax highlighting": "启用语法高亮的自动语言检测", - "Failed to change power level": "滥权等级修改失败", + "Failed to change power level": "特权等级修改失败", "Kick": "移除", "Kicks user with given id": "按照 ID 移除用户", "Last seen": "最近一次上线", "New passwords must match each other.": "新密码必须互相匹配。", - "Power level must be positive integer.": "滥权等级必须是正整数。", + "Power level must be positive integer.": "特权等级必须是正整数。", "Results from DuckDuckGo": "来自 DuckDuckGo 的结果", "%(roomName)s does not exist.": "%(roomName)s 不存在。", "Save": "保存", @@ -514,9 +514,9 @@ "You don't currently have any stickerpacks enabled": "你目前未启用任何贴纸包", "Key request sent.": "已发送密钥共享请求。", "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "如果你是房间中最后一位有权限的用户,在你降低自己的权限等级后将无法撤回此修改,因为你将无法重新获得权限。", - "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "你将无法撤回此修改,因为你正在将此用户的滥权等级提升至与你相同。", + "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "你将无法撤回此修改,因为此用户的等级将与你相同。", "Unmute": "取消静音", - "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s(滥权等级 %(powerLevelNumber)s)", + "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s(特权等级 %(powerLevelNumber)s)", "Hide Stickers": "隐藏贴图", "Show Stickers": "显示贴图", "%(duration)ss": "%(duration)s 秒", @@ -785,7 +785,7 @@ "Share room": "分享聊天室", "System Alerts": "系统警告", "Muted Users": "被禁言的用户", - "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "在启用加密的聊天室中,比如此聊天室,链接预览被默认禁用,以确保主服务器(访问链接、生成预览的地方)无法获知聊天室中的链接及其信息。", + "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "在启用加密的聊天室中,比如这个,默认禁用链接预览,以确保主服务器(访问链接、生成预览的地方)无法获知聊天室中的链接及其信息。", "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "当有人发送一条带有链接的消息后,可显示链接的预览,链接预览可包含此链接的网页标题、描述以及图片。", "The email field must not be blank.": "必须输入电子邮箱。", "The phone number field must not be blank.": "必须输入电话号码。", @@ -837,7 +837,7 @@ "Forces the current outbound group session in an encrypted room to be discarded": "强制丢弃加密聊天室中的当前出站群组会话", "Unable to connect to Homeserver. Retrying...": "无法连接至主服务器。正在重试…", "Sorry, your homeserver is too old to participate in this room.": "抱歉,因你的主服务器的程序版本过旧,无法加入此聊天室。", - "Mirror local video feed": "镜像翻转本地视频源", + "Mirror local video feed": "镜像翻转画面", "This room has been replaced and is no longer active.": "此聊天室已被取代,且不再活跃。", "The conversation continues here.": "对话在这里继续。", "Only room administrators will see this warning": "此警告仅聊天室管理员可见", @@ -1695,9 +1695,9 @@ "To report a Matrix-related security issue, please read the Matrix.org Security Disclosure Policy.": "要报告 Matrix 相关的安全问题,请阅读 Matrix.org 的安全公开策略。", "Something went wrong. Please try again or view your console for hints.": "出现问题。请重试或查看你的终端以获得提示。", "Please try again or view your console for hints.": "请重试或查看你的终端以获得提示。", - "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "你的服务器管理员未在私人聊天室和私聊中默认启用端对端加密。", + "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "你的服务器管理员默认关闭了私人聊天室和私聊中的端对端加密。", "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "在下方管理会话名称,登出你的会话或在你的用户资料中验证它们。", - "This room is bridging messages to the following platforms. Learn more.": "此聊天室正将消息桥接到以下平台。了解更多。", + "This room is bridging messages to the following platforms. Learn more.": "此聊天室的消息将桥接到其他平台,了解更多。", "This room isn’t bridging messages to any platforms. Learn more.": "此聊天室未将消息桥接到任何平台。了解更多。", "Bridges": "桥接", "Someone is using an unknown session": "有人在使用未知会话", @@ -2330,7 +2330,7 @@ " to store messages from ": " 存储来自 ", "rooms.": "聊天室的消息。", "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "此会话未备份你的密钥,但如果你已有现存备份,你可以继续并从中恢复和向其添加。", - "Invalid theme schema.": "无效主题方案。", + "Invalid theme schema.": "主题方案无效。", "Read Marker lifetime (ms)": "已读标记生存期 (ms)", "Read Marker off-screen lifetime (ms)": "已读标记屏幕外生存期 (ms)", "Unable to revoke sharing for email address": "无法撤消电子邮件地址共享", @@ -3071,7 +3071,7 @@ "Your message wasn't sent because this homeserver has been blocked by it's administrator. Please contact your service administrator to continue using the service.": "你的消息未被发送,因为此主服务器已被其管理员封禁。请联络你的服务管理员已继续使用服务。", "Filter all spaces": "过滤所有空间", "You have no visible notifications.": "你没有可见的通知。", - "Communities are changing to Spaces": "社群正在转变为空间", + "Communities are changing to Spaces": "社区正在向空间转变", "%(creator)s created this DM.": "%(creator)s 创建了此私聊。", "Verification requested": "已请求验证", "Security Key mismatch": "安全密钥不符", @@ -3307,7 +3307,7 @@ "Report": "报告", "Collapse reply thread": "折叠回复链", "Show preview": "显示预览", - "View source": "查看来源", + "View source": "查看源代码", "Forward": "转发", "Settings - %(spaceName)s": "设置 - %(spaceName)s", "Report the entire room": "报告整个聊天室", @@ -3385,15 +3385,17 @@ "Some invites couldn't be sent": "部分邀请无法送达", "We sent the others, but the below people couldn't be invited to ": "我们已向其他人发送邀请,除了以下无法邀请至 的人", "Integration manager": "集成管理器", - "Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.": "你的 %(brand)s 不允许你使用集成管理器来完成此操作。请联系管理员。", + "Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.": "你的 %(brand)s 不允许你使用集成管理器来完成此操作,请联系管理员。", "Using this widget may share data with %(widgetDomain)s & your integration manager.": "使用此挂件可能会和 %(widgetDomain)s 及您的集成管理器共享数据 。", - "Identity server is": "身份认证服务器是", + "Identity server is": "身份服务器地址", "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "集成管理器接收配置数据,并可以以你的名义修改挂件、发送聊天室邀请及设置权限级别。", - "Use an integration manager to manage bots, widgets, and sticker packs.": "使用集成管理器以管理机器人、挂件和贴纸包。", - "Use an integration manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "使用集成管理器 (%(serverName)s) 以管理机器人、挂件和贴纸包。", + "Use an integration manager to manage bots, widgets, and sticker packs.": "使用集成管理器管理机器人、挂件和贴纸包。", + "Use an integration manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "使用集成管理器 (%(serverName)s) 管理机器人、挂件和贴纸包。", "Identity server": "身份服务器", "Identity server (%(server)s)": "身份服务器(%(server)s)", "Could not connect to identity server": "无法连接到身份服务器", - "Not a valid identity server (status code %(code)s)": "不是有效的身份服务器(状态码 %(code)s)", - "Identity server URL must be HTTPS": "身份服务器连接必须是 HTTPS" + "Not a valid identity server (status code %(code)s)": "身份服务器无效(状态码 %(code)s)", + "Identity server URL must be HTTPS": "必须以 HTTPS 协议连接身份服务器", + "Send pseudonymous analytics data": "发送匿名统计数据", + "User %(userId)s is already invited to the room": "%(userId)s 已经被邀请过" } diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 74234746f6..837e371070 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -3583,5 +3583,70 @@ "To help space members find and join a private room, go to that room's Security & Privacy settings.": "要協助空間成員尋找並加入私人聊天室,請到該聊天室的「安全與隱私」設定。", "Help space members find private rooms": "協助空間成員尋找私人聊天室", "Help people in spaces to find and join private rooms": "協助空間中的夥伴尋找並加入私人聊天室", - "New in the Spaces beta": "Spaces 測試版的新功能" + "New in the Spaces beta": "Spaces 測試版的新功能", + "Share content": "分享內容", + "Application window": "應用程式視窗", + "Share entire screen": "分享整個螢幕", + "They didn't pick up": "他們未接聽", + "Call again": "重撥", + "They declined this call": "他們回絕了此通話", + "You declined this call": "您回絕了此通話", + "You can now share your screen by pressing the \"screen share\" button during a call. You can even do this in audio calls if both sides support it!": "您現在可以透過在通話中按下「畫面分享」按鈕來分享您的畫面了。如果雙方都支援,您甚至可以在音訊通話中使用此功能!", + "Screen sharing is here!": "畫面分享在此!", + "Your camera is still enabled": "您的攝影機仍為啟用狀態", + "Your camera is turned off": "您的攝影機已關閉", + "You are presenting": "您正在出席", + "%(sharerName)s is presenting": "%(sharerName)s 正在出席", + "Anyone will be able to find and join this room.": "任何人都可以找到並加入此聊天室。", + "We're working on this, but just want to let you know.": "我們正在為此努力,但只是想讓您知道。", + "Search for rooms or spaces": "搜尋聊天室或空間", + "Want to add an existing space instead?": "想要新增既有空間嗎?", + "Private space (invite only)": "私人空間(僅邀請)", + "Space visibility": "空間能見度", + "Add a space to a space you manage.": "新增空間到您管理的空間。", + "Only people invited will be able to find and join this space.": "僅有被邀請的人才能找到並加入此空間。", + "Anyone will be able to find and join this space, not just members of .": "不只是 的成員,任何人都可以找到並加入此空間。", + "Anyone in will be able to find and join.": " 中的任何人都可以找到並加入。", + "Adding spaces has moved.": "新增空間已移動。", + "Search for rooms": "搜尋聊天室", + "Search for spaces": "搜尋空間", + "Create a new space": "建立新空間", + "Want to add a new space instead?": "想要新增空間?", + "Add existing space": "新增既有的空間", + "Add space": "新增空間", + "Give feedback.": "給予回饋。", + "Thank you for trying Spaces. Your feedback will help inform the next versions.": "感謝您試用空間。您的回饋有助於在隨後的版本中改善此功能。", + "Spaces feedback": "空間回饋", + "Spaces are a new feature.": "空間為新功能。", + "Are you sure you want to leave ?": "您確定您想要離開 ?", + "Leave %(spaceName)s": "離開 %(spaceName)s", + "You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.": "您是某些要離開的聊天室與空間的唯一管理員。離開會讓它們沒有任何管理員。", + "You're the only admin of this space. Leaving it will mean no one has control over it.": "您是此空間唯一的管理員。離開將代表沒有人可以控制它。", + "You won't be able to rejoin unless you are re-invited.": "您將無法重新加入,除非您再次被邀請。", + "Search %(spaceName)s": "搜尋 %(spaceName)s", + "Leave specific rooms and spaces": "離開特定的聊天室與空間", + "Don't leave any": "不要離開任何", + "Leave all rooms and spaces": "離開所有聊天室與空間", + "Decrypting": "正在解密", + "Show all rooms": "顯示所有聊天室", + "All rooms you're in will appear in Home.": "您所在的所有聊天室都會出現在「首頁」。", + "Send pseudonymous analytics data": "傳送匿名分析資料", + "Missed call": "未接來電", + "Call declined": "拒絕通話", + "Stop recording": "停止錄製", + "Send voice message": "傳送語音訊息", + "Mute the microphone": "麥克風靜音", + "Unmute the microphone": "取消麥克風靜音", + "Dialpad": "撥號鍵盤", + "More": "更多", + "Show sidebar": "顯示側邊欄", + "Hide sidebar": "隱藏側邊欄", + "Start sharing your screen": "開始分享您的畫面", + "Stop sharing your screen": "停止分享您的畫面", + "Stop the camera": "停止攝影機", + "Start the camera": "開啟攝影機", + "Surround selected text when typing special characters": "輸入特殊字元以環繞選取的文字", + "%(oneUser)schanged the pinned messages for the room %(count)s times.|other": "%(oneUser)s 變更了聊天室的釘選訊息 %(count)s 次。", + "%(severalUsers)schanged the pinned messages for the room %(count)s times.|other": "%(severalUsers)s 變更了聊天室的釘選訊息 %(count)s 次。", + "Olm version:": "Olm 版本:" } diff --git a/src/settings/controllers/NotificationControllers.ts b/src/settings/controllers/NotificationControllers.ts index cc5c040a89..09e4e1dd1a 100644 --- a/src/settings/controllers/NotificationControllers.ts +++ b/src/settings/controllers/NotificationControllers.ts @@ -21,6 +21,7 @@ import { SettingLevel } from "../SettingLevel"; // XXX: This feels wrong. import { PushProcessor } from "matrix-js-sdk/src/pushprocessor"; +import { PushRuleActionName } from "matrix-js-sdk/src/@types/PushRules"; // .m.rule.master being enabled means all events match that push rule // default action on this rule is dont_notify, but it could be something else @@ -35,7 +36,7 @@ export function isPushNotifyDisabled(): boolean { } // If the rule is enabled then check it does not notify on everything - return masterRule.enabled && !masterRule.actions.includes("notify"); + return masterRule.enabled && !masterRule.actions.includes(PushRuleActionName.Notify); } function getNotifier(): any { // TODO: [TS] Formal type that doesn't cause a cyclical reference. diff --git a/src/stores/GroupFilterOrderStore.js b/src/stores/GroupFilterOrderStore.js index e81d1b81f7..821fbefc4f 100644 --- a/src/stores/GroupFilterOrderStore.js +++ b/src/stores/GroupFilterOrderStore.js @@ -14,12 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ import { Store } from 'flux/utils'; +import { EventType } from "matrix-js-sdk/src/@types/event"; import dis from '../dispatcher/dispatcher'; import GroupStore from './GroupStore'; import Analytics from '../Analytics'; import * as RoomNotifs from "../RoomNotifs"; import { MatrixClientPeg } from '../MatrixClientPeg'; import SettingsStore from "../settings/SettingsStore"; +import { CreateEventField } from "../components/views/dialogs/CreateSpaceFromCommunityDialog"; const INITIAL_STATE = { orderedTags: null, @@ -235,8 +237,12 @@ class GroupFilterOrderStore extends Store { (t) => (t[0] !== '+' || groupIds.includes(t)) && !removedTags.has(t), ); + const cli = MatrixClientPeg.get(); + const migratedCommunities = new Set(cli.getRooms().map(r => { + return r.currentState.getStateEvents(EventType.RoomCreate, "")?.getContent()[CreateEventField]; + }).filter(Boolean)); const groupIdsToAdd = groupIds.filter( - (groupId) => !tags.includes(groupId) && !removedTags.has(groupId), + (groupId) => !tags.includes(groupId) && !removedTags.has(groupId) && !migratedCommunities.has(groupId), ); return tagsToKeep.concat(groupIdsToAdd); diff --git a/src/stores/GroupStore.js b/src/stores/GroupStore.js index f1122cb945..63972b31fb 100644 --- a/src/stores/GroupStore.js +++ b/src/stores/GroupStore.js @@ -20,11 +20,11 @@ import FlairStore from './FlairStore'; import { MatrixClientPeg } from '../MatrixClientPeg'; import dis from '../dispatcher/dispatcher'; -function parseMembersResponse(response) { +export function parseMembersResponse(response) { return response.chunk.map((apiMember) => groupMemberFromApiObject(apiMember)); } -function parseRoomsResponse(response) { +export function parseRoomsResponse(response) { return response.chunk.map((apiRoom) => groupRoomFromApiObject(apiRoom)); } diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index 732428107f..e9820eee06 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -137,6 +137,20 @@ export default class WidgetStore extends AsyncStoreWithClient { if (edited && !this.roomMap.has(room.roomId)) { this.roomMap.set(room.roomId, roomInfo); } + + // If a persistent widget is active, check to see if it's just been removed. + // If it has, it needs to destroyed otherwise unmounting the node won't kill it + const persistentWidgetId = ActiveWidgetStore.getPersistentWidgetId(); + if (persistentWidgetId) { + if ( + ActiveWidgetStore.getRoomId(persistentWidgetId) === room.roomId && + !roomInfo.widgets.some(w => w.id === persistentWidgetId) + ) { + console.log(`Persistent widget ${persistentWidgetId} removed from room ${room.roomId}: destroying.`); + ActiveWidgetStore.destroyPersistentWidget(persistentWidgetId); + } + } + this.emit(room.roomId); } diff --git a/src/utils/DecryptFile.ts b/src/utils/DecryptFile.ts index e66db4ffb2..891439ffe1 100644 --- a/src/utils/DecryptFile.ts +++ b/src/utils/DecryptFile.ts @@ -17,18 +17,22 @@ limitations under the License. // Pull in the encryption lib so that we can decrypt attachments. import encrypt from 'browser-encrypt-attachment'; import { mediaFromContent } from "../customisations/Media"; -import { IEncryptedFile } from "../customisations/models/IMediaEventContent"; +import { IEncryptedFile, IMediaEventInfo } from "../customisations/models/IMediaEventContent"; import { getBlobSafeMimeType } from "./blobs"; /** * Decrypt a file attached to a matrix event. - * @param {IEncryptedFile} file The json taken from the matrix event. + * @param {IEncryptedFile} file The encrypted file information taken from the matrix event. * This passed to [link]{@link https://github.com/matrix-org/browser-encrypt-attachments} * as the encryption info object, so will also have the those keys in addition to * the keys below. + * @param {IMediaEventInfo} info The info parameter taken from the matrix event. * @returns {Promise} Resolves to a Blob of the file. */ -export function decryptFile(file: IEncryptedFile): Promise { +export function decryptFile( + file: IEncryptedFile, + info?: IMediaEventInfo, +): Promise { const media = mediaFromContent({ file }); // Download the encrypted file as an array buffer. return media.downloadSource().then((response) => { @@ -44,7 +48,7 @@ export function decryptFile(file: IEncryptedFile): Promise { // they introduce XSS attacks if the Blob URI is viewed directly in the // browser (e.g. by copying the URI into a new tab or window.) // See warning at top of file. - let mimetype = file.mimetype ? file.mimetype.split(";")[0].trim() : ''; + let mimetype = info?.mimetype ? info.mimetype.split(";")[0].trim() : ''; mimetype = getBlobSafeMimeType(mimetype); return new Blob([dataArray], { type: mimetype }); diff --git a/src/utils/EventUtils.ts b/src/utils/EventUtils.ts index e2af1c7464..7aef05c523 100644 --- a/src/utils/EventUtils.ts +++ b/src/utils/EventUtils.ts @@ -116,14 +116,14 @@ export function getEventDisplayInfo(mxEvent: MatrixEvent): { (eventType === EventType.RoomMessage && msgtype && msgtype.startsWith("m.key.verification")) || (eventType === EventType.RoomCreate) || (eventType === EventType.RoomEncryption) || - (eventType === EventType.CallInvite) || (tileHandler === "messages.MJitsiWidgetEvent") ); let isInfoMessage = ( !isBubbleMessage && eventType !== EventType.RoomMessage && eventType !== EventType.Sticker && - eventType !== EventType.RoomCreate + eventType !== EventType.RoomCreate && + eventType !== EventType.CallInvite ); // If we're showing hidden events in the timeline, we should use the diff --git a/src/utils/MediaEventHelper.ts b/src/utils/MediaEventHelper.ts index 8b8edcc62a..f68da309bc 100644 --- a/src/utils/MediaEventHelper.ts +++ b/src/utils/MediaEventHelper.ts @@ -76,7 +76,8 @@ export class MediaEventHelper implements IDestroyable { private fetchSource = () => { if (this.media.isEncrypted) { - return decryptFile(this.event.getContent().file); + const content = this.event.getContent(); + return decryptFile(content.file, content.info); } return this.media.downloadSource().then(r => r.blob()); }; @@ -87,7 +88,7 @@ export class MediaEventHelper implements IDestroyable { if (this.media.isEncrypted) { const content = this.event.getContent(); if (content.info?.thumbnail_file) { - return decryptFile(content.info.thumbnail_file); + return decryptFile(content.info.thumbnail_file, content.info.thumbnail_info); } else { // "Should never happen" console.warn("Media claims to have thumbnail and is encrypted, but no thumbnail_file found"); diff --git a/src/utils/space.tsx b/src/utils/space.tsx index fecb581e65..c1d8dbfbea 100644 --- a/src/utils/space.tsx +++ b/src/utils/space.tsx @@ -17,6 +17,7 @@ limitations under the License. import React from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import { EventType } from "matrix-js-sdk/src/@types/event"; +import { MatrixClient } from "matrix-js-sdk/src/client"; import { calculateRoomVia } from "./permalinks/Permalinks"; import Modal from "../Modal"; @@ -37,6 +38,7 @@ import { leaveRoomBehaviour } from "./membership"; import Spinner from "../components/views/elements/Spinner"; import dis from "../dispatcher/dispatcher"; import LeaveSpaceDialog from "../components/views/dialogs/LeaveSpaceDialog"; +import CreateSpaceFromCommunityDialog from "../components/views/dialogs/CreateSpaceFromCommunityDialog"; export const shouldShowSpaceSettings = (space: Room) => { const userId = space.client.getUserId(); @@ -173,3 +175,10 @@ export const leaveSpace = (space: Room) => { }, }, "mx_LeaveSpaceDialog_wrapper"); }; + +export const createSpaceFromCommunity = (cli: MatrixClient, groupId: string): Promise<[string?]> => { + return Modal.createTrackedDialog('Create Space', 'from community', CreateSpaceFromCommunityDialog, { + matrixClient: cli, + groupId, + }, "mx_CreateSpaceFromCommunityDialog_wrapper").finished as Promise<[string?]>; +}; diff --git a/yarn.lock b/yarn.lock index a780d1ffa0..5410819c06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,28 @@ # yarn lockfile v1 +"@actions/core@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.4.0.tgz#cf2e6ee317e314b03886adfeb20e448d50d6e524" + integrity sha512-CGx2ilGq5i7zSLgiiGUtBCxhRRxibJYU6Fim0Q1Wg2aQL2LTnF27zbqZOrxfvFQ55eSBW0L8uVStgtKMpa0Qlg== + +"@actions/github@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@actions/github/-/github-5.0.0.tgz#1754127976c50bd88b2e905f10d204d76d1472f8" + integrity sha512-QvE9eAAfEsS+yOOk0cylLBIO/d6WyWIOvsxxzdrPFaud39G6BOkUwScXZn1iBzQzHyu9SBkkLSWlohDWdsasAQ== + dependencies: + "@actions/http-client" "^1.0.11" + "@octokit/core" "^3.4.0" + "@octokit/plugin-paginate-rest" "^2.13.3" + "@octokit/plugin-rest-endpoint-methods" "^5.1.1" + +"@actions/http-client@^1.0.11": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@actions/http-client/-/http-client-1.0.11.tgz#c58b12e9aa8b159ee39e7dd6cbd0e91d905633c0" + integrity sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg== + dependencies: + tunnel "0.0.6" + "@babel/cli@^7.12.10": version "7.14.8" resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.14.8.tgz#fac73c0e2328a8af9fd3560c06b096bfa3730933" @@ -1331,7 +1353,7 @@ dependencies: "@octokit/types" "^6.0.3" -"@octokit/core@^3.5.0": +"@octokit/core@^3.4.0", "@octokit/core@^3.5.0": version "3.5.1" resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.5.1.tgz#8601ceeb1ec0e1b1b8217b960a413ed8e947809b" integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw== @@ -1367,6 +1389,18 @@ resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-9.3.0.tgz#160347858d727527901c6aae7f7d5c2414cc1f2e" integrity sha512-oz60hhL+mDsiOWhEwrj5aWXTOMVtQgcvP+sRzX4C3cH7WOK9QSAoEtjWh0HdOf6V3qpdgAmUMxnQPluzDWR7Fw== +"@octokit/openapi-types@^9.5.0": + version "9.7.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-9.7.0.tgz#9897cdefd629cd88af67b8dbe2e5fb19c63426b2" + integrity sha512-TUJ16DJU8mekne6+KVcMV5g6g/rJlrnIKn7aALG9QrNpnEipFc1xjoarh0PKaAWf2Hf+HwthRKYt+9mCm5RsRg== + +"@octokit/plugin-paginate-rest@^2.13.3": + version "2.15.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.15.1.tgz#264189dd3ce881c6c33758824aac05a4002e056a" + integrity sha512-47r52KkhQDkmvUKZqXzA1lKvcyJEfYh3TKAIe5+EzMeyDM3d+/s5v11i2gTk8/n6No6DPi3k5Ind6wtDbo/AEg== + dependencies: + "@octokit/types" "^6.24.0" + "@octokit/plugin-paginate-rest@^2.6.2": version "2.15.0" resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.15.0.tgz#9c956c3710b2bd786eb3814eaf5a2b17392c150d" @@ -1387,6 +1421,14 @@ "@octokit/types" "^6.23.0" deprecation "^2.3.1" +"@octokit/plugin-rest-endpoint-methods@^5.1.1": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.8.0.tgz#33b342fe41f2603fdf8b958e6652103bb3ea3f3b" + integrity sha512-qeLZZLotNkoq+it6F+xahydkkbnvSK0iDjlXFo3jNTB+Ss0qIbYQb9V/soKLMkgGw8Q2sHjY5YEXiA47IVPp4A== + dependencies: + "@octokit/types" "^6.25.0" + deprecation "^2.3.1" + "@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" @@ -1425,6 +1467,13 @@ dependencies: "@octokit/openapi-types" "^9.3.0" +"@octokit/types@^6.24.0", "@octokit/types@^6.25.0": + version "6.25.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.25.0.tgz#c8e37e69dbe7ce55ed98ee63f75054e7e808bf1a" + integrity sha512-bNvyQKfngvAd/08COlYIN54nRgxskmejgywodizQNyiKoXmWRAjKup2/LYwm+T9V0gsKH6tuld1gM0PzmOiB4Q== + dependencies: + "@octokit/openapi-types" "^9.5.0" + "@peculiar/asn1-schema@^2.0.27", "@peculiar/asn1-schema@^2.0.32": version "2.0.37" resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.0.37.tgz#700476512ab903d809f64a3040fb1b2fe6fb6d4b" @@ -1956,10 +2005,13 @@ ajv@^8.0.1: require-from-string "^2.0.2" uri-js "^4.2.2" -"allchange@github:matrix-org/allchange": - version "0.0.1" - resolved "https://codeload.github.com/matrix-org/allchange/tar.gz/56b37b06339a3ac3fe771f3ec3d0bff798df8dab" +allchange@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/allchange/-/allchange-1.0.0.tgz#f5177b7d97f8e97a2d059a1524db9a72d94dc6d2" + integrity sha512-O0VIaMIORxOaReyYEijDfKdpudJhbzzVYLdJR1aROyUgOLBEp9e5V/TDXQpjX23W90IFCSRZxsDb3exLRD05HA== dependencies: + "@actions/core" "^1.4.0" + "@actions/github" "^5.0.0" "@octokit/rest" "^18.6.7" cli-color "^2.0.0" js-yaml "^4.1.0" @@ -5686,10 +5738,9 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@12.2.0: - version "12.2.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-12.2.0.tgz#e1dc7ddac054289cb24ee3d11dba8a5ba5ddecf5" - integrity sha512-foSs3uKRc6uvFNhgY35eErBvLWVDd5RNIxxsdFKlmU3B+70YUf3BP3petyBNW34ORyOqNdX36IiApfLo3npNEw== +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": + version "12.3.1" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/3216d7e5a7a333212b00d4d7578e29a9f0e247d8" dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" @@ -7898,6 +7949,11 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" +tunnel@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"