Compare commits
81 Commits
hs/remove-
...
robin/call
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d7a9ee847 | ||
|
|
5df083f009 | ||
|
|
991ce70209 | ||
|
|
60de81b824 | ||
|
|
20d8abf7c2 | ||
|
|
fda658182a | ||
|
|
9bfea92b66 | ||
|
|
962136d453 | ||
|
|
917d53a56f | ||
|
|
e44ca88a7e | ||
|
|
cb7d77de45 | ||
|
|
cd6737942f | ||
|
|
a058d85c21 | ||
|
|
bf6ae73d39 | ||
|
|
273cdf41e9 | ||
|
|
3ab3041c45 | ||
|
|
2052080d7d | ||
|
|
cc95d154fb | ||
|
|
4e696d2dc6 | ||
|
|
610b14adc1 | ||
|
|
324dd5a858 | ||
|
|
bc4bc6c25e | ||
|
|
3f3fba99eb | ||
|
|
26a17f9314 | ||
|
|
fd91e78152 | ||
|
|
af476905b6 | ||
|
|
da87bbe854 | ||
|
|
47976447b5 | ||
|
|
53065f9437 | ||
|
|
179b368809 | ||
|
|
82957507d0 | ||
|
|
27c1b38dab | ||
|
|
7ff1fd259d | ||
|
|
8d891cde53 | ||
|
|
90cc44b340 | ||
|
|
b6c872142b | ||
|
|
3762d40620 | ||
|
|
42192cbe06 | ||
|
|
aa996010b4 | ||
|
|
e9a3625bd6 | ||
|
|
343dd06929 | ||
|
|
77b6c3b526 | ||
|
|
b721505211 | ||
|
|
56c7fc1948 | ||
|
|
9d8efacede | ||
|
|
1770b94ed3 | ||
|
|
dfdac8ef63 | ||
|
|
f1ebd85af1 | ||
|
|
4776a9971d | ||
|
|
20ac69f379 | ||
|
|
8c42b0bed8 | ||
|
|
fbc6f12408 | ||
|
|
b82c8554e3 | ||
|
|
3d705b1895 | ||
|
|
81c12db5ee | ||
|
|
e1d76e77a5 | ||
|
|
54e015706c | ||
|
|
cef25c2cab | ||
|
|
59c26fc3ad | ||
|
|
31af8b07dd | ||
|
|
c0d14daa17 | ||
|
|
ed35a7cba4 | ||
|
|
21e9d93e69 | ||
|
|
ffa8971195 | ||
|
|
072ee0cf36 | ||
|
|
4b02520453 | ||
|
|
bf48100d31 | ||
|
|
2da21248bb | ||
|
|
3c57323595 | ||
|
|
4c72f0c0b2 | ||
|
|
dfd08a8c01 | ||
|
|
7db909a47d | ||
|
|
1ad1387e05 | ||
|
|
c6b3bf962a | ||
|
|
e749b017c9 | ||
|
|
c00262f0c5 | ||
|
|
7a513a2dc2 | ||
|
|
808412c6be | ||
|
|
45497905be | ||
|
|
0997e0a747 | ||
|
|
6173c1224b |
19
.github/workflows/docker.yaml
vendored
@@ -25,14 +25,14 @@ jobs:
|
|||||||
fetch-depth: 0 # needed for docker-package to be able to calculate the version
|
fetch-depth: 0 # needed for docker-package to be able to calculate the version
|
||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
uses: sigstore/cosign-installer@c56c2d3e59e4281cc41dea2217323ba5694b171e # v3
|
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@4574d27a4764455b42196d70a065bc6853246a25 # v3
|
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3
|
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
||||||
with:
|
with:
|
||||||
install: true
|
install: true
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and load
|
- name: Build and load
|
||||||
id: test-build
|
id: test-build
|
||||||
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6
|
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
load: true
|
load: true
|
||||||
@@ -61,6 +61,7 @@ jobs:
|
|||||||
- name: Test the image
|
- name: Test the image
|
||||||
env:
|
env:
|
||||||
IMAGEID: ${{ steps.test-build.outputs.imageid }}
|
IMAGEID: ${{ steps.test-build.outputs.imageid }}
|
||||||
|
timeout-minutes: 2
|
||||||
run: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ jobs:
|
|||||||
--rm \
|
--rm \
|
||||||
-e "ELEMENT_WEB_PORT=$ELEMENT_WEB_PORT" \
|
-e "ELEMENT_WEB_PORT=$ELEMENT_WEB_PORT" \
|
||||||
-dp "$ELEMENT_WEB_PORT:$ELEMENT_WEB_PORT" \
|
-dp "$ELEMENT_WEB_PORT:$ELEMENT_WEB_PORT" \
|
||||||
-v $(pwd)/modules:/tmp/element-web-modules \
|
-v $(pwd)/modules:/modules \
|
||||||
"$IMAGEID" \
|
"$IMAGEID" \
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,14 +87,16 @@ jobs:
|
|||||||
test "$MODULE_0" = "/${MODULE_PATH}"
|
test "$MODULE_0" = "/${MODULE_PATH}"
|
||||||
|
|
||||||
# Check healthcheck
|
# Check healthcheck
|
||||||
test "$(docker inspect -f {{.State.Running}} $CONTAINER_ID)" == "true"
|
until test "$(docker inspect -f {{.State.Health.Status}} $CONTAINER_ID)" == "healthy"; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
docker stop "$CONTAINER_ID"
|
docker stop "$CONTAINER_ID"
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5
|
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
@@ -107,7 +110,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: build-and-push
|
id: build-and-push
|
||||||
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6
|
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
id: cpr
|
id: cpr
|
||||||
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
branch: actions/playwright-image-updates
|
branch: actions/playwright-image-updates
|
||||||
|
|||||||
2
.github/workflows/tests.yml
vendored
@@ -104,7 +104,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Skip SonarCloud in merge queue
|
- name: Skip SonarCloud in merge queue
|
||||||
if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true'
|
if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true'
|
||||||
uses: guibranco/github-status-action-v2@7ca807c2ba3401be532d29a876b93262108099fb
|
uses: guibranco/github-status-action-v2@5ef6e175c333bc629f3718b083c8a2ff6e0bbfbc
|
||||||
with:
|
with:
|
||||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||||
state: success
|
state: success
|
||||||
|
|||||||
2
.github/workflows/update-jitsi.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
run: "yarn update:jitsi"
|
run: "yarn update:jitsi"
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
branch: actions/jitsi-update
|
branch: actions/jitsi-update
|
||||||
|
|||||||
32
CHANGELOG.md
@@ -1,3 +1,35 @@
|
|||||||
|
Changes in [1.11.95](https://github.com/element-hq/element-web/releases/tag/v1.11.95) (2025-03-11)
|
||||||
|
==================================================================================================
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
* Room List Store: Filter rooms by active space ([#29399](https://github.com/element-hq/element-web/pull/29399)). Contributed by @MidhunSureshR.
|
||||||
|
* Room List - Update the room list store on actions from the dispatcher ([#29397](https://github.com/element-hq/element-web/pull/29397)). Contributed by @MidhunSureshR.
|
||||||
|
* Room List - Implement a minimal view model ([#29357](https://github.com/element-hq/element-web/pull/29357)). Contributed by @MidhunSureshR.
|
||||||
|
* New room list: add space menu in room header ([#29352](https://github.com/element-hq/element-web/pull/29352)). Contributed by @florianduros.
|
||||||
|
* Room List - Store sorted rooms in skip list ([#29345](https://github.com/element-hq/element-web/pull/29345)). Contributed by @MidhunSureshR.
|
||||||
|
* New room list: add dial to search section ([#29359](https://github.com/element-hq/element-web/pull/29359)). Contributed by @florianduros.
|
||||||
|
* New room list: add compose menu for spaces in header ([#29347](https://github.com/element-hq/element-web/pull/29347)). Contributed by @florianduros.
|
||||||
|
* Use EditInPlace control for Identity Server picker to improve a11y ([#29280](https://github.com/element-hq/element-web/pull/29280)). Contributed by @Half-Shot.
|
||||||
|
* First step to add header to new room list ([#29320](https://github.com/element-hq/element-web/pull/29320)). Contributed by @florianduros.
|
||||||
|
* Add Windows 64-bit arm link and remove 32-bit link on compatibility page ([#29312](https://github.com/element-hq/element-web/pull/29312)). Contributed by @t3chguy.
|
||||||
|
* Honour the backup disable flag from Element X ([#29290](https://github.com/element-hq/element-web/pull/29290)). Contributed by @dbkr.
|
||||||
|
|
||||||
|
## 🐛 Bug Fixes
|
||||||
|
|
||||||
|
* Fix edited code block width ([#29394](https://github.com/element-hq/element-web/pull/29394)). Contributed by @florianduros.
|
||||||
|
* new room list: keep space name in one line in header ([#29369](https://github.com/element-hq/element-web/pull/29369)). Contributed by @florianduros.
|
||||||
|
* Dismiss "Key storage out of sync" toast when secrets received ([#29348](https://github.com/element-hq/element-web/pull/29348)). Contributed by @richvdh.
|
||||||
|
* Minor CSS fixes for the new room list ([#29334](https://github.com/element-hq/element-web/pull/29334)). Contributed by @florianduros.
|
||||||
|
* Add padding to room header icon ([#29271](https://github.com/element-hq/element-web/pull/29271)). Contributed by @langleyd.
|
||||||
|
|
||||||
|
|
||||||
|
Changes in [1.11.94](https://github.com/element-hq/element-web/releases/tag/v1.11.94) (2025-02-27)
|
||||||
|
==================================================================================================
|
||||||
|
## 🐛 Bug Fixes
|
||||||
|
|
||||||
|
* [Backport staging] fix: /tmp/element-web-config may already exist preventing the container from booting up ([#29377](https://github.com/element-hq/element-web/pull/29377)). Contributed by @RiotRobot.
|
||||||
|
|
||||||
|
|
||||||
Changes in [1.11.93](https://github.com/element-hq/element-web/releases/tag/v1.11.93) (2025-02-25)
|
Changes in [1.11.93](https://github.com/element-hq/element-web/releases/tag/v1.11.93) (2025-02-25)
|
||||||
==================================================================================================
|
==================================================================================================
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# syntax=docker.io/docker/dockerfile:1.7-labs
|
# syntax=docker.io/docker/dockerfile:1.14-labs
|
||||||
|
|
||||||
# Builder
|
# Builder
|
||||||
FROM --platform=$BUILDPLATFORM node:22-bullseye AS builder
|
FROM --platform=$BUILDPLATFORM node:22-bullseye AS builder
|
||||||
@@ -47,4 +47,4 @@ USER nginx
|
|||||||
# HTTP listen port
|
# HTTP listen port
|
||||||
ENV ELEMENT_WEB_PORT=80
|
ENV ELEMENT_WEB_PORT=80
|
||||||
|
|
||||||
HEALTHCHECK --start-period=5s CMD wget --retry-connrefused --tries=5 -q --wait=3 --spider http://localhost:$ELEMENT_WEB_PORT/config.json
|
HEALTHCHECK --start-period=5s CMD wget -q --spider http://localhost:$ELEMENT_WEB_PORT/config.json
|
||||||
|
|||||||
@@ -31,5 +31,7 @@ module.exports = {
|
|||||||
|
|
||||||
"@babel/plugin-syntax-dynamic-import",
|
"@babel/plugin-syntax-dynamic-import",
|
||||||
"@babel/plugin-transform-runtime",
|
"@babel/plugin-transform-runtime",
|
||||||
|
["@babel/plugin-proposal-decorators", { version: "2023-11" }], // only needed by the js-sdk
|
||||||
|
"@babel/plugin-transform-class-static-block", // only needed by the js-sdk for decorators
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# Loads modules from `/tmp/element-web-modules` into config.json's `modules` field
|
# Loads modules from `/modules` into config.json's `modules` field
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
@@ -11,19 +11,19 @@ entrypoint_log() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Copy these config files as a base
|
# Copy these config files as a base
|
||||||
mkdir /tmp/element-web-config
|
mkdir -p /tmp/element-web-config
|
||||||
cp /app/config*.json /tmp/element-web-config/
|
cp /app/config*.json /tmp/element-web-config/
|
||||||
|
|
||||||
# If there are modules to be loaded
|
# If there are modules to be loaded
|
||||||
if [ -d "/tmp/element-web-modules" ]; then
|
if [ -d "/modules" ]; then
|
||||||
cd /tmp/element-web-modules
|
cd /modules
|
||||||
|
|
||||||
for MODULE in *
|
for MODULE in *
|
||||||
do
|
do
|
||||||
# If the module has a package.json, use its main field as the entrypoint
|
# If the module has a package.json, use its main field as the entrypoint
|
||||||
ENTRYPOINT="index.js"
|
ENTRYPOINT="index.js"
|
||||||
if [ -f "/tmp/element-web-modules/$MODULE/package.json" ]; then
|
if [ -f "/modules/$MODULE/package.json" ]; then
|
||||||
ENTRYPOINT=$(jq -r '.main' "/tmp/element-web-modules/$MODULE/package.json")
|
ENTRYPOINT=$(jq -r '.main' "/modules/$MODULE/package.json")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
entrypoint_log "Loading module $MODULE with entrypoint $ENTRYPOINT"
|
entrypoint_log "Loading module $MODULE with entrypoint $ENTRYPOINT"
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ server {
|
|||||||
add_header Cache-Control "no-cache";
|
add_header Cache-Control "no-cache";
|
||||||
}
|
}
|
||||||
location /modules {
|
location /modules {
|
||||||
alias /tmp/element-web-modules;
|
alias /modules;
|
||||||
}
|
}
|
||||||
# redirect server error pages to the static page /50x.html
|
# redirect server error pages to the static page /50x.html
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ image as root (`docker run --user 0`) or, better, change the port that nginx
|
|||||||
listens on via the `ELEMENT_WEB_PORT` environment variable.
|
listens on via the `ELEMENT_WEB_PORT` environment variable.
|
||||||
|
|
||||||
[Element Web Modules](https://github.com/element-hq/element-modules/tree/main/packages/element-web-module-api) can be dynamically loaded
|
[Element Web Modules](https://github.com/element-hq/element-modules/tree/main/packages/element-web-module-api) can be dynamically loaded
|
||||||
by being made available (e.g. via bind mount) in a directory within `/tmp/element-web-modules/`.
|
by being made available (e.g. via bind mount) in a directory within `/modules/`.
|
||||||
The default entrypoint will be index.js in that directory but can be overridden if a package.json file is found with a `main` directive.
|
The default entrypoint will be index.js in that directory but can be overridden if a package.json file is found with a `main` directive.
|
||||||
These modules will be presented in a `/modules` subdirectory within the webroot, and automatically added to the config.json `modules` field.
|
These modules will be presented in a `/modules` subdirectory within the webroot, and automatically added to the config.json `modules` field.
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ If you wish to use docker in read-only mode,
|
|||||||
you should follow the [upstream instructions](https://hub.docker.com/_/nginx#:~:text=Running%20nginx%20in%20read%2Donly%20mode)
|
you should follow the [upstream instructions](https://hub.docker.com/_/nginx#:~:text=Running%20nginx%20in%20read%2Donly%20mode)
|
||||||
but additionally include the following directories:
|
but additionally include the following directories:
|
||||||
|
|
||||||
- /tmp/element-web-config/
|
- /tmp/
|
||||||
- /etc/nginx/conf.d/
|
- /etc/nginx/conf.d/
|
||||||
|
|
||||||
The behaviour of the docker image can be customised via the following
|
The behaviour of the docker image can be customised via the following
|
||||||
|
|||||||
1
knip.ts
@@ -19,6 +19,7 @@ export default {
|
|||||||
ignore: [
|
ignore: [
|
||||||
// Keep for now
|
// Keep for now
|
||||||
"src/hooks/useLocalStorageState.ts",
|
"src/hooks/useLocalStorageState.ts",
|
||||||
|
"src/hooks/useTimeout.ts",
|
||||||
"src/components/views/elements/InfoTooltip.tsx",
|
"src/components/views/elements/InfoTooltip.tsx",
|
||||||
"src/components/views/elements/StyledCheckbox.tsx",
|
"src/components/views/elements/StyledCheckbox.tsx",
|
||||||
],
|
],
|
||||||
|
|||||||
18
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "element-web",
|
"name": "element-web",
|
||||||
"version": "1.11.93",
|
"version": "1.11.95",
|
||||||
"description": "Element: the future of secure communication",
|
"description": "Element: the future of secure communication",
|
||||||
"author": "New Vector Ltd.",
|
"author": "New Vector Ltd.",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
"@types/react-dom": "18.3.5",
|
"@types/react-dom": "18.3.5",
|
||||||
"oidc-client-ts": "3.1.0",
|
"oidc-client-ts": "3.1.0",
|
||||||
"jwt-decode": "4.0.0",
|
"jwt-decode": "4.0.0",
|
||||||
"caniuse-lite": "1.0.30001699",
|
"caniuse-lite": "1.0.30001701",
|
||||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
|
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
|
||||||
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
|
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
|
||||||
},
|
},
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
"@types/react-virtualized": "^9.21.30",
|
"@types/react-virtualized": "^9.21.30",
|
||||||
"@vector-im/compound-design-tokens": "^4.0.0",
|
"@vector-im/compound-design-tokens": "^4.0.0",
|
||||||
"@vector-im/compound-web": "^7.6.4",
|
"@vector-im/compound-web": "^7.6.4",
|
||||||
"@vector-im/matrix-wysiwyg": "2.38.0",
|
"@vector-im/matrix-wysiwyg": "2.38.2",
|
||||||
"@zxcvbn-ts/core": "^3.0.4",
|
"@zxcvbn-ts/core": "^3.0.4",
|
||||||
"@zxcvbn-ts/language-common": "^3.0.4",
|
"@zxcvbn-ts/language-common": "^3.0.4",
|
||||||
"@zxcvbn-ts/language-en": "^3.0.2",
|
"@zxcvbn-ts/language-en": "^3.0.2",
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
"png-chunks-extract": "^1.0.0",
|
"png-chunks-extract": "^1.0.0",
|
||||||
"posthog-js": "1.157.2",
|
"posthog-js": "1.157.2",
|
||||||
"qrcode": "1.5.4",
|
"qrcode": "1.5.4",
|
||||||
"re-resizable": "6.10.3",
|
"re-resizable": "6.11.2",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-beautiful-dnd": "^13.1.0",
|
"react-beautiful-dnd": "^13.1.0",
|
||||||
"react-blurhash": "^0.3.0",
|
"react-blurhash": "^0.3.0",
|
||||||
@@ -162,9 +162,11 @@
|
|||||||
"@babel/core": "^7.12.10",
|
"@babel/core": "^7.12.10",
|
||||||
"@babel/eslint-parser": "^7.12.10",
|
"@babel/eslint-parser": "^7.12.10",
|
||||||
"@babel/eslint-plugin": "^7.12.10",
|
"@babel/eslint-plugin": "^7.12.10",
|
||||||
|
"@babel/plugin-proposal-decorators": "^7.25.9",
|
||||||
"@babel/plugin-proposal-export-default-from": "^7.12.1",
|
"@babel/plugin-proposal-export-default-from": "^7.12.1",
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||||
"@babel/plugin-transform-class-properties": "^7.12.1",
|
"@babel/plugin-transform-class-properties": "^7.12.1",
|
||||||
|
"@babel/plugin-transform-class-static-block": "^7.26.0",
|
||||||
"@babel/plugin-transform-logical-assignment-operators": "^7.20.7",
|
"@babel/plugin-transform-logical-assignment-operators": "^7.20.7",
|
||||||
"@babel/plugin-transform-nullish-coalescing-operator": "^7.12.1",
|
"@babel/plugin-transform-nullish-coalescing-operator": "^7.12.1",
|
||||||
"@babel/plugin-transform-numeric-separator": "^7.12.7",
|
"@babel/plugin-transform-numeric-separator": "^7.12.7",
|
||||||
@@ -218,12 +220,12 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^8.19.0",
|
"@typescript-eslint/eslint-plugin": "^8.19.0",
|
||||||
"@typescript-eslint/parser": "^8.19.0",
|
"@typescript-eslint/parser": "^8.19.0",
|
||||||
"babel-jest": "^29.0.0",
|
"babel-jest": "^29.0.0",
|
||||||
"babel-loader": "^9.0.0",
|
"babel-loader": "^10.0.0",
|
||||||
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
|
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
|
||||||
"blob-polyfill": "^9.0.0",
|
"blob-polyfill": "^9.0.0",
|
||||||
"chokidar": "^4.0.0",
|
"chokidar": "^4.0.0",
|
||||||
"concurrently": "^9.0.0",
|
"concurrently": "^9.0.0",
|
||||||
"copy-webpack-plugin": "^12.0.0",
|
"copy-webpack-plugin": "^13.0.0",
|
||||||
"core-js": "^3.38.1",
|
"core-js": "^3.38.1",
|
||||||
"cronstrue": "^2.41.0",
|
"cronstrue": "^2.41.0",
|
||||||
"css-loader": "^7.0.0",
|
"css-loader": "^7.0.0",
|
||||||
@@ -274,7 +276,7 @@
|
|||||||
"postcss-preset-env": "^10.0.0",
|
"postcss-preset-env": "^10.0.0",
|
||||||
"postcss-scss": "^4.0.4",
|
"postcss-scss": "^4.0.4",
|
||||||
"postcss-simple-vars": "^7.0.1",
|
"postcss-simple-vars": "^7.0.1",
|
||||||
"prettier": "3.5.1",
|
"prettier": "3.5.2",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"raw-loader": "^4.0.2",
|
"raw-loader": "^4.0.2",
|
||||||
"rimraf": "^6.0.0",
|
"rimraf": "^6.0.0",
|
||||||
@@ -288,7 +290,7 @@
|
|||||||
"terser-webpack-plugin": "^5.3.9",
|
"terser-webpack-plugin": "^5.3.9",
|
||||||
"testcontainers": "^10.16.0",
|
"testcontainers": "^10.16.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "5.7.3",
|
"typescript": "5.8.2",
|
||||||
"util": "^0.12.5",
|
"util": "^0.12.5",
|
||||||
"web-streams-polyfill": "^4.0.0",
|
"web-streams-polyfill": "^4.0.0",
|
||||||
"webpack": "^5.89.0",
|
"webpack": "^5.89.0",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM mcr.microsoft.com/playwright:v1.50.1-noble
|
FROM mcr.microsoft.com/playwright:v1.51.0-noble
|
||||||
|
|
||||||
WORKDIR /work
|
WORKDIR /work
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const checkDMRoom = async (page: Page) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const startDMWithBob = async (page: Page, bob: Bot) => {
|
const startDMWithBob = async (page: Page, bob: Bot) => {
|
||||||
await page.locator(".mx_RoomList").getByRole("button", { name: "Start chat" }).click();
|
await page.locator(".mx_LegacyRoomList").getByRole("button", { name: "Start chat" }).click();
|
||||||
await page.getByTestId("invite-dialog-input").fill(bob.credentials.userId);
|
await page.getByTestId("invite-dialog-input").fill(bob.credentials.userId);
|
||||||
await page.locator(".mx_InviteDialog_tile_nameStack_name").getByText("Bob").click();
|
await page.locator(".mx_InviteDialog_tile_nameStack_name").getByText("Bob").click();
|
||||||
await expect(
|
await expect(
|
||||||
|
|||||||
@@ -22,18 +22,6 @@ test.use({
|
|||||||
msc3814_enabled: true,
|
msc3814_enabled: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
config: async ({ config, context }, use) => {
|
|
||||||
const wellKnown = {
|
|
||||||
...config.default_server_config,
|
|
||||||
"org.matrix.msc3814": true,
|
|
||||||
};
|
|
||||||
|
|
||||||
await context.route("https://localhost/.well-known/matrix/client", async (route) => {
|
|
||||||
await route.fulfill({ json: wellKnown });
|
|
||||||
});
|
|
||||||
|
|
||||||
await use(config);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe("Dehydration", () => {
|
test.describe("Dehydration", () => {
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ test.describe("Invite dialog", function () {
|
|||||||
"should support inviting a user to Direct Messages",
|
"should support inviting a user to Direct Messages",
|
||||||
{ tag: "@screenshot" },
|
{ tag: "@screenshot" },
|
||||||
async ({ page, app, user, bot }) => {
|
async ({ page, app, user, bot }) => {
|
||||||
await page.locator(".mx_RoomList").getByRole("button", { name: "Start chat" }).click();
|
await page.locator(".mx_LegacyRoomList").getByRole("button", { name: "Start chat" }).click();
|
||||||
|
|
||||||
const other = page.locator(".mx_InviteDialog_other");
|
const other = page.locator(".mx_InviteDialog_other");
|
||||||
// Assert that the header is rendered
|
// Assert that the header is rendered
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect, test } from "../../../element-web-test";
|
||||||
|
import type { Page } from "@playwright/test";
|
||||||
|
|
||||||
|
test.describe("Room list filters and sort", () => {
|
||||||
|
test.use({
|
||||||
|
displayName: "Alice",
|
||||||
|
botCreateOpts: {
|
||||||
|
displayName: "BotBob",
|
||||||
|
autoAcceptInvites: true,
|
||||||
|
},
|
||||||
|
labsFlags: ["feature_new_room_list"],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the room list
|
||||||
|
* @param page
|
||||||
|
*/
|
||||||
|
function getRoomList(page: Page) {
|
||||||
|
return page.getByTestId("room-list");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrimaryFilters(page: Page) {
|
||||||
|
return page.getByRole("listbox", { name: "Room list filters" });
|
||||||
|
}
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page, app, bot, user }) => {
|
||||||
|
// The notification toast is displayed above the search section
|
||||||
|
await app.closeNotificationToast();
|
||||||
|
|
||||||
|
await app.client.createRoom({ name: "empty room" });
|
||||||
|
|
||||||
|
const unReadDmId = await bot.createRoom({
|
||||||
|
name: "unread dm",
|
||||||
|
invite: [user.userId],
|
||||||
|
is_direct: true,
|
||||||
|
});
|
||||||
|
await bot.sendMessage(unReadDmId, "I am a robot. Beep.");
|
||||||
|
|
||||||
|
const unReadRoomId = await app.client.createRoom({ name: "unread room" });
|
||||||
|
await app.client.inviteUser(unReadRoomId, bot.credentials.userId);
|
||||||
|
await bot.joinRoom(unReadRoomId);
|
||||||
|
await bot.sendMessage(unReadRoomId, "I am a robot. Beep.");
|
||||||
|
|
||||||
|
const favouriteId = await app.client.createRoom({ name: "favourite room" });
|
||||||
|
await app.client.evaluate(async (client, favouriteId) => {
|
||||||
|
await client.setRoomTag(favouriteId, "m.favourite", { order: 0.5 });
|
||||||
|
}, favouriteId);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should filter the list (with primary filters)", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
||||||
|
const roomList = getRoomList(page);
|
||||||
|
const primaryFilters = getPrimaryFilters(page);
|
||||||
|
|
||||||
|
const allFilters = await primaryFilters.locator("option").all();
|
||||||
|
for (const filter of allFilters) {
|
||||||
|
expect(await filter.getAttribute("aria-selected")).toBe("false");
|
||||||
|
}
|
||||||
|
await expect(primaryFilters).toMatchScreenshot("unselected-primary-filters.png");
|
||||||
|
|
||||||
|
await primaryFilters.getByRole("option", { name: "Unread" }).click();
|
||||||
|
// only one room should be visible
|
||||||
|
await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible();
|
||||||
|
await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible();
|
||||||
|
expect(await roomList.locator("role=gridcell").count()).toBe(2);
|
||||||
|
await expect(primaryFilters).toMatchScreenshot("unread-primary-filters.png");
|
||||||
|
|
||||||
|
await primaryFilters.getByRole("option", { name: "Favourite" }).click();
|
||||||
|
await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible();
|
||||||
|
expect(await roomList.locator("role=gridcell").count()).toBe(1);
|
||||||
|
|
||||||
|
await primaryFilters.getByRole("option", { name: "People" }).click();
|
||||||
|
await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible();
|
||||||
|
expect(await roomList.locator("role=gridcell").count()).toBe(1);
|
||||||
|
|
||||||
|
await primaryFilters.getByRole("option", { name: "Rooms" }).click();
|
||||||
|
await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible();
|
||||||
|
await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible();
|
||||||
|
await expect(roomList.getByRole("gridcell", { name: "empty room" })).toBeVisible();
|
||||||
|
expect(await roomList.locator("role=gridcell").count()).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -9,7 +9,7 @@ import { type Page } from "@playwright/test";
|
|||||||
|
|
||||||
import { test, expect } from "../../../element-web-test";
|
import { test, expect } from "../../../element-web-test";
|
||||||
|
|
||||||
test.describe("Search section of the room list", () => {
|
test.describe("Room list panel", () => {
|
||||||
test.use({
|
test.use({
|
||||||
labsFlags: ["feature_new_room_list"],
|
labsFlags: ["feature_new_room_list"],
|
||||||
});
|
});
|
||||||
@@ -19,16 +19,23 @@ test.describe("Search section of the room list", () => {
|
|||||||
* @param page
|
* @param page
|
||||||
*/
|
*/
|
||||||
function getRoomListView(page: Page) {
|
function getRoomListView(page: Page) {
|
||||||
return page.getByTestId("room-list-view");
|
return page.getByTestId("room-list-panel");
|
||||||
}
|
}
|
||||||
|
|
||||||
test.beforeEach(async ({ page, app, user }) => {
|
test.beforeEach(async ({ page, app, user }) => {
|
||||||
// The notification toast is displayed above the search section
|
// The notification toast is displayed above the search section
|
||||||
await app.closeNotificationToast();
|
await app.closeNotificationToast();
|
||||||
|
|
||||||
|
// Populate the room list
|
||||||
|
for (let i = 0; i < 20; i++) {
|
||||||
|
await app.client.createRoom({ name: `room${i}` });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should render the room list view", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
test("should render the room list panel", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
||||||
const roomListView = getRoomListView(page);
|
const roomListView = getRoomListView(page);
|
||||||
await expect(roomListView).toMatchScreenshot("room-list-view.png");
|
// Wait for the last room to be visible
|
||||||
|
await expect(roomListView.getByRole("gridcell", { name: "Open room room19" })).toBeVisible();
|
||||||
|
await expect(roomListView).toMatchScreenshot("room-list-panel.png");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
50
playwright/e2e/left-panel/room-list-panel/room-list.spec.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { type Page } from "@playwright/test";
|
||||||
|
|
||||||
|
import { test, expect } from "../../../element-web-test";
|
||||||
|
|
||||||
|
test.describe("Room list", () => {
|
||||||
|
test.use({
|
||||||
|
labsFlags: ["feature_new_room_list"],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the room list
|
||||||
|
* @param page
|
||||||
|
*/
|
||||||
|
function getRoomList(page: Page) {
|
||||||
|
return page.getByTestId("room-list");
|
||||||
|
}
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page, app, user }) => {
|
||||||
|
// The notification toast is displayed above the search section
|
||||||
|
await app.closeNotificationToast();
|
||||||
|
for (let i = 0; i < 30; i++) {
|
||||||
|
await app.client.createRoom({ name: `room${i}` });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should render the room list", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
||||||
|
const roomListView = getRoomList(page);
|
||||||
|
await expect(roomListView.getByRole("gridcell", { name: "Open room room29" })).toBeVisible();
|
||||||
|
await expect(roomListView).toMatchScreenshot("room-list.png");
|
||||||
|
|
||||||
|
await roomListView.hover();
|
||||||
|
// Scroll to the end of the room list
|
||||||
|
await page.mouse.wheel(0, 1000);
|
||||||
|
await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible();
|
||||||
|
await expect(roomListView).toMatchScreenshot("room-list-scrolled.png");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should open the room when it is clicked", async ({ page, app, user }) => {
|
||||||
|
const roomListView = getRoomList(page);
|
||||||
|
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
|
||||||
|
await expect(page.getByRole("heading", { name: "room29", level: 1 })).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -67,6 +67,15 @@ test.describe("RightPanel", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test("should have padding under leave room", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||||
|
await viewRoomSummaryByName(page, app, ROOM_NAME);
|
||||||
|
|
||||||
|
const leaveButton = await page.getByRole("menuitem", { name: "Leave Room" });
|
||||||
|
await leaveButton.scrollIntoViewIfNeeded();
|
||||||
|
|
||||||
|
await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("with-leave-room.png");
|
||||||
|
});
|
||||||
|
|
||||||
test("should handle clicking add widgets", async ({ page, app }) => {
|
test("should handle clicking add widgets", async ({ page, app }) => {
|
||||||
await viewRoomSummaryByName(page, app, ROOM_NAME);
|
await viewRoomSummaryByName(page, app, ROOM_NAME);
|
||||||
|
|
||||||
|
|||||||
18
playwright/e2e/settings/quick-settings-menu.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { test, expect } from "../../element-web-test";
|
||||||
|
|
||||||
|
test.describe("Quick settings menu", () => {
|
||||||
|
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
|
||||||
|
await page.getByRole("button", { name: "Quick settings" }).click();
|
||||||
|
// Assert that the top heading is renderedc
|
||||||
|
const settings = page.getByTestId("quick-settings-menu");
|
||||||
|
await expect(settings).toBeVisible();
|
||||||
|
await expect(settings).toMatchScreenshot("quick-settings.png");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -91,7 +91,7 @@ test.describe("Security user settings tab", () => {
|
|||||||
await expect(tab.getByText(`Identity server (identity.example.org)`, { exact: true })).toBeVisible();
|
await expect(tab.getByText(`Identity server (identity.example.org)`, { exact: true })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should enable show integrations as enabled", async ({ app, page, user }) => {
|
test("should show integrations as enabled", async ({ app, page, user }) => {
|
||||||
const tab = await app.settings.openUserSettings("Security");
|
const tab = await app.settings.openUserSettings("Security");
|
||||||
|
|
||||||
const setIntegrationManager = tab.locator(".mx_SetIntegrationManager");
|
const setIntegrationManager = tab.locator(".mx_SetIntegrationManager");
|
||||||
@@ -102,7 +102,9 @@ test.describe("Security user settings tab", () => {
|
|||||||
}),
|
}),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
// Make sure integration manager's toggle switch is enabled
|
// Make sure integration manager's toggle switch is enabled
|
||||||
await expect(setIntegrationManager.locator(".mx_ToggleSwitch_enabled")).toBeVisible();
|
const toggleswitch = setIntegrationManager.getByLabel("Enable the integration manager");
|
||||||
|
await expect(toggleswitch).toBeVisible();
|
||||||
|
await expect(toggleswitch).toBeChecked();
|
||||||
await expect(setIntegrationManager.locator(".mx_SetIntegrationManager_heading_manager")).toHaveText(
|
await expect(setIntegrationManager.locator(".mx_SetIntegrationManager_heading_manager")).toHaveText(
|
||||||
"Manage integrations(scalar.vector.im)",
|
"Manage integrations(scalar.vector.im)",
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -875,6 +875,40 @@ test.describe("Timeline", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should render a code block", { tag: "@screenshot" }, async ({ page, app, room }) => {
|
||||||
|
await page.goto(`/#/room/${room.roomId}`);
|
||||||
|
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
||||||
|
|
||||||
|
// Wait until configuration is finished
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator(".mx_GenericEventListSummary_summary")
|
||||||
|
.getByText(`${OLD_NAME} created and configured the room.`),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Send a code block
|
||||||
|
const composer = app.getComposerField();
|
||||||
|
await composer.fill("```\nconsole.log('Hello, world!');\n```");
|
||||||
|
await composer.press("Enter");
|
||||||
|
|
||||||
|
const tile = page.locator(".mx_EventTile");
|
||||||
|
await expect(tile).toBeVisible();
|
||||||
|
await expect(tile).toMatchScreenshot("code-block.png", { mask: [page.locator(".mx_MessageTimestamp")] });
|
||||||
|
|
||||||
|
// Edit a code block and assert the edited code block has been correctly rendered
|
||||||
|
await tile.hover();
|
||||||
|
await page.getByRole("toolbar", { name: "Message Actions" }).getByRole("button", { name: "Edit" }).click();
|
||||||
|
await page
|
||||||
|
.getByRole("textbox", { name: "Edit message" })
|
||||||
|
.fill("```\nconsole.log('Edited: Hello, world!');\n```");
|
||||||
|
await page.getByRole("textbox", { name: "Edit message" }).press("Enter");
|
||||||
|
|
||||||
|
const newTile = page.locator(".mx_EventTile");
|
||||||
|
await expect(newTile).toMatchScreenshot("edited-code-block.png", {
|
||||||
|
mask: [page.locator(".mx_MessageTimestamp")],
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe("message sending", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
test.describe("message sending", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
@@ -25,7 +25,7 @@ import { type HomeserverContainer, type StartedHomeserverContainer } from "./Hom
|
|||||||
import { type StartedMatrixAuthenticationServiceContainer } from "./mas.ts";
|
import { type StartedMatrixAuthenticationServiceContainer } from "./mas.ts";
|
||||||
import { Api, ClientServerApi, type Verb } from "../plugins/utils/api.ts";
|
import { Api, ClientServerApi, type Verb } from "../plugins/utils/api.ts";
|
||||||
|
|
||||||
const TAG = "develop@sha256:8d1c531cf6010b63142a04e1b138a60720946fa131ad404813232f02db4ce7ba";
|
const TAG = "develop@sha256:26e0d9c5ca96218243432d48a9f8596e4c1bc10b748f0a1bddf9916b914d1216";
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {
|
const DEFAULT_CONFIG = {
|
||||||
server_name: "localhost",
|
server_name: "localhost",
|
||||||
@@ -144,6 +144,7 @@ const DEFAULT_CONFIG = {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
include_offline_users_on_sync: true,
|
include_offline_users_on_sync: true,
|
||||||
},
|
},
|
||||||
|
room_list_publication_rules: [{ action: "allow" }],
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SynapseConfig = Partial<typeof DEFAULT_CONFIG>;
|
export type SynapseConfig = Partial<typeof DEFAULT_CONFIG>;
|
||||||
|
|||||||
@@ -269,9 +269,12 @@
|
|||||||
@import "./views/right_panel/_VerificationPanel.pcss";
|
@import "./views/right_panel/_VerificationPanel.pcss";
|
||||||
@import "./views/right_panel/_WidgetCard.pcss";
|
@import "./views/right_panel/_WidgetCard.pcss";
|
||||||
@import "./views/room_settings/_AliasSettings.pcss";
|
@import "./views/room_settings/_AliasSettings.pcss";
|
||||||
@import "./views/rooms/RoomListView/_RoomListHeaderView.pcss";
|
@import "./views/rooms/RoomListPanel/_RoomList.pcss";
|
||||||
@import "./views/rooms/RoomListView/_RoomListSearch.pcss";
|
@import "./views/rooms/RoomListPanel/_RoomListCell.pcss";
|
||||||
@import "./views/rooms/RoomListView/_RoomListView.pcss";
|
@import "./views/rooms/RoomListPanel/_RoomListHeaderView.pcss";
|
||||||
|
@import "./views/rooms/RoomListPanel/_RoomListPanel.pcss";
|
||||||
|
@import "./views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss";
|
||||||
|
@import "./views/rooms/RoomListPanel/_RoomListSearch.pcss";
|
||||||
@import "./views/rooms/_AppsDrawer.pcss";
|
@import "./views/rooms/_AppsDrawer.pcss";
|
||||||
@import "./views/rooms/_Autocomplete.pcss";
|
@import "./views/rooms/_Autocomplete.pcss";
|
||||||
@import "./views/rooms/_AuxPanel.pcss";
|
@import "./views/rooms/_AuxPanel.pcss";
|
||||||
@@ -289,6 +292,7 @@
|
|||||||
@import "./views/rooms/_IRCLayout.pcss";
|
@import "./views/rooms/_IRCLayout.pcss";
|
||||||
@import "./views/rooms/_InvitedIconView.pcss";
|
@import "./views/rooms/_InvitedIconView.pcss";
|
||||||
@import "./views/rooms/_JumpToBottomButton.pcss";
|
@import "./views/rooms/_JumpToBottomButton.pcss";
|
||||||
|
@import "./views/rooms/_LegacyRoomList.pcss";
|
||||||
@import "./views/rooms/_LegacyRoomListHeader.pcss";
|
@import "./views/rooms/_LegacyRoomListHeader.pcss";
|
||||||
@import "./views/rooms/_LinkPreviewGroup.pcss";
|
@import "./views/rooms/_LinkPreviewGroup.pcss";
|
||||||
@import "./views/rooms/_LinkPreviewWidget.pcss";
|
@import "./views/rooms/_LinkPreviewWidget.pcss";
|
||||||
@@ -313,7 +317,6 @@
|
|||||||
@import "./views/rooms/_RoomHeader.pcss";
|
@import "./views/rooms/_RoomHeader.pcss";
|
||||||
@import "./views/rooms/_RoomInfoLine.pcss";
|
@import "./views/rooms/_RoomInfoLine.pcss";
|
||||||
@import "./views/rooms/_RoomKnocksBar.pcss";
|
@import "./views/rooms/_RoomKnocksBar.pcss";
|
||||||
@import "./views/rooms/_RoomList.pcss";
|
|
||||||
@import "./views/rooms/_RoomPreviewBar.pcss";
|
@import "./views/rooms/_RoomPreviewBar.pcss";
|
||||||
@import "./views/rooms/_RoomPreviewCard.pcss";
|
@import "./views/rooms/_RoomPreviewCard.pcss";
|
||||||
@import "./views/rooms/_RoomSearchAuxPanel.pcss";
|
@import "./views/rooms/_RoomSearchAuxPanel.pcss";
|
||||||
@@ -362,6 +365,7 @@
|
|||||||
@import "./views/settings/encryption/_EncryptionCard.pcss";
|
@import "./views/settings/encryption/_EncryptionCard.pcss";
|
||||||
@import "./views/settings/encryption/_EncryptionCardEmphasisedContent.pcss";
|
@import "./views/settings/encryption/_EncryptionCardEmphasisedContent.pcss";
|
||||||
@import "./views/settings/encryption/_RecoveryPanelOutOfSync.pcss";
|
@import "./views/settings/encryption/_RecoveryPanelOutOfSync.pcss";
|
||||||
|
@import "./views/settings/encryption/_ResetIdentityPanel.pcss";
|
||||||
@import "./views/settings/tabs/_SettingsBanner.pcss";
|
@import "./views/settings/tabs/_SettingsBanner.pcss";
|
||||||
@import "./views/settings/tabs/_SettingsIndent.pcss";
|
@import "./views/settings/tabs/_SettingsIndent.pcss";
|
||||||
@import "./views/settings/tabs/_SettingsSection.pcss";
|
@import "./views/settings/tabs/_SettingsSection.pcss";
|
||||||
|
|||||||
@@ -12,4 +12,5 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
align-items: var(--mx-flex-align, unset);
|
align-items: var(--mx-flex-align, unset);
|
||||||
justify-content: var(--mx-flex-justify, unset);
|
justify-content: var(--mx-flex-justify, unset);
|
||||||
gap: var(--mx-flex-gap, unset);
|
gap: var(--mx-flex-gap, unset);
|
||||||
|
flex-wrap: var(--mx-flex-wrap, unset);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,3 +100,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
.mx_RoomSummaryCard_roomName {
|
.mx_RoomSummaryCard_roomName {
|
||||||
margin: $spacing-12 0 $spacing-4;
|
margin: $spacing-12 0 $spacing-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_leave {
|
||||||
|
margin: 0 0 var(--cpd-space-8x);
|
||||||
|
}
|
||||||
|
|||||||
15
res/css/views/rooms/RoomListPanel/_RoomList.pcss
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_RoomList {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.mx_RoomList_List {
|
||||||
|
/* Avoid when on hover, the background color to be on top of the right border */
|
||||||
|
padding-right: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
res/css/views/rooms/RoomListPanel/_RoomListCell.pcss
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The RoomCell has the following structure:
|
||||||
|
* button----------------------------------------|
|
||||||
|
* | <-12px-> container--------------------------|
|
||||||
|
* | | room avatar <-12px-> content-----|
|
||||||
|
* | | | room_name |
|
||||||
|
* | | | ----------| <-- border
|
||||||
|
* |---------------------------------------------|
|
||||||
|
*/
|
||||||
|
.mx_RoomListCell {
|
||||||
|
all: unset;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--cpd-color-bg-action-secondary-hovered);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomListCell_container {
|
||||||
|
padding-left: var(--cpd-space-3x);
|
||||||
|
font: var(--cpd-font-body-md-regular);
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.mx_RoomListCell_content {
|
||||||
|
height: 100%;
|
||||||
|
flex: 1;
|
||||||
|
/* The border is only under the room name and the future hover menu */
|
||||||
|
border-bottom: var(--cpd-border-width-0-5) solid var(--cpd-color-bg-subtle-secondary);
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
span {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,12 +6,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_RoomListHeaderView {
|
.mx_RoomListHeaderView {
|
||||||
height: 60px;
|
flex: 0 0 60px;
|
||||||
padding: 0 var(--cpd-space-3x);
|
padding: 0 var(--cpd-space-3x);
|
||||||
|
|
||||||
h1 {
|
.mx_RoomListHeaderView_title {
|
||||||
all: unset;
|
min-width: 0;
|
||||||
font: var(--cpd-font-heading-sm-semibold);
|
|
||||||
|
h1 {
|
||||||
|
all: unset;
|
||||||
|
font: var(--cpd-font-heading-sm-semibold);
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_RoomListView {
|
.mx_RoomListPanel {
|
||||||
background-color: var(--cpd-color-bg-canvas-default);
|
background-color: var(--cpd-color-bg-canvas-default);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-right: 1px solid var(--cpd-color-bg-subtle-primary);
|
border-right: 1px solid var(--cpd-color-bg-subtle-primary);
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_RoomListPrimaryFilters {
|
||||||
|
margin: unset;
|
||||||
|
list-style-type: none;
|
||||||
|
padding: var(--cpd-space-2x) var(--cpd-space-3x);
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
.mx_RoomListSearch {
|
.mx_RoomListSearch {
|
||||||
/* From figma, this should be aligned with the room header */
|
/* From figma, this should be aligned with the room header */
|
||||||
height: 64px;
|
flex: 0 0 64px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-bottom: var(--cpd-border-width-1) solid var(--cpd-color-bg-subtle-primary);
|
border-bottom: var(--cpd-border-width-1) solid var(--cpd-color-bg-subtle-primary);
|
||||||
padding: 0 var(--cpd-space-3x);
|
padding: 0 var(--cpd-space-3x);
|
||||||
@@ -683,6 +683,7 @@ $left-gutter: 64px;
|
|||||||
line-height: inherit !important;
|
line-height: inherit !important;
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
color: inherit; /* inherit the colour from the dark or light theme by default (but not for code blocks) */
|
color: inherit; /* inherit the colour from the dark or light theme by default (but not for code blocks) */
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
pre,
|
pre,
|
||||||
code {
|
code {
|
||||||
|
|||||||
@@ -6,31 +6,31 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
|||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_RoomList {
|
.mx_LegacyRoomList {
|
||||||
padding-right: 7px; /* width of the scrollbar, to line things up */
|
padding-right: 7px; /* width of the scrollbar, to line things up */
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomList_iconPlus::before {
|
.mx_LegacyRoomList_iconPlus::before {
|
||||||
mask-image: url("$(res)/img/element-icons/roomlist/plus-circle.svg");
|
mask-image: url("$(res)/img/element-icons/roomlist/plus-circle.svg");
|
||||||
}
|
}
|
||||||
.mx_RoomList_iconNewRoom::before {
|
.mx_LegacyRoomList_iconNewRoom::before {
|
||||||
mask-image: url("$(res)/img/element-icons/roomlist/hash-plus.svg");
|
mask-image: url("$(res)/img/element-icons/roomlist/hash-plus.svg");
|
||||||
}
|
}
|
||||||
.mx_RoomList_iconNewVideoRoom::before {
|
.mx_LegacyRoomList_iconNewVideoRoom::before {
|
||||||
mask-image: url("$(res)/img/element-icons/roomlist/hash-video.svg");
|
mask-image: url("$(res)/img/element-icons/roomlist/hash-video.svg");
|
||||||
}
|
}
|
||||||
.mx_RoomList_iconAddExistingRoom::before {
|
.mx_LegacyRoomList_iconAddExistingRoom::before {
|
||||||
mask-image: url("$(res)/img/element-icons/roomlist/hash.svg");
|
mask-image: url("$(res)/img/element-icons/roomlist/hash.svg");
|
||||||
}
|
}
|
||||||
.mx_RoomList_iconExplore::before {
|
.mx_LegacyRoomList_iconExplore::before {
|
||||||
mask-image: url("$(res)/img/element-icons/roomlist/hash-search.svg");
|
mask-image: url("$(res)/img/element-icons/roomlist/hash-search.svg");
|
||||||
}
|
}
|
||||||
.mx_RoomList_iconDialpad::before {
|
.mx_LegacyRoomList_iconDialpad::before {
|
||||||
mask-image: url("$(res)/img/element-icons/roomlist/dialpad.svg");
|
mask-image: url("$(res)/img/element-icons/roomlist/dialpad.svg");
|
||||||
}
|
}
|
||||||
.mx_RoomList_iconStartChat::before {
|
.mx_LegacyRoomList_iconStartChat::before {
|
||||||
mask-image: url("@vector-im/compound-design-tokens/icons/user-add-solid.svg");
|
mask-image: url("@vector-im/compound-design-tokens/icons/user-add-solid.svg");
|
||||||
}
|
}
|
||||||
.mx_RoomList_iconInvite::before {
|
.mx_LegacyRoomList_iconInvite::before {
|
||||||
mask-image: url("$(res)/img/element-icons/room/share.svg");
|
mask-image: url("$(res)/img/element-icons/room/share.svg");
|
||||||
}
|
}
|
||||||
@@ -7,19 +7,13 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_SetIntegrationManager {
|
.mx_SetIntegrationManager {
|
||||||
.mx_SettingsFlag {
|
.mx_SetIntegrationManager_heading_manager {
|
||||||
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
.mx_SetIntegrationManager_heading_manager {
|
column-gap: $spacing-4;
|
||||||
display: flex;
|
}
|
||||||
align-items: center;
|
form {
|
||||||
flex-wrap: wrap;
|
margin-top: var(--cpd-space-3x);
|
||||||
column-gap: $spacing-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_ToggleSwitch {
|
|
||||||
align-self: flex-start;
|
|
||||||
min-width: var(--ToggleSwitch-min-width); /* avoid compression */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
res/css/views/settings/encryption/_ResetIdentityPanel.pcss
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Red text for the "Do not close this window" warning
|
||||||
|
.mx_ResetIdentityPanel_warning {
|
||||||
|
color: var(--cpd-color-text-critical-primary);
|
||||||
|
}
|
||||||
2
src/@types/global.d.ts
vendored
@@ -47,6 +47,7 @@ import { type DeepReadonly } from "./common";
|
|||||||
import type MatrixChat from "../components/structures/MatrixChat";
|
import type MatrixChat from "../components/structures/MatrixChat";
|
||||||
import { type InitialCryptoSetupStore } from "../stores/InitialCryptoSetupStore";
|
import { type InitialCryptoSetupStore } from "../stores/InitialCryptoSetupStore";
|
||||||
import { type ModuleApiType } from "../modules/Api.ts";
|
import { type ModuleApiType } from "../modules/Api.ts";
|
||||||
|
import type { RoomListStoreV3Class } from "../stores/room-list-v3/RoomListStoreV3.ts";
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
|
||||||
@@ -99,6 +100,7 @@ declare global {
|
|||||||
mxToastStore: ToastStore;
|
mxToastStore: ToastStore;
|
||||||
mxDeviceListener: DeviceListener;
|
mxDeviceListener: DeviceListener;
|
||||||
mxRoomListStore: RoomListStore;
|
mxRoomListStore: RoomListStore;
|
||||||
|
mxRoomListStoreV3: RoomListStoreV3Class;
|
||||||
mxRoomListLayoutStore: RoomListLayoutStore;
|
mxRoomListLayoutStore: RoomListLayoutStore;
|
||||||
mxPlatformPeg: PlatformPeg;
|
mxPlatformPeg: PlatformPeg;
|
||||||
mxIntegrationManagers: typeof IntegrationManagers;
|
mxIntegrationManagers: typeof IntegrationManagers;
|
||||||
|
|||||||
@@ -521,8 +521,7 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
|
|||||||
[KeyBindingAction.GoToHome]: {
|
[KeyBindingAction.GoToHome]: {
|
||||||
default: {
|
default: {
|
||||||
ctrlKey: true,
|
ctrlKey: true,
|
||||||
altKey: !IS_MAC,
|
altKey: true,
|
||||||
shiftKey: IS_MAC,
|
|
||||||
key: Key.H,
|
key: Key.H,
|
||||||
},
|
},
|
||||||
displayName: _td("keyboard|go_home_view"),
|
displayName: _td("keyboard|go_home_view"),
|
||||||
@@ -585,7 +584,7 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
|
|||||||
},
|
},
|
||||||
[KeyBindingAction.ToggleHiddenEventVisibility]: {
|
[KeyBindingAction.ToggleHiddenEventVisibility]: {
|
||||||
default: {
|
default: {
|
||||||
ctrlOrCmdKey: true,
|
ctrlKey: true,
|
||||||
shiftKey: true,
|
shiftKey: true,
|
||||||
key: Key.H,
|
key: Key.H,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import PosthogTrackers from "../../PosthogTrackers";
|
|||||||
import type PageType from "../../PageTypes";
|
import type PageType from "../../PageTypes";
|
||||||
import { Landmark, LandmarkNavigation } from "../../accessibility/LandmarkNavigation";
|
import { Landmark, LandmarkNavigation } from "../../accessibility/LandmarkNavigation";
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
import { RoomListView } from "../views/rooms/RoomListView";
|
import { RoomListPanel } from "../views/rooms/RoomListPanel";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
isMinimized: boolean;
|
isMinimized: boolean;
|
||||||
@@ -390,7 +390,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||||||
return (
|
return (
|
||||||
<div className={containerClasses}>
|
<div className={containerClasses}>
|
||||||
<div className="mx_LeftPanel_roomListContainer">
|
<div className="mx_LeftPanel_roomListContainer">
|
||||||
<RoomListView activeSpace={this.state.activeSpace} />
|
<RoomListPanel activeSpace={this.state.activeSpace} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -118,8 +118,6 @@ import { RoomStatusBarUnsentMessages } from "./RoomStatusBarUnsentMessages";
|
|||||||
import { LargeLoader } from "./LargeLoader";
|
import { LargeLoader } from "./LargeLoader";
|
||||||
import { isVideoRoom } from "../../utils/video-rooms";
|
import { isVideoRoom } from "../../utils/video-rooms";
|
||||||
import { SDKContext } from "../../contexts/SDKContext";
|
import { SDKContext } from "../../contexts/SDKContext";
|
||||||
import { CallStore, CallStoreEvent } from "../../stores/CallStore";
|
|
||||||
import { type Call } from "../../models/Call";
|
|
||||||
import { RoomSearchView } from "./RoomSearchView";
|
import { RoomSearchView } from "./RoomSearchView";
|
||||||
import eventSearch, { type SearchInfo, SearchScope } from "../../Searching";
|
import eventSearch, { type SearchInfo, SearchScope } from "../../Searching";
|
||||||
import VoipUserMapper from "../../VoipUserMapper";
|
import VoipUserMapper from "../../VoipUserMapper";
|
||||||
@@ -190,7 +188,6 @@ export interface IRoomState {
|
|||||||
*/
|
*/
|
||||||
search?: SearchInfo;
|
search?: SearchInfo;
|
||||||
callState?: CallState;
|
callState?: CallState;
|
||||||
activeCall: Call | null;
|
|
||||||
canPeek: boolean;
|
canPeek: boolean;
|
||||||
canSelfRedact: boolean;
|
canSelfRedact: boolean;
|
||||||
showApps: boolean;
|
showApps: boolean;
|
||||||
@@ -401,7 +398,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
membersLoaded: !llMembers,
|
membersLoaded: !llMembers,
|
||||||
numUnreadMessages: 0,
|
numUnreadMessages: 0,
|
||||||
callState: undefined,
|
callState: undefined,
|
||||||
activeCall: null,
|
|
||||||
canPeek: false,
|
canPeek: false,
|
||||||
canSelfRedact: false,
|
canSelfRedact: false,
|
||||||
showApps: false,
|
showApps: false,
|
||||||
@@ -577,7 +573,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
mainSplitContentType: room ? this.getMainSplitContentType(room) : undefined,
|
mainSplitContentType: room ? this.getMainSplitContentType(room) : undefined,
|
||||||
initialEventId: undefined, // default to clearing this, will get set later in the method if needed
|
initialEventId: undefined, // default to clearing this, will get set later in the method if needed
|
||||||
showRightPanel: roomId ? this.context.rightPanelStore.isOpenForRoom(roomId) : false,
|
showRightPanel: roomId ? this.context.rightPanelStore.isOpenForRoom(roomId) : false,
|
||||||
activeCall: roomId ? CallStore.instance.getActiveCall(roomId) : null,
|
|
||||||
promptAskToJoin: this.context.roomViewStore.promptAskToJoin(),
|
promptAskToJoin: this.context.roomViewStore.promptAskToJoin(),
|
||||||
viewRoomOpts: this.context.roomViewStore.getViewRoomOpts(),
|
viewRoomOpts: this.context.roomViewStore.getViewRoomOpts(),
|
||||||
};
|
};
|
||||||
@@ -727,23 +722,17 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onConnectedCalls = (): void => {
|
private onCallClose = (): void => {
|
||||||
if (this.state.roomId === undefined) return;
|
// Stop viewing the call
|
||||||
const activeCall = CallStore.instance.getActiveCall(this.state.roomId);
|
defaultDispatcher.dispatch<ViewRoomPayload>(
|
||||||
if (activeCall === null) {
|
{
|
||||||
// We disconnected from the call, so stop viewing it
|
action: Action.ViewRoom,
|
||||||
defaultDispatcher.dispatch<ViewRoomPayload>(
|
room_id: this.state.roomId,
|
||||||
{
|
view_call: false,
|
||||||
action: Action.ViewRoom,
|
metricsTrigger: undefined,
|
||||||
room_id: this.state.roomId,
|
},
|
||||||
view_call: false,
|
true,
|
||||||
metricsTrigger: undefined,
|
); // Synchronous so that CallView disappears immediately
|
||||||
},
|
|
||||||
true,
|
|
||||||
); // Synchronous so that CallView disappears immediately
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ activeCall });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private getRoomId = (): string | undefined => {
|
private getRoomId = (): string | undefined => {
|
||||||
@@ -900,8 +889,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
|
WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
|
||||||
this.context.widgetStore.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
|
this.context.widgetStore.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
|
||||||
|
|
||||||
CallStore.instance.on(CallStoreEvent.ConnectedCalls, this.onConnectedCalls);
|
|
||||||
|
|
||||||
this.props.resizeNotifier.on("isResizing", this.onIsResizing);
|
this.props.resizeNotifier.on("isResizing", this.onIsResizing);
|
||||||
|
|
||||||
this.settingWatchers = [
|
this.settingWatchers = [
|
||||||
@@ -1027,7 +1014,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
CallStore.instance.off(CallStoreEvent.ConnectedCalls, this.onConnectedCalls);
|
|
||||||
this.context.legacyCallHandler.off(LegacyCallHandlerEvent.CallState, this.onCallState);
|
this.context.legacyCallHandler.off(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||||
|
|
||||||
// cancel any pending calls to the throttled updated
|
// cancel any pending calls to the throttled updated
|
||||||
@@ -2562,9 +2548,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||||||
<CallView
|
<CallView
|
||||||
room={this.state.room}
|
room={this.state.room}
|
||||||
resizing={this.state.resizing}
|
resizing={this.state.resizing}
|
||||||
waitForCall={isVideoRoom(this.state.room)}
|
|
||||||
skipLobby={this.context.roomViewStore.skipCallLobby() ?? false}
|
skipLobby={this.context.roomViewStore.skipCallLobby() ?? false}
|
||||||
role="main"
|
role="main"
|
||||||
|
onClose={this.onCallClose}
|
||||||
/>
|
/>
|
||||||
{previewBar}
|
{previewBar}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
|
|||||||
<>
|
<>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("action|new_room")}
|
label={_t("action|new_room")}
|
||||||
iconClassName="mx_RoomList_iconNewRoom"
|
iconClassName="mx_LegacyRoomList_iconNewRoom"
|
||||||
onClick={async (e): Promise<void> => {
|
onClick={async (e): Promise<void> => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -132,7 +132,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
|
|||||||
{videoRoomsEnabled && (
|
{videoRoomsEnabled && (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("action|new_video_room")}
|
label={_t("action|new_video_room")}
|
||||||
iconClassName="mx_RoomList_iconNewVideoRoom"
|
iconClassName="mx_LegacyRoomList_iconNewVideoRoom"
|
||||||
onClick={async (e): Promise<void> => {
|
onClick={async (e): Promise<void> => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -157,7 +157,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
|
|||||||
)}
|
)}
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("action|add_existing_room")}
|
label={_t("action|add_existing_room")}
|
||||||
iconClassName="mx_RoomList_iconAddExistingRoom"
|
iconClassName="mx_LegacyRoomList_iconAddExistingRoom"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -168,7 +168,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
|
|||||||
{canCreateSpace && (
|
{canCreateSpace && (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("room_list|add_space_label")}
|
label={_t("room_list|add_space_label")}
|
||||||
iconClassName="mx_RoomList_iconPlus"
|
iconClassName="mx_LegacyRoomList_iconPlus"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ type FlexProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any
|
|||||||
* @default start
|
* @default start
|
||||||
*/
|
*/
|
||||||
justify?: "start" | "center" | "end" | "space-between";
|
justify?: "start" | "center" | "end" | "space-between";
|
||||||
|
/**
|
||||||
|
* The wrapping of the flex children
|
||||||
|
* @default nowrap
|
||||||
|
*/
|
||||||
|
wrap?: "wrap" | "nowrap" | "wrap-reverse";
|
||||||
/**
|
/**
|
||||||
* The spacing between the flex children, expressed with the CSS unit
|
* The spacing between the flex children, expressed with the CSS unit
|
||||||
* @default 0
|
* @default 0
|
||||||
@@ -60,6 +65,7 @@ export function Flex<T extends keyof JSX.IntrinsicElements | JSXElementConstruct
|
|||||||
align = "start",
|
align = "start",
|
||||||
justify = "start",
|
justify = "start",
|
||||||
gap = "0",
|
gap = "0",
|
||||||
|
wrap = "nowrap",
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
@@ -71,8 +77,9 @@ export function Flex<T extends keyof JSX.IntrinsicElements | JSXElementConstruct
|
|||||||
"--mx-flex-align": align,
|
"--mx-flex-align": align,
|
||||||
"--mx-flex-justify": justify,
|
"--mx-flex-justify": justify,
|
||||||
"--mx-flex-gap": gap,
|
"--mx-flex-gap": gap,
|
||||||
|
"--mx-flex-wrap": wrap,
|
||||||
}),
|
}),
|
||||||
[align, direction, display, gap, justify],
|
[align, direction, display, gap, justify, wrap],
|
||||||
);
|
);
|
||||||
|
|
||||||
return React.createElement(as, { ...props, className: classNames("mx_Flex", className), style }, children);
|
return React.createElement(as, { ...props, className: classNames("mx_Flex", className), style }, children);
|
||||||
|
|||||||
66
src/components/viewmodels/roomlist/RoomListViewModel.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
import type { Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||||
|
import dispatcher from "../../../dispatcher/dispatcher";
|
||||||
|
import { Action } from "../../../dispatcher/actions";
|
||||||
|
import { type PrimaryFilter, type SecondaryFilters, useFilteredRooms } from "./useFilteredRooms";
|
||||||
|
|
||||||
|
export interface RoomListViewState {
|
||||||
|
/**
|
||||||
|
* A list of rooms to be displayed in the left panel.
|
||||||
|
*/
|
||||||
|
rooms: Room[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the room having given roomId.
|
||||||
|
*/
|
||||||
|
openRoom: (roomId: string) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of objects that provide the view enough information
|
||||||
|
* to render primary room filters.
|
||||||
|
*/
|
||||||
|
primaryFilters: PrimaryFilter[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function to activate a given secondary filter.
|
||||||
|
*/
|
||||||
|
activateSecondaryFilter: (filter: SecondaryFilters) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently active secondary filter.
|
||||||
|
*/
|
||||||
|
activeSecondaryFilter: SecondaryFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View model for the new room list
|
||||||
|
* @see {@link RoomListViewState} for more information about what this view model returns.
|
||||||
|
*/
|
||||||
|
export function useRoomListViewModel(): RoomListViewState {
|
||||||
|
const { primaryFilters, rooms, activateSecondaryFilter, activeSecondaryFilter } = useFilteredRooms();
|
||||||
|
|
||||||
|
const openRoom = useCallback((roomId: string): void => {
|
||||||
|
dispatcher.dispatch<ViewRoomPayload>({
|
||||||
|
action: Action.ViewRoom,
|
||||||
|
room_id: roomId,
|
||||||
|
metricsTrigger: "RoomList",
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
rooms,
|
||||||
|
openRoom,
|
||||||
|
primaryFilters,
|
||||||
|
activateSecondaryFilter,
|
||||||
|
activeSecondaryFilter,
|
||||||
|
};
|
||||||
|
}
|
||||||
188
src/components/viewmodels/roomlist/useFilteredRooms.tsx
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useCallback, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
import type { Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { FilterKey } from "../../../stores/room-list-v3/skip-list/filters";
|
||||||
|
import { _t, _td, type TranslationKey } from "../../../languageHandler";
|
||||||
|
import RoomListStoreV3 from "../../../stores/room-list-v3/RoomListStoreV3";
|
||||||
|
import { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
|
||||||
|
import { useEventEmitter } from "../../../hooks/useEventEmitter";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides information about a primary filter.
|
||||||
|
* A primary filter is a commonly used filter that is given
|
||||||
|
* more precedence in the UI. For eg, primary filters may be
|
||||||
|
* rendered as pills above the room list.
|
||||||
|
*/
|
||||||
|
export interface PrimaryFilter {
|
||||||
|
// A function to toggle this filter on and off.
|
||||||
|
toggle: () => void;
|
||||||
|
// Whether this filter is currently applied
|
||||||
|
active: boolean;
|
||||||
|
// Text that can be used in the UI to represent this filter.
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FilteredRooms {
|
||||||
|
primaryFilters: PrimaryFilter[];
|
||||||
|
rooms: Room[];
|
||||||
|
activateSecondaryFilter: (filter: SecondaryFilters) => void;
|
||||||
|
activeSecondaryFilter: SecondaryFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterKeyToNameMap: Map<FilterKey, TranslationKey> = new Map([
|
||||||
|
[FilterKey.UnreadFilter, _td("room_list|filters|unread")],
|
||||||
|
[FilterKey.FavouriteFilter, _td("room_list|filters|favourite")],
|
||||||
|
[FilterKey.PeopleFilter, _td("room_list|filters|people")],
|
||||||
|
[FilterKey.RoomsFilter, _td("room_list|filters|rooms")],
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are the secondary filters which are not prominently shown
|
||||||
|
* in the UI.
|
||||||
|
*/
|
||||||
|
export const enum SecondaryFilters {
|
||||||
|
AllActivity,
|
||||||
|
MentionsOnly,
|
||||||
|
InvitesOnly,
|
||||||
|
LowPriority,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map from {@link SecondaryFilters} which the UI understands to
|
||||||
|
* {@link FilterKey} which the store understands.
|
||||||
|
*/
|
||||||
|
const secondaryFiltersToFilterKeyMap = new Map([
|
||||||
|
[SecondaryFilters.AllActivity, undefined],
|
||||||
|
[SecondaryFilters.MentionsOnly, FilterKey.MentionsFilter],
|
||||||
|
[SecondaryFilters.InvitesOnly, FilterKey.InvitesFilter],
|
||||||
|
[SecondaryFilters.LowPriority, FilterKey.LowPriorityFilter],
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this function to determine if a given primary filter is compatible with
|
||||||
|
* a given secondary filter. Practically, this determines whether it makes sense
|
||||||
|
* to expose two filters together in the UI - for eg, it does not make sense to show the
|
||||||
|
* favourite primary filter if the active secondary filter is low priority.
|
||||||
|
* @param primary Primary filter key
|
||||||
|
* @param secondary Secondary filter key
|
||||||
|
* @returns true if compatible, false otherwise
|
||||||
|
*/
|
||||||
|
function isPrimaryFilterCompatible(primary: FilterKey, secondary: FilterKey): boolean {
|
||||||
|
if (secondary === FilterKey.MentionsFilter) {
|
||||||
|
if (primary === FilterKey.UnreadFilter) return false;
|
||||||
|
} else if (secondary === FilterKey.InvitesFilter) {
|
||||||
|
if (primary === FilterKey.UnreadFilter || primary === FilterKey.FavouriteFilter) return false;
|
||||||
|
} else if (secondary === FilterKey.LowPriorityFilter) {
|
||||||
|
if (primary === FilterKey.FavouriteFilter) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track available filters and provide a filtered list of rooms.
|
||||||
|
*/
|
||||||
|
export function useFilteredRooms(): FilteredRooms {
|
||||||
|
/**
|
||||||
|
* Primary filter refers to the pill based filters
|
||||||
|
* rendered above the room list.
|
||||||
|
*/
|
||||||
|
const [primaryFilter, setPrimaryFilter] = useState<FilterKey | undefined>();
|
||||||
|
/**
|
||||||
|
* Secondary filters are also filters but they are hidden
|
||||||
|
* away in a popup menu.
|
||||||
|
*/
|
||||||
|
const [activeSecondaryFilter, setActiveSecondaryFilter] = useState<SecondaryFilters>(SecondaryFilters.AllActivity);
|
||||||
|
|
||||||
|
const secondaryFilter = useMemo(
|
||||||
|
() => secondaryFiltersToFilterKeyMap.get(activeSecondaryFilter),
|
||||||
|
[activeSecondaryFilter],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [rooms, setRooms] = useState(() => RoomListStoreV3.instance.getSortedRoomsInActiveSpace());
|
||||||
|
|
||||||
|
const updateRoomsFromStore = useCallback((filters: FilterKey[] = []): void => {
|
||||||
|
const newRooms = RoomListStoreV3.instance.getSortedRoomsInActiveSpace(filters);
|
||||||
|
setRooms(newRooms);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const filterUndefined = (array: (FilterKey | undefined)[]): FilterKey[] =>
|
||||||
|
array.filter((f) => f !== undefined) as FilterKey[];
|
||||||
|
|
||||||
|
const getAppliedFilters = (): FilterKey[] => {
|
||||||
|
return filterUndefined([primaryFilter, secondaryFilter]);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEventEmitter(RoomListStoreV3.instance, LISTS_UPDATE_EVENT, () => {
|
||||||
|
const filters = getAppliedFilters();
|
||||||
|
updateRoomsFromStore(filters);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Secondary filters are activated using this function.
|
||||||
|
* This is different to how primary filters work because the secondary
|
||||||
|
* filters are static i.e they are always available and don't need to be
|
||||||
|
* hidden.
|
||||||
|
*/
|
||||||
|
const activateSecondaryFilter = useCallback(
|
||||||
|
(filter: SecondaryFilters): void => {
|
||||||
|
// If the filter is already active, just return.
|
||||||
|
if (filter === activeSecondaryFilter) return;
|
||||||
|
|
||||||
|
// SecondaryFilter is an enum for the UI, let's convert it to something
|
||||||
|
// that the store will understand.
|
||||||
|
const secondary = secondaryFiltersToFilterKeyMap.get(filter);
|
||||||
|
|
||||||
|
// Active primary filter may need to be toggled off when applying this secondary filer.
|
||||||
|
let primary = primaryFilter;
|
||||||
|
if (
|
||||||
|
primaryFilter !== undefined &&
|
||||||
|
secondary !== undefined &&
|
||||||
|
!isPrimaryFilterCompatible(primaryFilter, secondary)
|
||||||
|
) {
|
||||||
|
primary = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveSecondaryFilter(filter);
|
||||||
|
setPrimaryFilter(primary);
|
||||||
|
updateRoomsFromStore(filterUndefined([primary, secondary]));
|
||||||
|
},
|
||||||
|
[activeSecondaryFilter, primaryFilter, updateRoomsFromStore],
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tells the view which primary filters are available, how to toggle them
|
||||||
|
* and whether a given primary filter is active. @see {@link PrimaryFilter}
|
||||||
|
*/
|
||||||
|
const primaryFilters = useMemo(() => {
|
||||||
|
const createPrimaryFilter = (key: FilterKey, name: string): PrimaryFilter => {
|
||||||
|
return {
|
||||||
|
toggle: () => {
|
||||||
|
setPrimaryFilter((currentFilter) => {
|
||||||
|
const filter = currentFilter === key ? undefined : key;
|
||||||
|
updateRoomsFromStore(filterUndefined([filter, secondaryFilter]));
|
||||||
|
return filter;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
active: primaryFilter === key,
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const filters: PrimaryFilter[] = [];
|
||||||
|
for (const [key, name] of filterKeyToNameMap.entries()) {
|
||||||
|
if (secondaryFilter && !isPrimaryFilterCompatible(key, secondaryFilter)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
filters.push(createPrimaryFilter(key, _t(name)));
|
||||||
|
}
|
||||||
|
return filters;
|
||||||
|
}, [primaryFilter, updateRoomsFromStore, secondaryFilter]);
|
||||||
|
|
||||||
|
return { primaryFilters, rooms, activateSecondaryFilter, activeSecondaryFilter };
|
||||||
|
}
|
||||||
@@ -9,12 +9,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
|||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { type ReactNode } from "react";
|
||||||
|
import { Link } from "@vector-im/compound-web";
|
||||||
|
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import sendBugReport, { downloadBugReport } from "../../../rageshake/submit-rageshake";
|
import sendBugReport, { downloadBugReport, RageshakeError } from "../../../rageshake/submit-rageshake";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import QuestionDialog from "./QuestionDialog";
|
import QuestionDialog from "./QuestionDialog";
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
@@ -26,7 +27,7 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
|
|||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import { getBrowserSupport } from "../../../SupportedBrowser";
|
import { getBrowserSupport } from "../../../SupportedBrowser";
|
||||||
|
|
||||||
interface IProps {
|
export interface BugReportDialogProps {
|
||||||
onFinished: (success: boolean) => void;
|
onFinished: (success: boolean) => void;
|
||||||
initialText?: string;
|
initialText?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
@@ -36,7 +37,7 @@ interface IProps {
|
|||||||
interface IState {
|
interface IState {
|
||||||
sendLogs: boolean;
|
sendLogs: boolean;
|
||||||
busy: boolean;
|
busy: boolean;
|
||||||
err: string | null;
|
err: ReactNode | null;
|
||||||
issueUrl: string;
|
issueUrl: string;
|
||||||
text: string;
|
text: string;
|
||||||
progress: string | null;
|
progress: string | null;
|
||||||
@@ -44,11 +45,11 @@ interface IState {
|
|||||||
downloadProgress: string | null;
|
downloadProgress: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class BugReportDialog extends React.Component<IProps, IState> {
|
export default class BugReportDialog extends React.Component<BugReportDialogProps, IState> {
|
||||||
private unmounted: boolean;
|
private unmounted: boolean;
|
||||||
private issueRef: React.RefObject<Field>;
|
private issueRef: React.RefObject<Field>;
|
||||||
|
|
||||||
public constructor(props: IProps) {
|
public constructor(props: BugReportDialogProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -89,6 +90,42 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
|
|||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getErrorText(error: Error | RageshakeError): ReactNode {
|
||||||
|
if (error instanceof RageshakeError) {
|
||||||
|
let errorText;
|
||||||
|
switch (error.errorcode) {
|
||||||
|
case "DISALLOWED_APP":
|
||||||
|
errorText = _t("bug_reporting|failed_send_logs_causes|disallowed_app");
|
||||||
|
break;
|
||||||
|
case "REJECTED_BAD_VERSION":
|
||||||
|
errorText = _t("bug_reporting|failed_send_logs_causes|rejected_version");
|
||||||
|
break;
|
||||||
|
case "REJECTED_UNEXPECTED_RECOVERY_KEY":
|
||||||
|
errorText = _t("bug_reporting|failed_send_logs_causes|rejected_recovery_key");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (error.errorcode?.startsWith("REJECTED")) {
|
||||||
|
errorText = _t("bug_reporting|failed_send_logs_causes|rejected_generic");
|
||||||
|
} else {
|
||||||
|
errorText = _t("bug_reporting|failed_send_logs_causes|server_unknown_error");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>{errorText}</p>
|
||||||
|
{error.policyURL && (
|
||||||
|
<Link size="medium" target="_blank" href={error.policyURL}>
|
||||||
|
{_t("action|learn_more")}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <p>{_t("bug_reporting|failed_send_logs_causes|unknown_error")}</p>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private onSubmit = (): void => {
|
private onSubmit = (): void => {
|
||||||
if ((!this.state.text || !this.state.text.trim()) && (!this.state.issueUrl || !this.state.issueUrl.trim())) {
|
if ((!this.state.text || !this.state.text.trim()) && (!this.state.issueUrl || !this.state.issueUrl.trim())) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -126,7 +163,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
|
|||||||
this.setState({
|
this.setState({
|
||||||
busy: false,
|
busy: false,
|
||||||
progress: null,
|
progress: null,
|
||||||
err: _t("bug_reporting|failed_send_logs") + `${err.message}`,
|
err: this.getErrorText(err),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -155,7 +192,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
|
|||||||
this.setState({
|
this.setState({
|
||||||
downloadBusy: false,
|
downloadBusy: false,
|
||||||
downloadProgress:
|
downloadProgress:
|
||||||
_t("bug_reporting|failed_send_logs") + `${err instanceof Error ? err.message : ""}`,
|
_t("bug_reporting|failed_download_logs") + `${err instanceof Error ? err.message : ""}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { EventType } from "matrix-js-sdk/src/matrix";
|
import { EventType } from "matrix-js-sdk/src/matrix";
|
||||||
import React, { useContext, useRef, useState, type MouseEvent, type ReactNode } from "react";
|
import React, { useContext, useRef, useState, type MouseEvent, type ReactNode } from "react";
|
||||||
import { Tooltip } from "@vector-im/compound-web";
|
|
||||||
|
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import { useTimeout } from "../../../hooks/useTimeout";
|
|
||||||
import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds";
|
import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds";
|
||||||
import AccessibleButton from "./AccessibleButton";
|
import AccessibleButton from "./AccessibleButton";
|
||||||
import Spinner from "./Spinner";
|
import Spinner from "./Spinner";
|
||||||
@@ -42,15 +40,6 @@ const MiniAvatarUploader: React.FC<IProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
const [hover, setHover] = useState(false);
|
|
||||||
const [show, setShow] = useState(false);
|
|
||||||
|
|
||||||
useTimeout(() => {
|
|
||||||
setShow(true);
|
|
||||||
}, 3000); // show after 3 seconds
|
|
||||||
useTimeout(() => {
|
|
||||||
setShow(false);
|
|
||||||
}, 13000); // hide after being shown for 10 seconds
|
|
||||||
|
|
||||||
const uploadRef = useRef<HTMLInputElement>(null);
|
const uploadRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
@@ -61,7 +50,6 @@ const MiniAvatarUploader: React.FC<IProps> = ({
|
|||||||
isUserAvatar || room?.currentState?.maySendStateEvent(EventType.RoomAvatar, cli.getSafeUserId());
|
isUserAvatar || room?.currentState?.maySendStateEvent(EventType.RoomAvatar, cli.getSafeUserId());
|
||||||
if (!canSetAvatar) return <React.Fragment>{children}</React.Fragment>;
|
if (!canSetAvatar) return <React.Fragment>{children}</React.Fragment>;
|
||||||
|
|
||||||
const visible = !!label && (hover || show);
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<input
|
<input
|
||||||
@@ -84,24 +72,23 @@ const MiniAvatarUploader: React.FC<IProps> = ({
|
|||||||
accept="image/*"
|
accept="image/*"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Tooltip label={label!} open={visible} onOpenChange={setHover}>
|
<AccessibleButton
|
||||||
<AccessibleButton
|
className={classNames("mx_MiniAvatarUploader", {
|
||||||
className={classNames("mx_MiniAvatarUploader", {
|
mx_MiniAvatarUploader_busy: busy,
|
||||||
mx_MiniAvatarUploader_busy: busy,
|
mx_MiniAvatarUploader_hasAvatar: hasAvatar,
|
||||||
mx_MiniAvatarUploader_hasAvatar: hasAvatar,
|
})}
|
||||||
})}
|
disabled={busy}
|
||||||
disabled={busy}
|
onClick={() => {
|
||||||
onClick={() => {
|
uploadRef.current?.click();
|
||||||
uploadRef.current?.click();
|
}}
|
||||||
}}
|
aria-label={label}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
<div className="mx_MiniAvatarUploader_indicator">
|
<div className="mx_MiniAvatarUploader_indicator">
|
||||||
{busy ? <Spinner w={20} h={20} /> : <div className="mx_MiniAvatarUploader_cameraIcon" />}
|
{busy ? <Spinner w={20} h={20} /> : <div className="mx_MiniAvatarUploader_cameraIcon" />}
|
||||||
</div>
|
</div>
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</Tooltip>
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -126,10 +126,6 @@ const ActiveLoadedCallEvent = forwardRef<any, ActiveLoadedCallEventProps>(({ mxE
|
|||||||
return [_t("action|leave"), "danger", disconnect];
|
return [_t("action|leave"), "danger", disconnect];
|
||||||
case ConnectionState.Disconnecting:
|
case ConnectionState.Disconnecting:
|
||||||
return [_t("action|leave"), "danger", null];
|
return [_t("action|leave"), "danger", null];
|
||||||
case ConnectionState.Connecting:
|
|
||||||
case ConnectionState.Lobby:
|
|
||||||
case ConnectionState.WidgetLoading:
|
|
||||||
return [_t("action|join"), "primary", null];
|
|
||||||
}
|
}
|
||||||
}, [connectionState, connect, disconnect]);
|
}, [connectionState, connect, disconnect]);
|
||||||
|
|
||||||
|
|||||||
@@ -441,6 +441,7 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
|||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
className="mx_RoomSummaryCard_leave"
|
||||||
Icon={LeaveIcon}
|
Icon={LeaveIcon}
|
||||||
kind="critical"
|
kind="critical"
|
||||||
label={_t("action|leave_room")}
|
label={_t("action|leave_room")}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
|
|||||||
{showCreateRooms && (
|
{showCreateRooms && (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("action|start_new_chat")}
|
label={_t("action|start_new_chat")}
|
||||||
iconClassName="mx_RoomList_iconStartChat"
|
iconClassName="mx_LegacyRoomList_iconStartChat"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -158,7 +158,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
|
|||||||
{showInviteUsers && (
|
{showInviteUsers && (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("action|invite_to_space")}
|
label={_t("action|invite_to_space")}
|
||||||
iconClassName="mx_RoomList_iconInvite"
|
iconClassName="mx_LegacyRoomList_iconInvite"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -230,7 +230,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
|||||||
<IconizedContextMenuOptionList first>
|
<IconizedContextMenuOptionList first>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("action|explore_rooms")}
|
label={_t("action|explore_rooms")}
|
||||||
iconClassName="mx_RoomList_iconExplore"
|
iconClassName="mx_LegacyRoomList_iconExplore"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -247,7 +247,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
|||||||
<>
|
<>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("action|new_room")}
|
label={_t("action|new_room")}
|
||||||
iconClassName="mx_RoomList_iconNewRoom"
|
iconClassName="mx_LegacyRoomList_iconNewRoom"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -261,7 +261,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
|||||||
{videoRoomsEnabled && (
|
{videoRoomsEnabled && (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("action|new_video_room")}
|
label={_t("action|new_video_room")}
|
||||||
iconClassName="mx_RoomList_iconNewVideoRoom"
|
iconClassName="mx_LegacyRoomList_iconNewVideoRoom"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -279,7 +279,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
|||||||
)}
|
)}
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("action|add_existing_room")}
|
label={_t("action|add_existing_room")}
|
||||||
iconClassName="mx_RoomList_iconAddExistingRoom"
|
iconClassName="mx_LegacyRoomList_iconAddExistingRoom"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -300,7 +300,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
|||||||
<>
|
<>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("action|new_room")}
|
label={_t("action|new_room")}
|
||||||
iconClassName="mx_RoomList_iconNewRoom"
|
iconClassName="mx_LegacyRoomList_iconNewRoom"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -312,7 +312,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
|||||||
{videoRoomsEnabled && (
|
{videoRoomsEnabled && (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("action|new_video_room")}
|
label={_t("action|new_video_room")}
|
||||||
iconClassName="mx_RoomList_iconNewVideoRoom"
|
iconClassName="mx_LegacyRoomList_iconNewVideoRoom"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -333,7 +333,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
|||||||
{showExploreRooms ? (
|
{showExploreRooms ? (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("action|explore_public_rooms")}
|
label={_t("action|explore_public_rooms")}
|
||||||
iconClassName="mx_RoomList_iconExplore"
|
iconClassName="mx_LegacyRoomList_iconExplore"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -678,7 +678,7 @@ export default class LegacyRoomList extends React.PureComponent<IProps, IState>
|
|||||||
}
|
}
|
||||||
onKeyDownHandler(ev);
|
onKeyDownHandler(ev);
|
||||||
}}
|
}}
|
||||||
className="mx_RoomList"
|
className="mx_LegacyRoomList"
|
||||||
role="tree"
|
role="tree"
|
||||||
aria-label={_t("common|rooms")}
|
aria-label={_t("common|rooms")}
|
||||||
ref={this.treeRef}
|
ref={this.treeRef}
|
||||||
|
|||||||
51
src/components/views/rooms/RoomListPanel/RoomList.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useCallback, type JSX } from "react";
|
||||||
|
import { AutoSizer, List, type ListRowProps } from "react-virtualized";
|
||||||
|
|
||||||
|
import { type RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel";
|
||||||
|
import { _t } from "../../../../languageHandler";
|
||||||
|
import { RoomListCell } from "./RoomListCell";
|
||||||
|
|
||||||
|
interface RoomListProps {
|
||||||
|
/**
|
||||||
|
* The view model state for the room list.
|
||||||
|
*/
|
||||||
|
vm: RoomListViewState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A virtualized list of rooms.
|
||||||
|
*/
|
||||||
|
export function RoomList({ vm: { rooms, openRoom } }: RoomListProps): JSX.Element {
|
||||||
|
const roomRendererMemoized = useCallback(
|
||||||
|
({ key, index, style }: ListRowProps) => (
|
||||||
|
<RoomListCell room={rooms[index]} key={key} style={style} onClick={() => openRoom(rooms[index].roomId)} />
|
||||||
|
),
|
||||||
|
[rooms, openRoom],
|
||||||
|
);
|
||||||
|
|
||||||
|
// The first div is needed to make the virtualized list take all the remaining space and scroll correctly
|
||||||
|
return (
|
||||||
|
<div className="mx_RoomList" data-testid="room-list">
|
||||||
|
<AutoSizer>
|
||||||
|
{({ height, width }) => (
|
||||||
|
<List
|
||||||
|
aria-label={_t("room_list|list_title")}
|
||||||
|
className="mx_RoomList_List"
|
||||||
|
rowRenderer={roomRendererMemoized}
|
||||||
|
rowCount={rooms.length}
|
||||||
|
rowHeight={48}
|
||||||
|
height={height}
|
||||||
|
width={width}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AutoSizer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
44
src/components/views/rooms/RoomListPanel/RoomListCell.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { type JSX } from "react";
|
||||||
|
import { type Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
import { _t } from "../../../../languageHandler";
|
||||||
|
import { Flex } from "../../../utils/Flex";
|
||||||
|
import DecoratedRoomAvatar from "../../avatars/DecoratedRoomAvatar";
|
||||||
|
|
||||||
|
interface RoomListCellProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||||
|
/**
|
||||||
|
* The room to display
|
||||||
|
*/
|
||||||
|
room: Room;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cell in the room list
|
||||||
|
*/
|
||||||
|
export function RoomListCell({ room, ...props }: RoomListCellProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="mx_RoomListCell"
|
||||||
|
type="button"
|
||||||
|
aria-label={_t("room_list|room|open_room", { roomName: room.name })}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{/* We need this extra div between the button and the content in order to add a padding which is not messing with the virtualized list */}
|
||||||
|
<Flex className="mx_RoomListCell_container" gap="var(--cpd-space-3x)" align="center">
|
||||||
|
<DecoratedRoomAvatar room={room} size="32px" />
|
||||||
|
<Flex className="mx_RoomListCell_content" align="center">
|
||||||
|
{/* We truncate the room name when too long. Title here is to show the full name on hover */}
|
||||||
|
<span title={room.name}>{room.name}</span>
|
||||||
|
{/* Future hover menu et notification badges */}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -38,8 +38,8 @@ export function RoomListHeaderView(): JSX.Element {
|
|||||||
align="center"
|
align="center"
|
||||||
data-testid="room-list-header"
|
data-testid="room-list-header"
|
||||||
>
|
>
|
||||||
<Flex align="center" gap="var(--cpd-space-1x)">
|
<Flex className="mx_RoomListHeaderView_title" align="center" gap="var(--cpd-space-1x)">
|
||||||
<h1>{vm.title}</h1>
|
<h1 title={vm.title}>{vm.title}</h1>
|
||||||
{vm.displaySpaceMenu && <SpaceMenu vm={vm} />}
|
{vm.displaySpaceMenu && <SpaceMenu vm={vm} />}
|
||||||
</Flex>
|
</Flex>
|
||||||
{vm.displayComposeMenu && <ComposeMenu vm={vm} />}
|
{vm.displayComposeMenu && <ComposeMenu vm={vm} />}
|
||||||
@@ -11,8 +11,10 @@ import { shouldShowComponent } from "../../../../customisations/helpers/UICompon
|
|||||||
import { UIComponent } from "../../../../settings/UIFeature";
|
import { UIComponent } from "../../../../settings/UIFeature";
|
||||||
import { RoomListSearch } from "./RoomListSearch";
|
import { RoomListSearch } from "./RoomListSearch";
|
||||||
import { RoomListHeaderView } from "./RoomListHeaderView";
|
import { RoomListHeaderView } from "./RoomListHeaderView";
|
||||||
|
import { RoomListView } from "./RoomListView";
|
||||||
|
import { Flex } from "../../../utils/Flex";
|
||||||
|
|
||||||
type RoomListViewProps = {
|
type RoomListPanelProps = {
|
||||||
/**
|
/**
|
||||||
* Current active space
|
* Current active space
|
||||||
* See {@link RoomListSearch}
|
* See {@link RoomListSearch}
|
||||||
@@ -21,15 +23,22 @@ type RoomListViewProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A view component for the room list.
|
* The panel of the room list
|
||||||
*/
|
*/
|
||||||
export const RoomListView: React.FC<RoomListViewProps> = ({ activeSpace }) => {
|
export const RoomListPanel: React.FC<RoomListPanelProps> = ({ activeSpace }) => {
|
||||||
const displayRoomSearch = shouldShowComponent(UIComponent.FilterContainer);
|
const displayRoomSearch = shouldShowComponent(UIComponent.FilterContainer);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="mx_RoomListView" data-testid="room-list-view">
|
<Flex
|
||||||
|
as="section"
|
||||||
|
className="mx_RoomListPanel"
|
||||||
|
data-testid="room-list-panel"
|
||||||
|
direction="column"
|
||||||
|
align="stretch"
|
||||||
|
>
|
||||||
{displayRoomSearch && <RoomListSearch activeSpace={activeSpace} />}
|
{displayRoomSearch && <RoomListSearch activeSpace={activeSpace} />}
|
||||||
<RoomListHeaderView />
|
<RoomListHeaderView />
|
||||||
</section>
|
<RoomListView />
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { type JSX } from "react";
|
||||||
|
import { ChatFilter } from "@vector-im/compound-web";
|
||||||
|
|
||||||
|
import type { RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel";
|
||||||
|
import { Flex } from "../../../utils/Flex";
|
||||||
|
import { _t } from "../../../../languageHandler";
|
||||||
|
|
||||||
|
interface RoomListPrimaryFiltersProps {
|
||||||
|
/**
|
||||||
|
* The view model for the room list
|
||||||
|
*/
|
||||||
|
vm: RoomListViewState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The primary filters for the room list
|
||||||
|
*/
|
||||||
|
export function RoomListPrimaryFilters({ vm }: RoomListPrimaryFiltersProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
as="ul"
|
||||||
|
role="listbox"
|
||||||
|
aria-label={_t("room_list|primary_filters")}
|
||||||
|
className="mx_RoomListPrimaryFilters"
|
||||||
|
align="center"
|
||||||
|
gap="var(--cpd-space-2x)"
|
||||||
|
wrap="wrap"
|
||||||
|
>
|
||||||
|
{vm.primaryFilters.map((filter) => (
|
||||||
|
<li role="option" aria-selected={filter.active} key={filter.name}>
|
||||||
|
<ChatFilter selected={filter.active} onClick={filter.toggle}>
|
||||||
|
{filter.name}
|
||||||
|
</ChatFilter>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
25
src/components/views/rooms/RoomListPanel/RoomListView.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { type JSX } from "react";
|
||||||
|
|
||||||
|
import { useRoomListViewModel } from "../../../viewmodels/roomlist/RoomListViewModel";
|
||||||
|
import { RoomList } from "./RoomList";
|
||||||
|
import { RoomListPrimaryFilters } from "./RoomListPrimaryFilters";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Host the room list and the (future) room filters
|
||||||
|
*/
|
||||||
|
export function RoomListView(): JSX.Element {
|
||||||
|
const vm = useRoomListViewModel();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<RoomListPrimaryFilters vm={vm} />
|
||||||
|
<RoomList vm={vm} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,4 +5,4 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { RoomListView } from "./RoomListView";
|
export { RoomListPanel } from "./RoomListPanel";
|
||||||
@@ -27,18 +27,6 @@ export const RoomTileCallSummary: FC<Props> = ({ call }) => {
|
|||||||
text = _t("common|video");
|
text = _t("common|video");
|
||||||
active = false;
|
active = false;
|
||||||
break;
|
break;
|
||||||
case ConnectionState.WidgetLoading:
|
|
||||||
text = _t("common|loading");
|
|
||||||
active = false;
|
|
||||||
break;
|
|
||||||
case ConnectionState.Lobby:
|
|
||||||
text = _t("common|lobby");
|
|
||||||
active = false;
|
|
||||||
break;
|
|
||||||
case ConnectionState.Connecting:
|
|
||||||
text = _t("room|joining");
|
|
||||||
active = true;
|
|
||||||
break;
|
|
||||||
case ConnectionState.Connected:
|
case ConnectionState.Connected:
|
||||||
case ConnectionState.Disconnecting:
|
case ConnectionState.Disconnecting:
|
||||||
text = _t("common|joined");
|
text = _t("common|joined");
|
||||||
|
|||||||
@@ -95,16 +95,9 @@ export function attachMentions(
|
|||||||
const userMentions = new Set<string>();
|
const userMentions = new Set<string>();
|
||||||
let roomMention = false;
|
let roomMention = false;
|
||||||
|
|
||||||
// If there's a reply, initialize the mentioned users as the sender of that
|
// If there's a reply, initialize the mentioned users as the sender of that event.
|
||||||
// event + any mentioned users in that event.
|
|
||||||
if (replyToEvent) {
|
if (replyToEvent) {
|
||||||
userMentions.add(replyToEvent.sender!.userId);
|
userMentions.add(replyToEvent.sender!.userId);
|
||||||
// TODO What do we do if the reply event *doeesn't* have this property?
|
|
||||||
// Try to fish out replies from the contents?
|
|
||||||
const userIds = replyToEvent.getContent()["m.mentions"]?.user_ids;
|
|
||||||
if (Array.isArray(userIds)) {
|
|
||||||
userIds.forEach((userId) => userMentions.add(userId));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If user provided content is available, check to see if any users are mentioned.
|
// If user provided content is available, check to see if any users are mentioned.
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ interface EditorProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Editor = memo(
|
export const Editor = memo(
|
||||||
forwardRef<HTMLDivElement, EditorProps>(function Editor(
|
forwardRef<HTMLDivElement | null, EditorProps>(function Editor(
|
||||||
{ disabled, placeholder, leftComponent, rightComponent }: EditorProps,
|
{ disabled, placeholder, leftComponent, rightComponent }: EditorProps,
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { type RefObject, useEffect } from "react";
|
|||||||
|
|
||||||
import { setCursorPositionAtTheEnd } from "./utils";
|
import { setCursorPositionAtTheEnd } from "./utils";
|
||||||
|
|
||||||
export function useSetCursorPosition(disabled: boolean, ref: RefObject<HTMLElement>): void {
|
export function useSetCursorPosition(disabled: boolean, ref: RefObject<HTMLDivElement | null>): void {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ref.current && !disabled) {
|
if (ref.current && !disabled) {
|
||||||
setCursorPositionAtTheEnd(ref.current);
|
setCursorPositionAtTheEnd(ref.current);
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { type EmptyObject } from "matrix-js-sdk/src/matrix";
|
import { type EmptyObject } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { Root, InlineField, Label, ToggleInput } from "@vector-im/compound-web";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
|
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
|
||||||
import { type IntegrationManagerInstance } from "../../../integrations/IntegrationManagerInstance";
|
import { type IntegrationManagerInstance } from "../../../integrations/IntegrationManagerInstance";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||||
import ToggleSwitch from "../elements/ToggleSwitch";
|
|
||||||
import Heading from "../typography/Heading";
|
import Heading from "../typography/Heading";
|
||||||
import { SettingsSubsectionText } from "./shared/SettingsSubsection";
|
import { SettingsSubsectionText } from "./shared/SettingsSubsection";
|
||||||
import { UIFeature } from "../../../settings/UIFeature";
|
import { UIFeature } from "../../../settings/UIFeature";
|
||||||
@@ -66,26 +66,33 @@ export default class SetIntegrationManager extends React.Component<EmptyObject,
|
|||||||
if (!SettingsStore.getValue(UIFeature.Widgets)) return null;
|
if (!SettingsStore.getValue(UIFeature.Widgets)) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label
|
<div className="mx_SetIntegrationManager" data-testid="mx_SetIntegrationManager">
|
||||||
className="mx_SetIntegrationManager"
|
|
||||||
data-testid="mx_SetIntegrationManager"
|
|
||||||
htmlFor="toggle_integration"
|
|
||||||
>
|
|
||||||
<div className="mx_SettingsFlag">
|
<div className="mx_SettingsFlag">
|
||||||
<div className="mx_SetIntegrationManager_heading_manager">
|
<div className="mx_SetIntegrationManager_heading_manager">
|
||||||
<Heading size="3">{_t("integration_manager|manage_title")}</Heading>
|
<Heading size="3">{_t("integration_manager|manage_title")}</Heading>
|
||||||
<Heading size="4">{managerName}</Heading>
|
<Heading size="4">{managerName}</Heading>
|
||||||
</div>
|
</div>
|
||||||
<ToggleSwitch
|
|
||||||
id="toggle_integration"
|
|
||||||
checked={this.state.provisioningEnabled}
|
|
||||||
disabled={false}
|
|
||||||
onChange={this.onProvisioningToggled}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<SettingsSubsectionText>{bodyText}</SettingsSubsectionText>
|
<SettingsSubsectionText>{bodyText}</SettingsSubsectionText>
|
||||||
<SettingsSubsectionText>{_t("integration_manager|explainer")}</SettingsSubsectionText>
|
<SettingsSubsectionText>{_t("integration_manager|explainer")}</SettingsSubsectionText>
|
||||||
</label>
|
<Root>
|
||||||
|
<InlineField
|
||||||
|
name="enable_im"
|
||||||
|
control={
|
||||||
|
<ToggleInput
|
||||||
|
role="switch"
|
||||||
|
id="mx_SetIntegrationManager_Toggle"
|
||||||
|
checked={this.state.provisioningEnabled}
|
||||||
|
onChange={this.onProvisioningToggled}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Label htmlFor="mx_SetIntegrationManager_Toggle">
|
||||||
|
{_t("integration_manager|toggle_label")}
|
||||||
|
</Label>
|
||||||
|
</InlineField>
|
||||||
|
</Root>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Breadcrumb, Button, VisualList, VisualListItem } from "@vector-im/compound-web";
|
import { Breadcrumb, Button, InlineSpinner, VisualList, VisualListItem } from "@vector-im/compound-web";
|
||||||
import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
|
import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
|
||||||
import InfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info";
|
import InfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info";
|
||||||
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
|
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
|
||||||
import React, { type MouseEventHandler } from "react";
|
import React, { useState, type MouseEventHandler } from "react";
|
||||||
|
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
import { EncryptionCard } from "./EncryptionCard";
|
import { EncryptionCard } from "./EncryptionCard";
|
||||||
@@ -44,6 +44,10 @@ interface ResetIdentityPanelProps {
|
|||||||
export function ResetIdentityPanel({ onCancelClick, onFinish, variant }: ResetIdentityPanelProps): JSX.Element {
|
export function ResetIdentityPanel({ onCancelClick, onFinish, variant }: ResetIdentityPanelProps): JSX.Element {
|
||||||
const matrixClient = useMatrixClientContext();
|
const matrixClient = useMatrixClientContext();
|
||||||
|
|
||||||
|
// After the user clicks "Continue", we disable the button so it can't be
|
||||||
|
// clicked again, and warn the user not to close the window.
|
||||||
|
const [inProgress, setInProgress] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
@@ -78,18 +82,34 @@ export function ResetIdentityPanel({ onCancelClick, onFinish, variant }: ResetId
|
|||||||
<EncryptionCardButtons>
|
<EncryptionCardButtons>
|
||||||
<Button
|
<Button
|
||||||
destructive={true}
|
destructive={true}
|
||||||
|
disabled={inProgress}
|
||||||
onClick={async (evt) => {
|
onClick={async (evt) => {
|
||||||
|
setInProgress(true);
|
||||||
await matrixClient
|
await matrixClient
|
||||||
.getCrypto()
|
.getCrypto()
|
||||||
?.resetEncryption((makeRequest) => uiAuthCallback(matrixClient, makeRequest));
|
?.resetEncryption((makeRequest) => uiAuthCallback(matrixClient, makeRequest));
|
||||||
onFinish(evt);
|
onFinish(evt);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{_t("action|continue")}
|
{inProgress ? (
|
||||||
</Button>
|
<>
|
||||||
<Button kind="tertiary" onClick={onCancelClick}>
|
<InlineSpinner /> {_t("settings|encryption|advanced|reset_in_progress")}
|
||||||
{_t("action|cancel")}
|
</>
|
||||||
|
) : (
|
||||||
|
_t("action|continue")
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
{inProgress ? (
|
||||||
|
<EncryptionCardEmphasisedContent>
|
||||||
|
<span className="mx_ResetIdentityPanel_warning">
|
||||||
|
{_t("settings|encryption|advanced|do_not_close_warning")}
|
||||||
|
</span>
|
||||||
|
</EncryptionCardEmphasisedContent>
|
||||||
|
) : (
|
||||||
|
<Button kind="tertiary" onClick={onCancelClick}>
|
||||||
|
{_t("action|cancel")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</EncryptionCardButtons>
|
</EncryptionCardButtons>
|
||||||
</EncryptionCard>
|
</EncryptionCard>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ const QuickSettingsButton: React.FC<{
|
|||||||
wrapperClassName={classNames("mx_QuickSettingsButton_ContextMenuWrapper", {
|
wrapperClassName={classNames("mx_QuickSettingsButton_ContextMenuWrapper", {
|
||||||
mx_QuickSettingsButton_ContextMenuWrapper_new_room_list: newRoomListEnabled,
|
mx_QuickSettingsButton_ContextMenuWrapper_new_room_list: newRoomListEnabled,
|
||||||
})}
|
})}
|
||||||
|
// Eventually replace with a properly aria-labelled menu
|
||||||
|
data-testid="quick-settings-menu"
|
||||||
onFinished={closeMenu}
|
onFinished={closeMenu}
|
||||||
managed={false}
|
managed={false}
|
||||||
focusLock={true}
|
focusLock={true}
|
||||||
|
|||||||
@@ -165,12 +165,12 @@ export default class VerificationShowSas extends React.Component<IProps, IState>
|
|||||||
} else {
|
} else {
|
||||||
confirm = (
|
confirm = (
|
||||||
<div className="mx_VerificationShowSas_buttonRow">
|
<div className="mx_VerificationShowSas_buttonRow">
|
||||||
<AccessibleButton onClick={this.onDontMatchClick} kind="danger">
|
|
||||||
{_t("encryption|verification|sas_no_match")}
|
|
||||||
</AccessibleButton>
|
|
||||||
<AccessibleButton onClick={this.onMatchClick} kind="primary">
|
<AccessibleButton onClick={this.onMatchClick} kind="primary">
|
||||||
{_t("encryption|verification|sas_match")}
|
{_t("encryption|verification|sas_match")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
<AccessibleButton onClick={this.onDontMatchClick} kind="secondary">
|
||||||
|
{_t("encryption|verification|sas_no_match")}
|
||||||
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
import React, { type FC, useContext, useEffect, type AriaRole, useCallback } from "react";
|
import React, { type FC, useContext, useEffect, type AriaRole, useCallback } from "react";
|
||||||
|
|
||||||
import type { Room } from "matrix-js-sdk/src/matrix";
|
import type { Room } from "matrix-js-sdk/src/matrix";
|
||||||
import { type Call, ConnectionState, ElementCall } from "../../../models/Call";
|
import { type Call, CallEvent } from "../../../models/Call";
|
||||||
import { useCall } from "../../../hooks/useCall";
|
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import AppTile from "../elements/AppTile";
|
import AppTile from "../elements/AppTile";
|
||||||
import { CallStore } from "../../../stores/CallStore";
|
import { CallStore } from "../../../stores/CallStore";
|
||||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||||
|
import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
|
||||||
|
import { useCall } from "../../../hooks/useCall";
|
||||||
|
|
||||||
interface JoinCallViewProps {
|
interface JoinCallViewProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
@@ -22,10 +23,12 @@ interface JoinCallViewProps {
|
|||||||
call: Call;
|
call: Call;
|
||||||
skipLobby?: boolean;
|
skipLobby?: boolean;
|
||||||
role?: AriaRole;
|
role?: AriaRole;
|
||||||
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby, role }) => {
|
const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby, role, onClose }) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
useTypedEventEmitter(call, CallEvent.Close, onClose);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// We'll take this opportunity to tidy up our room state
|
// We'll take this opportunity to tidy up our room state
|
||||||
@@ -38,17 +41,6 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
|
|||||||
call.widget.data.skipLobby = skipLobby;
|
call.widget.data.skipLobby = skipLobby;
|
||||||
}, [call.widget, skipLobby]);
|
}, [call.widget, skipLobby]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (call.connectionState === ConnectionState.Disconnected) {
|
|
||||||
// immediately start the call
|
|
||||||
// (this will start the lobby view in the widget and connect to all required widget events)
|
|
||||||
call.start();
|
|
||||||
}
|
|
||||||
return (): void => {
|
|
||||||
// If we are connected the widget is sticky and we do not want to destroy the call.
|
|
||||||
if (!call.connected) call.destroy();
|
|
||||||
};
|
|
||||||
}, [call]);
|
|
||||||
const disconnectAllOtherCalls: () => Promise<void> = useCallback(async () => {
|
const disconnectAllOtherCalls: () => Promise<void> = useCallback(async () => {
|
||||||
// The stickyPromise has to resolve before the widget actually becomes sticky.
|
// The stickyPromise has to resolve before the widget actually becomes sticky.
|
||||||
// We only let the widget become sticky after disconnecting all other active calls.
|
// We only let the widget become sticky after disconnecting all other active calls.
|
||||||
@@ -57,6 +49,7 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
|
|||||||
);
|
);
|
||||||
await Promise.all(calls.map(async (call) => await call.disconnect()));
|
await Promise.all(calls.map(async (call) => await call.disconnect()));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_CallView" role={role}>
|
<div className="mx_CallView" role={role}>
|
||||||
<AppTile
|
<AppTile
|
||||||
@@ -76,26 +69,27 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
|
|||||||
interface CallViewProps {
|
interface CallViewProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
resizing: boolean;
|
resizing: boolean;
|
||||||
/**
|
|
||||||
* If true, the view will be blank until a call appears. Otherwise, the join
|
|
||||||
* button will create a call if there isn't already one.
|
|
||||||
*/
|
|
||||||
waitForCall: boolean;
|
|
||||||
skipLobby?: boolean;
|
skipLobby?: boolean;
|
||||||
role?: AriaRole;
|
role?: AriaRole;
|
||||||
|
/**
|
||||||
|
* Callback for when the user closes the call.
|
||||||
|
*/
|
||||||
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CallView: FC<CallViewProps> = ({ room, resizing, waitForCall, skipLobby, role }) => {
|
export const CallView: FC<CallViewProps> = ({ room, resizing, skipLobby, role, onClose }) => {
|
||||||
const call = useCall(room.roomId);
|
const call = useCall(room.roomId);
|
||||||
|
|
||||||
useEffect(() => {
|
return (
|
||||||
if (call === null && !waitForCall) {
|
call && (
|
||||||
ElementCall.create(room, skipLobby);
|
<JoinCallView
|
||||||
}
|
room={room}
|
||||||
}, [call, room, skipLobby, waitForCall]);
|
resizing={resizing}
|
||||||
if (call === null) {
|
call={call}
|
||||||
return null;
|
skipLobby={skipLobby}
|
||||||
} else {
|
role={role}
|
||||||
return <JoinCallView room={room} resizing={resizing} call={call} skipLobby={skipLobby} role={role} />;
|
onClose={onClose}
|
||||||
}
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ const RoomContext = createContext<
|
|||||||
threadId: undefined,
|
threadId: undefined,
|
||||||
liveTimeline: undefined,
|
liveTimeline: undefined,
|
||||||
narrow: false,
|
narrow: false,
|
||||||
activeCall: null,
|
|
||||||
msc3946ProcessDynamicPredecessor: false,
|
msc3946ProcessDynamicPredecessor: false,
|
||||||
canAskToJoin: false,
|
canAskToJoin: false,
|
||||||
promptAskToJoin: false,
|
promptAskToJoin: false,
|
||||||
|
|||||||
@@ -343,7 +343,7 @@ export default async function createRoom(client: MatrixClient, opts: IOpts): Pro
|
|||||||
await client.setPowerLevel(roomId, client.getUserId()!, 100);
|
await client.setPowerLevel(roomId, client.getUserId()!, 100);
|
||||||
} else if (opts.roomType === RoomType.UnstableCall) {
|
} else if (opts.roomType === RoomType.UnstableCall) {
|
||||||
// Set up this video room with an Element call
|
// Set up this video room with an Element call
|
||||||
await ElementCall.create(await room);
|
ElementCall.create(await room);
|
||||||
|
|
||||||
// Reset our power level back to admin so that the call becomes immutable
|
// Reset our power level back to admin so that the call becomes immutable
|
||||||
await client.setPowerLevel(roomId, client.getUserId()!, 100);
|
await client.setPowerLevel(roomId, client.getUserId()!, 100);
|
||||||
|
|||||||
@@ -75,9 +75,5 @@ export const useFull = (call: Call | null): boolean => {
|
|||||||
|
|
||||||
export const useJoinCallButtonDisabledTooltip = (call: Call | null): string | null => {
|
export const useJoinCallButtonDisabledTooltip = (call: Call | null): string | null => {
|
||||||
const isFull = useFull(call);
|
const isFull = useFull(call);
|
||||||
const state = useConnectionState(call);
|
return isFull ? _t("voip|join_button_tooltip_call_full") : null;
|
||||||
|
|
||||||
if (state === ConnectionState.Connecting) return _t("voip|join_button_tooltip_connecting");
|
|
||||||
if (isFull) return _t("voip|join_button_tooltip_call_full");
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -80,12 +80,14 @@
|
|||||||
"maximise": "Maximalizovat",
|
"maximise": "Maximalizovat",
|
||||||
"mention": "Zmínit",
|
"mention": "Zmínit",
|
||||||
"minimise": "Minimalizovat",
|
"minimise": "Minimalizovat",
|
||||||
|
"new_message": "Nová zpráva",
|
||||||
"new_room": "Nová místnost",
|
"new_room": "Nová místnost",
|
||||||
"new_video_room": "Nová video místnost",
|
"new_video_room": "Nová video místnost",
|
||||||
"next": "Další",
|
"next": "Další",
|
||||||
"no": "Ne",
|
"no": "Ne",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"open": "Otevřít",
|
"open": "Otevřít",
|
||||||
|
"open_menu": "Otevřít nabídku",
|
||||||
"pause": "Pozastavit",
|
"pause": "Pozastavit",
|
||||||
"pin": "Připnout",
|
"pin": "Připnout",
|
||||||
"play": "Přehrát",
|
"play": "Přehrát",
|
||||||
@@ -405,7 +407,15 @@
|
|||||||
"download_logs": "Stáhnout záznamy",
|
"download_logs": "Stáhnout záznamy",
|
||||||
"downloading_logs": "Stahování záznamů",
|
"downloading_logs": "Stahování záznamů",
|
||||||
"error_empty": "Dejte nám vědět, prosím, co se pokazilo nebo vytvořte issue na GitHubu, kde problém popište.",
|
"error_empty": "Dejte nám vědět, prosím, co se pokazilo nebo vytvořte issue na GitHubu, kde problém popište.",
|
||||||
"failed_send_logs": "Nepodařilo se odeslat záznamy: ",
|
"failed_download_logs": "Stažení ladících protokolů se nezdařilo: ",
|
||||||
|
"failed_send_logs_causes": {
|
||||||
|
"disallowed_app": "Vaše hlášení o chybě bylo zamítnuto. Rageshake server tuto aplikaci nepodporuje.",
|
||||||
|
"rejected_generic": "Vaše hlášení o chybě bylo zamítnuto. Rageshake server odmítl obsah zprávy kvůli zásadám.",
|
||||||
|
"rejected_recovery_key": "Vaše hlášení o chybě bylo z bezpečnostních důvodů odmítnuto, protože obsahovalo klíč pro obnovení.",
|
||||||
|
"rejected_version": "Vaše hlášení o chybě bylo zamítnuto, protože verze, kterou používáte, je příliš stará.",
|
||||||
|
"server_unknown_error": "Rageshake server narazil na neznámou chybu a nemohl zpracovat zprávu.",
|
||||||
|
"unknown_error": "Odeslání protokolů se nezdařilo."
|
||||||
|
},
|
||||||
"github_issue": "issue na GitHubu",
|
"github_issue": "issue na GitHubu",
|
||||||
"introduction": "Pokud jste odeslali chybu prostřednictvím GitHubu, ladící protokoly nám mohou pomoci problém vysledovat. ",
|
"introduction": "Pokud jste odeslali chybu prostřednictvím GitHubu, ladící protokoly nám mohou pomoci problém vysledovat. ",
|
||||||
"log_request": "Abychom tomu mohli pro příště předejít, <a>pošlete nám prosím záznamy</a>.",
|
"log_request": "Abychom tomu mohli pro příště předejít, <a>pošlete nám prosím záznamy</a>.",
|
||||||
@@ -495,7 +505,6 @@
|
|||||||
"legal": "Právní informace",
|
"legal": "Právní informace",
|
||||||
"light": "Světlý",
|
"light": "Světlý",
|
||||||
"loading": "Načítání…",
|
"loading": "Načítání…",
|
||||||
"lobby": "Předsálí",
|
|
||||||
"location": "Poloha",
|
"location": "Poloha",
|
||||||
"low_priority": "Nízká priorita",
|
"low_priority": "Nízká priorita",
|
||||||
"matrix": "Matrix",
|
"matrix": "Matrix",
|
||||||
@@ -1244,6 +1253,7 @@
|
|||||||
"change": "Změnit server identit",
|
"change": "Změnit server identit",
|
||||||
"change_prompt": "Odpojit se ze serveru <current /> a připojit na <new />?",
|
"change_prompt": "Odpojit se ze serveru <current /> a připojit na <new />?",
|
||||||
"change_server_prompt": "Pokud nechcete na hledání existujících kontaktů používat server <server />, zvolte si jiný server.",
|
"change_server_prompt": "Pokud nechcete na hledání existujících kontaktů používat server <server />, zvolte si jiný server.",
|
||||||
|
"changed": "Váš server identit byl změněn",
|
||||||
"checking": "Kontrolování serveru",
|
"checking": "Kontrolování serveru",
|
||||||
"description_connected": "Pro hledání existujících kontaktů používáte server identit <server></server>. Níže ho můžete změnit.",
|
"description_connected": "Pro hledání existujících kontaktů používáte server identit <server></server>. Níže ho můžete změnit.",
|
||||||
"description_disconnected": "Pro hledání existujících kontaktů nepoužíváte žádný server identit <server></server>. Abyste mohli hledat kontakty, nějaký níže nastavte.",
|
"description_disconnected": "Pro hledání existujících kontaktů nepoužíváte žádný server identit <server></server>. Abyste mohli hledat kontakty, nějaký níže nastavte.",
|
||||||
@@ -1286,7 +1296,9 @@
|
|||||||
"title": "Nepodporovaný prohlížeč",
|
"title": "Nepodporovaný prohlížeč",
|
||||||
"use_desktop_heading": "Místo toho použijte %(brand)s Desktop",
|
"use_desktop_heading": "Místo toho použijte %(brand)s Desktop",
|
||||||
"use_mobile_heading": "Místo toho použijte %(brand)s na mobilu",
|
"use_mobile_heading": "Místo toho použijte %(brand)s na mobilu",
|
||||||
"use_mobile_heading_after_desktop": "Nebo použijte naši mobilní aplikaci"
|
"use_mobile_heading_after_desktop": "Nebo použijte naši mobilní aplikaci",
|
||||||
|
"windows_64bit": "Windows (64-bit)",
|
||||||
|
"windows_arm_64bit": "Windows (ARM 64-bit)"
|
||||||
},
|
},
|
||||||
"info_tooltip_title": "Informace",
|
"info_tooltip_title": "Informace",
|
||||||
"integration_manager": {
|
"integration_manager": {
|
||||||
@@ -1295,6 +1307,7 @@
|
|||||||
"error_connecting_heading": "Nepovedlo se připojení ke správci integrací",
|
"error_connecting_heading": "Nepovedlo se připojení ke správci integrací",
|
||||||
"explainer": "Správci integrace přijímají konfigurační data a mohou vaším jménem upravovat widgety, odesílat pozvánky do místností a nastavovat úrovně oprávnění.",
|
"explainer": "Správci integrace přijímají konfigurační data a mohou vaším jménem upravovat widgety, odesílat pozvánky do místností a nastavovat úrovně oprávnění.",
|
||||||
"manage_title": "Správa integrací",
|
"manage_title": "Správa integrací",
|
||||||
|
"toggle_label": "Povolit správce integrací",
|
||||||
"use_im": "Použít správce integrací na správu botů, widgetů a nálepek.",
|
"use_im": "Použít správce integrací na správu botů, widgetů a nálepek.",
|
||||||
"use_im_default": "Použít správce integrací <b>(%(serverName)s)</b> na správu botů, widgetů a nálepek."
|
"use_im_default": "Použít správce integrací <b>(%(serverName)s)</b> na správu botů, widgetů a nálepek."
|
||||||
},
|
},
|
||||||
@@ -2097,11 +2110,16 @@
|
|||||||
"one": "Momentálně se připojuje %(count)s místnost",
|
"one": "Momentálně se připojuje %(count)s místnost",
|
||||||
"other": "Momentálně se připojuje %(count)s místností"
|
"other": "Momentálně se připojuje %(count)s místností"
|
||||||
},
|
},
|
||||||
|
"list_title": "Seznam místností",
|
||||||
"notification_options": "Možnosti oznámení",
|
"notification_options": "Možnosti oznámení",
|
||||||
|
"open_space_menu": "Otevřít nabídku prostoru",
|
||||||
"redacting_messages_status": {
|
"redacting_messages_status": {
|
||||||
"one": "Momentálně se odstraňují zprávy v %(count)s místnosti",
|
"one": "Momentálně se odstraňují zprávy v %(count)s místnosti",
|
||||||
"other": "Momentálně se odstraňují zprávy v %(count)s místnostech"
|
"other": "Momentálně se odstraňují zprávy v %(count)s místnostech"
|
||||||
},
|
},
|
||||||
|
"room": {
|
||||||
|
"open_room": "Otevřít místnost %(roomName)s"
|
||||||
|
},
|
||||||
"show_less": "Zobrazit méně",
|
"show_less": "Zobrazit méně",
|
||||||
"show_n_more": {
|
"show_n_more": {
|
||||||
"other": "Zobrazit %(count)s dalších",
|
"other": "Zobrazit %(count)s dalších",
|
||||||
@@ -2112,6 +2130,10 @@
|
|||||||
"sort_by_activity": "Aktivity",
|
"sort_by_activity": "Aktivity",
|
||||||
"sort_by_alphabet": "A–Z",
|
"sort_by_alphabet": "A–Z",
|
||||||
"sort_unread_first": "Zobrazovat místnosti s nepřečtenými zprávami jako první",
|
"sort_unread_first": "Zobrazovat místnosti s nepřečtenými zprávami jako první",
|
||||||
|
"space_menu": {
|
||||||
|
"home": "Domov prostoru",
|
||||||
|
"space_settings": "Nastavení prostoru"
|
||||||
|
},
|
||||||
"space_menu_label": "Nabídka pro %(spaceName)s",
|
"space_menu_label": "Nabídka pro %(spaceName)s",
|
||||||
"sublist_options": "Možnosti seznamu",
|
"sublist_options": "Možnosti seznamu",
|
||||||
"suggested_rooms_heading": "Doporučené místnosti"
|
"suggested_rooms_heading": "Doporučené místnosti"
|
||||||
@@ -2474,12 +2496,14 @@
|
|||||||
"breadcrumb_title_forgot": "Zapomněli jste klíč pro obnovení? Budete muset obnovit svou identitu.",
|
"breadcrumb_title_forgot": "Zapomněli jste klíč pro obnovení? Budete muset obnovit svou identitu.",
|
||||||
"breadcrumb_warning": "Udělejte to pouze v případě, že se domníváte, že váš účet byl napaden.",
|
"breadcrumb_warning": "Udělejte to pouze v případě, že se domníváte, že váš účet byl napaden.",
|
||||||
"details_title": "Podrobnosti o šifrování",
|
"details_title": "Podrobnosti o šifrování",
|
||||||
|
"do_not_close_warning": "Nezavírejte toto okno, dokud není resetování dokončeno",
|
||||||
"export_keys": "Exportovat klíče",
|
"export_keys": "Exportovat klíče",
|
||||||
"import_keys": "Importovat klíče",
|
"import_keys": "Importovat klíče",
|
||||||
"other_people_device_description": "Ve výchozím nastavení v šifrovaných místnostech nikomu neposílat šifrované zprávy, dokud je neověříte",
|
"other_people_device_description": "Ve výchozím nastavení v šifrovaných místnostech nikomu neposílat šifrované zprávy, dokud je neověříte",
|
||||||
"other_people_device_label": "Nikdy neposílejte šifrované zprávy na neověřená zařízení",
|
"other_people_device_label": "Nikdy neposílejte šifrované zprávy na neověřená zařízení",
|
||||||
"other_people_device_title": "Zařízení ostatních uživatelů",
|
"other_people_device_title": "Zařízení ostatních uživatelů",
|
||||||
"reset_identity": "Obnovit kryptografickou identitu",
|
"reset_identity": "Obnovit kryptografickou identitu",
|
||||||
|
"reset_in_progress": "Probíhá resetování...",
|
||||||
"session_id": "ID relace:",
|
"session_id": "ID relace:",
|
||||||
"session_key": "Klíč relace:",
|
"session_key": "Klíč relace:",
|
||||||
"title": "Rozšířené"
|
"title": "Rozšířené"
|
||||||
@@ -3902,7 +3926,6 @@
|
|||||||
"input_devices": "Vstupní zařízení",
|
"input_devices": "Vstupní zařízení",
|
||||||
"jitsi_call": "Jitsi konference",
|
"jitsi_call": "Jitsi konference",
|
||||||
"join_button_tooltip_call_full": "Omlouváme se — tento hovor je v současné době plný",
|
"join_button_tooltip_call_full": "Omlouváme se — tento hovor je v současné době plný",
|
||||||
"join_button_tooltip_connecting": "Spojování",
|
|
||||||
"legacy_call": "Zastaralý způsob hovoru",
|
"legacy_call": "Zastaralý způsob hovoru",
|
||||||
"maximise": "Vyplnit obrazovku",
|
"maximise": "Vyplnit obrazovku",
|
||||||
"maximise_call": "Maximalizovat hovor",
|
"maximise_call": "Maximalizovat hovor",
|
||||||
|
|||||||
@@ -72,12 +72,14 @@
|
|||||||
"maximise": "Mwyhau",
|
"maximise": "Mwyhau",
|
||||||
"mention": "Crybwyll",
|
"mention": "Crybwyll",
|
||||||
"minimise": "Lleihau",
|
"minimise": "Lleihau",
|
||||||
|
"new_message": "Neges newydd",
|
||||||
"new_room": "Ystafell newydd",
|
"new_room": "Ystafell newydd",
|
||||||
"new_video_room": "Ystafell fideo newydd",
|
"new_video_room": "Ystafell fideo newydd",
|
||||||
"next": "Nesaf",
|
"next": "Nesaf",
|
||||||
"no": "Na",
|
"no": "Na",
|
||||||
"ok": "Iawn",
|
"ok": "Iawn",
|
||||||
"open": "Agor",
|
"open": "Agor",
|
||||||
|
"open_menu": "Agor dewislen",
|
||||||
"pause": "Oedi",
|
"pause": "Oedi",
|
||||||
"pin": "Pinio",
|
"pin": "Pinio",
|
||||||
"play": "Chwarae",
|
"play": "Chwarae",
|
||||||
@@ -322,11 +324,11 @@
|
|||||||
"server_picker_title_default": "Opsiynau Gweinydd",
|
"server_picker_title_default": "Opsiynau Gweinydd",
|
||||||
"server_picker_title_registration": "Cyfrif gwesteiwr ymlaen",
|
"server_picker_title_registration": "Cyfrif gwesteiwr ymlaen",
|
||||||
"session_logged_out_description": "Er diogelwch, mae'r sesiwn hon wedi'i hallgofnodi. Mewngofnodwch eto.",
|
"session_logged_out_description": "Er diogelwch, mae'r sesiwn hon wedi'i hallgofnodi. Mewngofnodwch eto.",
|
||||||
"session_logged_out_title": "Wedi Arwyddo Allan",
|
"session_logged_out_title": "Wedi Allgofnodi",
|
||||||
"set_email": {
|
"set_email": {
|
||||||
"description": "Bydd hyn yn caniatáu ichi ailosod eich cyfrinair a derbyn hysbysiadau.",
|
"description": "Bydd hyn yn caniatáu i chi ailosod eich cyfrinair a derbyn hysbysiadau.",
|
||||||
"verification_pending_description": "Gwiriwch eich e-bost a chliciwch ar y ddolen sydd ynddo. Unwaith y gwneir hyn, cliciwch parhau.",
|
"verification_pending_description": "Gwiriwch eich e-bost a chliciwch ar y ddolen sydd ynddo. Unwaith y gwneir hyn, cliciwch parhau.",
|
||||||
"verification_pending_title": "Dilysu yn yr Arfaeth"
|
"verification_pending_title": "Yn Aros i Wirio"
|
||||||
},
|
},
|
||||||
"set_email_prompt": "Ydych chi am osod cyfeiriad e-bost?",
|
"set_email_prompt": "Ydych chi am osod cyfeiriad e-bost?",
|
||||||
"sign_in_description": "Defnyddiwch eich cyfrif i barhau.",
|
"sign_in_description": "Defnyddiwch eich cyfrif i barhau.",
|
||||||
@@ -335,7 +337,7 @@
|
|||||||
"sign_in_or_register": "Mewngofnodi neu Creu Cyfrif",
|
"sign_in_or_register": "Mewngofnodi neu Creu Cyfrif",
|
||||||
"sign_in_or_register_description": "Defnyddiwch eich cyfrif neu crëwch un newydd i barhau.",
|
"sign_in_or_register_description": "Defnyddiwch eich cyfrif neu crëwch un newydd i barhau.",
|
||||||
"sign_in_prompt": "Oes gennych chi gyfrif? <a>Mewngofnodwch</a>",
|
"sign_in_prompt": "Oes gennych chi gyfrif? <a>Mewngofnodwch</a>",
|
||||||
"sign_in_with_sso": "Mewngofnodwch gyda mewngofnodi sengl",
|
"sign_in_with_sso": "Mewngofnodwch gyda mewngofnod sengl",
|
||||||
"signing_in": "Wrthi'n mewngofnodi…",
|
"signing_in": "Wrthi'n mewngofnodi…",
|
||||||
"soft_logout": {
|
"soft_logout": {
|
||||||
"clear_data_button": "Clirio'r holl ddata",
|
"clear_data_button": "Clirio'r holl ddata",
|
||||||
@@ -348,9 +350,9 @@
|
|||||||
"soft_logout_intro_unsupported_auth": "Ni allwch fewngofnodi i'ch cyfrif. Cysylltwch â gweinyddwr eich gweinydd cartref am ragor o wybodaeth.",
|
"soft_logout_intro_unsupported_auth": "Ni allwch fewngofnodi i'ch cyfrif. Cysylltwch â gweinyddwr eich gweinydd cartref am ragor o wybodaeth.",
|
||||||
"soft_logout_subheading": "Clirio data personol",
|
"soft_logout_subheading": "Clirio data personol",
|
||||||
"soft_logout_warning": "Rhybudd: mae eich data personol (gan gynnwys allweddi amgryptio) yn dal i gael ei storio yn y sesiwn hon. Cliriwch ef os ydych wedi gorffen defnyddio'r sesiwn hon, neu eisiau mewngofnodi i gyfrif arall.",
|
"soft_logout_warning": "Rhybudd: mae eich data personol (gan gynnwys allweddi amgryptio) yn dal i gael ei storio yn y sesiwn hon. Cliriwch ef os ydych wedi gorffen defnyddio'r sesiwn hon, neu eisiau mewngofnodi i gyfrif arall.",
|
||||||
"sso": "Arwyddo Sengl",
|
"sso": "Mewngofnod Sengl",
|
||||||
"sso_complete_in_browser_dialog_title": "Ewch i'ch porwr i gwblhau Mewngofnodi",
|
"sso_complete_in_browser_dialog_title": "Ewch i'ch porwr i gwblhau Mewngofnodi",
|
||||||
"sso_failed_missing_storage": "Gofynnom i'r porwr gofio pa weinydd cartref rydych chi'n ei ddefnyddio i'ch galluogi i fewngofnodi, ond yn anffodus mae eich porwr wedi anghofio hynny. Ewch i'r dudalen mewngofnodi a cheisiwch eto.",
|
"sso_failed_missing_storage": "Rydym wedi gofyn i'r porwr gofio pa weinydd cartref rydych chi'n ei ddefnyddio i'ch galluogi i fewngofnodi, ond yn anffodus mae eich porwr wedi anghofio hynny. Ewch i'r dudalen mewngofnodi a cheisiwch eto.",
|
||||||
"sso_or_username_password": "%(ssoButtons)s Neu %(usernamePassword)s",
|
"sso_or_username_password": "%(ssoButtons)s Neu %(usernamePassword)s",
|
||||||
"sync_footer_subtitle": "Os ydych chi wedi ymuno â llawer o ystafelloedd, gallai hyn gymryd peth amser",
|
"sync_footer_subtitle": "Os ydych chi wedi ymuno â llawer o ystafelloedd, gallai hyn gymryd peth amser",
|
||||||
"syncing": "Cydweddu…",
|
"syncing": "Cydweddu…",
|
||||||
@@ -367,17 +369,17 @@
|
|||||||
"msisdn_token_incorrect": "Tocyn yn anghywir",
|
"msisdn_token_incorrect": "Tocyn yn anghywir",
|
||||||
"msisdn_token_prompt": "Rhowch y cod sydd ynddo:",
|
"msisdn_token_prompt": "Rhowch y cod sydd ynddo:",
|
||||||
"password_prompt": "Cadarnhewch eich hunaniaeth trwy nodi cyfrinair eich cyfrif isod.",
|
"password_prompt": "Cadarnhewch eich hunaniaeth trwy nodi cyfrinair eich cyfrif isod.",
|
||||||
"recaptcha_missing_params": "Allwedd gyhoeddus captcha ar goll yng nghyfluniad y gweinydd cartref. Rhowch wybod i'ch gweinyddwr gweinyddwr am hyn.",
|
"recaptcha_missing_params": "Mae allwedd gyhoeddus captcha ar goll yn ffurfweddiad y gweinydd cartref. Rhowch wybod i'ch gweinyddwr gweinyddwr am hyn.",
|
||||||
"registration_token_label": "Tocyn cofrestru",
|
"registration_token_label": "Tocyn cofrestru",
|
||||||
"registration_token_prompt": "Rhowch docyn cofrestru a ddarparwyd gan weinyddwr y gweinydd cartref.",
|
"registration_token_prompt": "Rhowch docyn cofrestru a ddarparwyd gan weinyddwr y gweinydd cartref.",
|
||||||
"sso_body": "Cadarnhewch ychwanegu'r cyfeiriad e-bost hwn trwy ddefnyddio Single Sign On i brofi pwy ydych.",
|
"sso_body": "Cadarnhewch ychwanegu'r cyfeiriad e-bost hwn trwy ddefnyddio Single Sign On i brofi pwy ydych.",
|
||||||
"sso_failed": "Aeth rhywbeth o'i le wrth gadarnhau pwy ydych chi. Canslo a cheisio eto.",
|
"sso_failed": "Aeth rhywbeth o'i le wrth gadarnhau pwy ydych chi. Diddymu a cheisio eto.",
|
||||||
"sso_postauth_body": "Cliciwch ar y botwm isod i gadarnhau pwy ydych chi.",
|
"sso_postauth_body": "Cliciwch ar y botwm isod i gadarnhau pwy ydych chi.",
|
||||||
"sso_postauth_title": "Cadarnhau i barhau",
|
"sso_postauth_title": "Cadarnhau i barhau",
|
||||||
"sso_preauth_body": "I barhau, defnyddiwch Arwyddo Sengl i brofi pwy ydych.",
|
"sso_preauth_body": "I barhau, defnyddiwch Mewngofnod Sengl i brofi pwy ydych.",
|
||||||
"sso_title": "Defnyddiwch Arwyddo Sengl i barhau",
|
"sso_title": "Defnyddiwch Mewngofnod Sengl i barhau",
|
||||||
"terms": "Adolygwch a derbyniwch bolisïau'r gweinydd cartref hwn:",
|
"terms": "Darllenwch a derbyniwch bolisïau'r gweinydd cartref hwn:",
|
||||||
"terms_invalid": "Adolygwch a derbyniwch holl bolisïau'r gweinydd cartref"
|
"terms_invalid": "Darllenwch a derbyniwch bolisïau'r gweinydd cartref hwn"
|
||||||
},
|
},
|
||||||
"unsupported_auth": "Nid yw'r gweinydd cartref hwn yn cynnig unrhyw lifau mewngofnodi a gefnogir gan y cleient hwn.",
|
"unsupported_auth": "Nid yw'r gweinydd cartref hwn yn cynnig unrhyw lifau mewngofnodi a gefnogir gan y cleient hwn.",
|
||||||
"unsupported_auth_email": "Nid yw'r gweinydd cartref hwn yn cefnogi mewngofnodi gan ddefnyddio cyfeiriad e-bost.",
|
"unsupported_auth_email": "Nid yw'r gweinydd cartref hwn yn cefnogi mewngofnodi gan ddefnyddio cyfeiriad e-bost.",
|
||||||
@@ -389,15 +391,14 @@
|
|||||||
},
|
},
|
||||||
"bug_reporting": {
|
"bug_reporting": {
|
||||||
"additional_context": "Os oes cyd-destun ychwanegol a fyddai'n helpu i ddadansoddi'r mater, megis yr hyn yr oeddech yn ei wneud ar y pryd, IDau ystafelloedd, IDau defnyddiwr, ac ati, cynhwyswch y pethau hynny yma.",
|
"additional_context": "Os oes cyd-destun ychwanegol a fyddai'n helpu i ddadansoddi'r mater, megis yr hyn yr oeddech yn ei wneud ar y pryd, IDau ystafelloedd, IDau defnyddiwr, ac ati, cynhwyswch y pethau hynny yma.",
|
||||||
"before_submitting": "Rydym yn argymell <a>creu rhifyn GitHub</a> i sicrhau bod eich adroddiad yn cael ei adolygu.",
|
"before_submitting": "Rydym yn argymell <a>creu mater GitHub</a> i sicrhau bod eich adroddiad yn cael ei ddarllen.",
|
||||||
"collecting_information": "Casglu gwybodaeth fersiwn ap",
|
"collecting_information": "Casglu gwybodaeth fersiwn ap",
|
||||||
"collecting_logs": "Casglu boncyffion",
|
"collecting_logs": "Casglu cofnodion",
|
||||||
"create_new_issue": "Os gwelwch yn dda<newIssueLink> creu rhifyn newydd</newIssueLink> ar GitHub fel y gallwn ymchwilio i'r byg hwn.",
|
"create_new_issue": "Os gwelwch yn dda<newIssueLink> crewch fater newydd</newIssueLink> ar GitHub fel y gallwn ymchwilio i'r byg hwn.",
|
||||||
"description": "Mae logiau dadfygio yn cynnwys data defnydd cymhwysiad gan gynnwys eich enw defnyddiwr, IDau neu arallenwau'r ystafelloedd yr ydych wedi ymweld â nhw, pa elfennau UI y gwnaethoch ryngweithio â nhw ddiwethaf, ac enwau defnyddwyr defnyddwyr eraill. Nid ydynt yn cynnwys negeseuon.",
|
"description": "Mae cofnodion dadfygio yn cynnwys data defnydd cymhwysiad gan gynnwys eich enw defnyddiwr, IDau neu arallenwau'r ystafelloedd yr ydych wedi ymweld â nhw, pa elfennau UI y gwnaethoch ryngweithio â nhw ddiwethaf, ac enwau defnyddwyr defnyddwyr eraill. Nid ydynt yn cynnwys negeseuon.",
|
||||||
"download_logs": "Lawrlwythwch logiau",
|
"download_logs": "Llwytho logiau i lawr",
|
||||||
"downloading_logs": "Wrthi'n llwytho i lawr logiau",
|
"downloading_logs": "Wrthi'n llwytho logiau i lawr",
|
||||||
"error_empty": "Dywedwch wrthym beth aeth o'i le neu, yn well, crëwch fater GitHub sy'n disgrifio'r broblem.",
|
"error_empty": "Dywedwch wrthym beth aeth o'i le neu, yn well, crëwch fater GitHub sy'n disgrifio'r broblem.",
|
||||||
"failed_send_logs": "Wedi methu ag anfon logiau: ",
|
|
||||||
"github_issue": "Mater GitHub",
|
"github_issue": "Mater GitHub",
|
||||||
"introduction": "Os ydych chi wedi cyflwyno byg trwy GitHub, gall logiau dadfygio ein helpu i ddod o hyd i'r broblem. ",
|
"introduction": "Os ydych chi wedi cyflwyno byg trwy GitHub, gall logiau dadfygio ein helpu i ddod o hyd i'r broblem. ",
|
||||||
"log_request": "Er mwyn ein helpu i atal hyn yn y dyfodol, <a>anfonwch logiau atom</a> .",
|
"log_request": "Er mwyn ein helpu i atal hyn yn y dyfodol, <a>anfonwch logiau atom</a> .",
|
||||||
@@ -483,7 +484,6 @@
|
|||||||
"legal": "Cyfreithiol",
|
"legal": "Cyfreithiol",
|
||||||
"light": "Golau",
|
"light": "Golau",
|
||||||
"loading": "Llwytho…",
|
"loading": "Llwytho…",
|
||||||
"lobby": "Cyntedd",
|
|
||||||
"location": "Lleoliad",
|
"location": "Lleoliad",
|
||||||
"low_priority": "Blaenoriaeth isel",
|
"low_priority": "Blaenoriaeth isel",
|
||||||
"matrix": "Matrics",
|
"matrix": "Matrics",
|
||||||
@@ -507,7 +507,7 @@
|
|||||||
"people": "Pobl",
|
"people": "Pobl",
|
||||||
"preferences": "Dewisiadau",
|
"preferences": "Dewisiadau",
|
||||||
"presence": "Presenoldeb",
|
"presence": "Presenoldeb",
|
||||||
"preview_message": "Hei chi. Ti yw'r gorau!",
|
"preview_message": "Hei ti. Ti yw'r gorau!",
|
||||||
"privacy": "Preifatrwydd",
|
"privacy": "Preifatrwydd",
|
||||||
"private": "Preifat",
|
"private": "Preifat",
|
||||||
"private_room": "Ystafell breifat",
|
"private_room": "Ystafell breifat",
|
||||||
@@ -531,7 +531,7 @@
|
|||||||
"select_all": "Dewis y cyfan",
|
"select_all": "Dewis y cyfan",
|
||||||
"server": "Gweinydd",
|
"server": "Gweinydd",
|
||||||
"settings": "Gosodiadau",
|
"settings": "Gosodiadau",
|
||||||
"setup_secure_messages": "Sefydlu Negeseuon Diogel",
|
"setup_secure_messages": "Gosod Negeseuon Diogel",
|
||||||
"show_more": "Dangos mwy",
|
"show_more": "Dangos mwy",
|
||||||
"someone": "Rhywun",
|
"someone": "Rhywun",
|
||||||
"space": "Bwlch",
|
"space": "Bwlch",
|
||||||
@@ -543,12 +543,12 @@
|
|||||||
"support": "Cymorth",
|
"support": "Cymorth",
|
||||||
"system_alerts": "Rhybuddion System",
|
"system_alerts": "Rhybuddion System",
|
||||||
"theme": "Thema",
|
"theme": "Thema",
|
||||||
"thread": "Edau",
|
"thread": "Edefyn",
|
||||||
"threads": "Edau",
|
"threads": "Edau",
|
||||||
"timeline": "Llinell Amser",
|
"timeline": "Llinell Amser",
|
||||||
"unavailable": "ddim ar gael",
|
"unavailable": "ddim ar gael",
|
||||||
"unencrypted": "Heb ei amgryptio",
|
"unencrypted": "Heb ei amgryptio",
|
||||||
"unmute": "Dad-ddistewi",
|
"unmute": "Dad-dewi",
|
||||||
"unnamed_room": "Ystafell Ddienw",
|
"unnamed_room": "Ystafell Ddienw",
|
||||||
"unnamed_space": "Gofod Dienw",
|
"unnamed_space": "Gofod Dienw",
|
||||||
"unverified": "Heb ei wirio",
|
"unverified": "Heb ei wirio",
|
||||||
@@ -556,7 +556,7 @@
|
|||||||
"user": "Defnyddwyr",
|
"user": "Defnyddwyr",
|
||||||
"user_avatar": "Llun proffil",
|
"user_avatar": "Llun proffil",
|
||||||
"username": "Enw defnyddiwr",
|
"username": "Enw defnyddiwr",
|
||||||
"verification_cancelled": "Dilysiad wedi'i ganslo",
|
"verification_cancelled": "Dilysiad wedi'i ddiddymu",
|
||||||
"verified": "Gwiriwyd",
|
"verified": "Gwiriwyd",
|
||||||
"version": "Fersiwn",
|
"version": "Fersiwn",
|
||||||
"video": "Fideo",
|
"video": "Fideo",
|
||||||
@@ -567,22 +567,22 @@
|
|||||||
"composer": {
|
"composer": {
|
||||||
"autocomplete": {
|
"autocomplete": {
|
||||||
"@room_description": "Rhowch wybod i'r ystafell gyfan",
|
"@room_description": "Rhowch wybod i'r ystafell gyfan",
|
||||||
"command_a11y": "Command Autocomplete",
|
"command_a11y": "Awtogwblhau Gorchymyn",
|
||||||
"command_description": "Gorchmynion",
|
"command_description": "Gorchmynion",
|
||||||
"emoji_a11y": "Autocomplete Emoji",
|
"emoji_a11y": "Awtogwblhau Emoji",
|
||||||
"notification_a11y": "Hysbysiad yn Awtolenwi",
|
"notification_a11y": "Awtogwblhau Hysbysiad",
|
||||||
"notification_description": "Hysbysiad Ystafell",
|
"notification_description": "Hysbysiad Ystafell",
|
||||||
"room_a11y": "Ystafell Awtogwblhau",
|
"room_a11y": "Awtogwblhau Ystafell",
|
||||||
"space_a11y": "Gofod Autocomplete",
|
"space_a11y": "Awtogwblhau Gofod",
|
||||||
"user_a11y": "Defnyddiwr yn Awtolenwi",
|
"user_a11y": "Awtogwblhau Defnyddiwr",
|
||||||
"user_description": "Defnyddwyr"
|
"user_description": "Defnyddwyr"
|
||||||
},
|
},
|
||||||
"close_sticker_picker": "Cuddio sticeri",
|
"close_sticker_picker": "Cuddio sticeri",
|
||||||
"edit_composer_label": "Golygu neges",
|
"edit_composer_label": "Golygu neges",
|
||||||
"format_bold": "Trwm",
|
"format_bold": "Trwm",
|
||||||
"format_code_block": "Bloc cod",
|
"format_code_block": "Bloc cod",
|
||||||
"format_decrease_indent": "Gostyngiad mewnoliad",
|
"format_decrease_indent": "Lleihau mewnoliad",
|
||||||
"format_increase_indent": "Cynnydd mewnoliad",
|
"format_increase_indent": "Cynnyddu mewnoliad",
|
||||||
"format_inline_code": "Cod",
|
"format_inline_code": "Cod",
|
||||||
"format_insert_link": "Mewnosod dolen",
|
"format_insert_link": "Mewnosod dolen",
|
||||||
"format_italic": "Italig",
|
"format_italic": "Italig",
|
||||||
@@ -604,7 +604,7 @@
|
|||||||
"no_perms_notice": "Nid oes gennych ganiatâd i bostio i'r ystafell hon",
|
"no_perms_notice": "Nid oes gennych ganiatâd i bostio i'r ystafell hon",
|
||||||
"placeholder": "Anfon neges…",
|
"placeholder": "Anfon neges…",
|
||||||
"placeholder_encrypted": "Anfon neges wedi'i hamgryptio…",
|
"placeholder_encrypted": "Anfon neges wedi'i hamgryptio…",
|
||||||
"placeholder_reply": "Anfonwch ateb…",
|
"placeholder_reply": "Anfon ateb…",
|
||||||
"placeholder_reply_encrypted": "Anfon ateb wedi'i amgryptio…",
|
"placeholder_reply_encrypted": "Anfon ateb wedi'i amgryptio…",
|
||||||
"placeholder_thread": "Ateb i edefyn…",
|
"placeholder_thread": "Ateb i edefyn…",
|
||||||
"placeholder_thread_encrypted": "Ymateb i edefyn wedi'i amgryptio…",
|
"placeholder_thread_encrypted": "Ymateb i edefyn wedi'i amgryptio…",
|
||||||
@@ -620,17 +620,17 @@
|
|||||||
"stop_voice_message": "Stopio recordio",
|
"stop_voice_message": "Stopio recordio",
|
||||||
"voice_message_button": "Neges Llais"
|
"voice_message_button": "Neges Llais"
|
||||||
},
|
},
|
||||||
"console_dev_note": "Os ydych chi'n gwybod beth rydych chi'n ei wneud, mae Element yn ffynhonnell agored, gwnewch yn siŵr eich bod chi'n edrych ar ein GitHub ( https://github.com/vector-im/element-web/ ) a chyfrannu!",
|
"console_dev_note": "Os ydych chi'n gwybod beth rydych chi'n ei wneud, mae Element yn raglen cod agored, gwnewch yn siŵr eich bod chi'n edrych ar ein GitHub ( https://github.com/vector-im/element-web/ ) a chyfrannu!",
|
||||||
"console_scam_warning": "Os dywedodd rhywun wrthych am gopïo/gludo rhywbeth yma, mae'n debygol iawn eich bod yn cael eich twyllo!",
|
"console_scam_warning": "Os dywedodd rhywun wrthych am gopïo/gludo rhywbeth yma, mae'n debygol iawn eich bod yn cael eich twyllo!",
|
||||||
"console_wait": "Arhoswch!",
|
"console_wait": "Arhoswch!",
|
||||||
"create_room": {
|
"create_room": {
|
||||||
"action_create_room": "Creu ystafell",
|
"action_create_room": "Creu ystafell",
|
||||||
"action_create_video_room": "Creu ystafell fideo",
|
"action_create_video_room": "Creu ystafell fideo",
|
||||||
"encrypted_video_room_warning": "Ni allwch analluogi hyn yn nes ymlaen. Bydd yr ystafell yn cael ei hamgryptio ond ni fydd yr alwad wedi'i mewnosod.",
|
"encrypted_video_room_warning": "Allwch chi ddim analluogi hyn yn nes ymlaen. Bydd yr ystafell yn cael ei hamgryptio ond ni fydd yr alwad wedi'i mewnosod.",
|
||||||
"encrypted_warning": "Ni allwch analluogi hyn yn nes ymlaen. Ni fydd pontydd a'r mwyafrif o bots yn gweithio eto.",
|
"encrypted_warning": "Allwch chi ddim analluogi hyn yn nes ymlaen. Ni fydd pontydd a'r mwyafrif o fotiau'n gweithio eto.",
|
||||||
"encryption_forced": "Mae angen amgryptio ar eich gweinydd er mwyn ei alluogi mewn ystafelloedd preifat.",
|
"encryption_forced": "Mae angen amgryptio ar eich gweinydd er mwyn ei alluogi mewn ystafelloedd preifat.",
|
||||||
"encryption_label": "Galluogi amgryptio o'r dechrau i'r diwedd",
|
"encryption_label": "Galluogi amgryptio o'r dechrau i'r diwedd",
|
||||||
"error_title": "Methiant i greu ystafell",
|
"error_title": "Methu creu ystafell",
|
||||||
"generic_error": "Mae'n bosibl nad yw'r gweinydd ar gael, wedi'i orlwytho, neu eich bod yn taro byg.",
|
"generic_error": "Mae'n bosibl nad yw'r gweinydd ar gael, wedi'i orlwytho, neu eich bod yn taro byg.",
|
||||||
"join_rule_change_notice": "Gallwch newid hwn unrhyw bryd o osodiadau ystafell.",
|
"join_rule_change_notice": "Gallwch newid hwn unrhyw bryd o osodiadau ystafell.",
|
||||||
"join_rule_invite": "Ystafell breifat (gwahoddiad yn unig)",
|
"join_rule_invite": "Ystafell breifat (gwahoddiad yn unig)",
|
||||||
@@ -647,7 +647,7 @@
|
|||||||
"title_video_room": "Creu ystafell fideo",
|
"title_video_room": "Creu ystafell fideo",
|
||||||
"topic_label": "Pwnc (dewisol)",
|
"topic_label": "Pwnc (dewisol)",
|
||||||
"unfederated": "Rhwystro unrhyw un nad yw'n rhan o %(serverName)s rhag ymuno â'r ystafell hon byth.",
|
"unfederated": "Rhwystro unrhyw un nad yw'n rhan o %(serverName)s rhag ymuno â'r ystafell hon byth.",
|
||||||
"unfederated_label_default_off": "Efallai y byddwch yn galluogi hyn os mai dim ond ar gyfer cydweithio â thimau mewnol ar eich gweinydd cartref y bydd yr ystafell yn cael ei defnyddio. Ni ellir newid hyn yn ddiweddarach.",
|
"unfederated_label_default_off": "Efallai y byddwch yn galluogi hyn os mai dim ond ar gyfer cydweithio â thimau mewnol ar eich gweinydd cartref y bydd yr ystafell yn cael ei defnyddio. Nid oes modd newid hyn yn ddiweddarach.",
|
||||||
"unfederated_label_default_on": "Efallai y byddwch yn analluogi hyn os bydd yr ystafell yn cael ei defnyddio ar gyfer cydweithio â thimau allanol sydd â'u gweinydd cartref eu hunain. Ni ellir newid hyn yn ddiweddarach.",
|
"unfederated_label_default_on": "Efallai y byddwch yn analluogi hyn os bydd yr ystafell yn cael ei defnyddio ar gyfer cydweithio â thimau allanol sydd â'u gweinydd cartref eu hunain. Ni ellir newid hyn yn ddiweddarach.",
|
||||||
"unsupported_version": "Nid yw'r gweinydd yn cefnogi'r fersiwn ystafell a nodwyd."
|
"unsupported_version": "Nid yw'r gweinydd yn cefnogi'r fersiwn ystafell a nodwyd."
|
||||||
},
|
},
|
||||||
@@ -662,7 +662,7 @@
|
|||||||
"creating_rooms": "Wrthi'n creu ystafelloedd…",
|
"creating_rooms": "Wrthi'n creu ystafelloedd…",
|
||||||
"done_action": "Ewch i'm gofod",
|
"done_action": "Ewch i'm gofod",
|
||||||
"done_action_first_room": "Ewch i fy ystafell gyntaf",
|
"done_action_first_room": "Ewch i fy ystafell gyntaf",
|
||||||
"explainer": "Mae lleoedd yn ffordd newydd o grwpio ystafelloedd a phobl. Pa fath o Ofod ydych chi am ei greu? Gallwch newid hyn yn nes ymlaen.",
|
"explainer": "Mae gofodau yn ffordd newydd o grwpio ystafelloedd a phobl. Pa fath o Ofod ydych chi am ei greu? Gallwch newid hyn yn nes ymlaen.",
|
||||||
"failed_create_initial_rooms": "Wedi methu â chreu ystafelloedd gofod cychwynnol",
|
"failed_create_initial_rooms": "Wedi methu â chreu ystafelloedd gofod cychwynnol",
|
||||||
"failed_invite_users": "Wedi methu â gwahodd y defnyddwyr canlynol i'ch gofod: %(csvUsers)s",
|
"failed_invite_users": "Wedi methu â gwahodd y defnyddwyr canlynol i'ch gofod: %(csvUsers)s",
|
||||||
"invite_teammates_by_username": "Gwahoddiad yn ôl enw defnyddiwr",
|
"invite_teammates_by_username": "Gwahoddiad yn ôl enw defnyddiwr",
|
||||||
@@ -680,13 +680,13 @@
|
|||||||
"private_space": "Fi a fy nghyd-chwaraewyr",
|
"private_space": "Fi a fy nghyd-chwaraewyr",
|
||||||
"private_space_description": "Man preifat i chi a'ch cyd-chwaraewyr",
|
"private_space_description": "Man preifat i chi a'ch cyd-chwaraewyr",
|
||||||
"public_description": "Mannau agored i unrhyw un, gorau i gymunedau",
|
"public_description": "Mannau agored i unrhyw un, gorau i gymunedau",
|
||||||
"public_heading": "Eich man cyhoeddus",
|
"public_heading": "Eich gofod cyhoeddus",
|
||||||
"search_public_button": "Chwilio am fannau cyhoeddus",
|
"search_public_button": "Chwilio am gofodau cyhoeddus",
|
||||||
"setup_rooms_community_description": "Gadewch i ni greu ystafell ar gyfer pob un ohonynt.",
|
"setup_rooms_community_description": "Gadewch i ni greu ystafell ar gyfer pob un ohonyn nhw.",
|
||||||
"setup_rooms_community_heading": "Beth yw rhai pethau rydych am eu trafod yn %(spaceName)s?",
|
"setup_rooms_community_heading": "Beth yw rhai pethau rydych am eu trafod yn %(spaceName)s?",
|
||||||
"setup_rooms_description": "Gallwch ychwanegu mwy yn nes ymlaen hefyd, gan gynnwys y rhai sydd eisoes yn bodoli.",
|
"setup_rooms_description": "Gallwch ychwanegu mwy yn nes ymlaen hefyd, gan gynnwys y rhai sydd eisoes yn bodoli.",
|
||||||
"setup_rooms_private_description": "Byddwn yn creu ystafelloedd ar gyfer pob un ohonynt.",
|
"setup_rooms_private_description": "Byddwn yn creu ystafelloedd ar gyfer pob un ohonyn nhw.",
|
||||||
"setup_rooms_private_heading": "Pa brosiectau y mae eich tîm yn gweithio arnynt?",
|
"setup_rooms_private_heading": "Pa brosiectau y mae eich tîm yn gweithio arnyn nhw?",
|
||||||
"share_description": "Dim ond chi yw e ar hyn o bryd, bydd hyd yn oed yn well gydag eraill.",
|
"share_description": "Dim ond chi yw e ar hyn o bryd, bydd hyd yn oed yn well gydag eraill.",
|
||||||
"share_heading": "Rhannu %(name)s",
|
"share_heading": "Rhannu %(name)s",
|
||||||
"skip_action": "Hepgor am nawr",
|
"skip_action": "Hepgor am nawr",
|
||||||
@@ -1199,6 +1199,7 @@
|
|||||||
"change": "Newid gweinydd hunaniaeth",
|
"change": "Newid gweinydd hunaniaeth",
|
||||||
"change_prompt": "Datgysylltwch o'r gweinydd hunaniaeth<current /> a chysylltu â<new /> yn lle hynny?",
|
"change_prompt": "Datgysylltwch o'r gweinydd hunaniaeth<current /> a chysylltu â<new /> yn lle hynny?",
|
||||||
"change_server_prompt": "Os nad ydych am ddefnyddio<server /> i ddarganfod a bod yn ddarganfyddadwy gan gysylltiadau presennol yr ydych yn gwybod, rhowch weinydd hunaniaeth arall isod.",
|
"change_server_prompt": "Os nad ydych am ddefnyddio<server /> i ddarganfod a bod yn ddarganfyddadwy gan gysylltiadau presennol yr ydych yn gwybod, rhowch weinydd hunaniaeth arall isod.",
|
||||||
|
"changed": "Mae eich gweinydd hunaniaeth wedi'i newid",
|
||||||
"checking": "Gwirio gweinydd",
|
"checking": "Gwirio gweinydd",
|
||||||
"description_connected": "Rydych chi'n defnyddio ar hyn o bryd<server></server> i ddarganfod a bod yn ddarganfyddadwy gan gysylltiadau presennol rydych chi'n eu hadnabod. Gallwch newid eich gweinydd hunaniaeth isod.",
|
"description_connected": "Rydych chi'n defnyddio ar hyn o bryd<server></server> i ddarganfod a bod yn ddarganfyddadwy gan gysylltiadau presennol rydych chi'n eu hadnabod. Gallwch newid eich gweinydd hunaniaeth isod.",
|
||||||
"description_disconnected": "Nid ydych yn defnyddio gweinydd adnabod ar hyn o bryd. I ddarganfod a chael eich darganfod gan gysylltiadau presennol rydych chi'n eu hadnabod, ychwanegwch un isod.",
|
"description_disconnected": "Nid ydych yn defnyddio gweinydd adnabod ar hyn o bryd. I ddarganfod a chael eich darganfod gan gysylltiadau presennol rydych chi'n eu hadnabod, ychwanegwch un isod.",
|
||||||
@@ -1237,7 +1238,9 @@
|
|||||||
"title": "Nid yw %(brand)s yn cefnogi'r porwr hwn",
|
"title": "Nid yw %(brand)s yn cefnogi'r porwr hwn",
|
||||||
"use_desktop_heading": "Defnyddiwch %(brand)s Penbwrdd yn lle hynny",
|
"use_desktop_heading": "Defnyddiwch %(brand)s Penbwrdd yn lle hynny",
|
||||||
"use_mobile_heading": "Defnyddiwch %(brand)s ar ffôn symudol yn lle hynny",
|
"use_mobile_heading": "Defnyddiwch %(brand)s ar ffôn symudol yn lle hynny",
|
||||||
"use_mobile_heading_after_desktop": "Neu defnyddiwch ein app symudol"
|
"use_mobile_heading_after_desktop": "Neu defnyddiwch ein app symudol",
|
||||||
|
"windows_64bit": "Windows (64-did)",
|
||||||
|
"windows_arm_64bit": "Windows (ARM 64-did)"
|
||||||
},
|
},
|
||||||
"info_tooltip_title": "Gwybodaeth",
|
"info_tooltip_title": "Gwybodaeth",
|
||||||
"integration_manager": {
|
"integration_manager": {
|
||||||
@@ -1977,12 +1980,17 @@
|
|||||||
"home_menu_label": "Opsiynau cartref",
|
"home_menu_label": "Opsiynau cartref",
|
||||||
"join_public_room_label": "Ymunwch â'r ystafell gyhoeddus",
|
"join_public_room_label": "Ymunwch â'r ystafell gyhoeddus",
|
||||||
"notification_options": "Opsiynau hysbysu",
|
"notification_options": "Opsiynau hysbysu",
|
||||||
|
"open_space_menu": "Agor dewislen gofod",
|
||||||
"show_less": "Dangos llai",
|
"show_less": "Dangos llai",
|
||||||
"show_previews": "Dangos rhagolwg o negeseuon",
|
"show_previews": "Dangos rhagolwg o negeseuon",
|
||||||
"sort_by": "Trefnu yn ôl",
|
"sort_by": "Trefnu yn ôl",
|
||||||
"sort_by_activity": "Gweithgaredd",
|
"sort_by_activity": "Gweithgaredd",
|
||||||
"sort_by_alphabet": "A-Z",
|
"sort_by_alphabet": "A-Z",
|
||||||
"sort_unread_first": "Dangos ystafelloedd gyda negeseuon heb eu darllen yn gyntaf",
|
"sort_unread_first": "Dangos ystafelloedd gyda negeseuon heb eu darllen yn gyntaf",
|
||||||
|
"space_menu": {
|
||||||
|
"home": "Cartref gofod",
|
||||||
|
"space_settings": "Gosodiadau Gofod"
|
||||||
|
},
|
||||||
"space_menu_label": "Dewislen %(spaceName)s",
|
"space_menu_label": "Dewislen %(spaceName)s",
|
||||||
"sublist_options": "Rhestrwch opsiynau",
|
"sublist_options": "Rhestrwch opsiynau",
|
||||||
"suggested_rooms_heading": "Ystafelloedd a Awgrymir"
|
"suggested_rooms_heading": "Ystafelloedd a Awgrymir"
|
||||||
@@ -3536,7 +3544,6 @@
|
|||||||
"input_devices": "Dyfeisiau mewnbwn",
|
"input_devices": "Dyfeisiau mewnbwn",
|
||||||
"jitsi_call": "Cynhadledd Jitsi",
|
"jitsi_call": "Cynhadledd Jitsi",
|
||||||
"join_button_tooltip_call_full": "Mae'n ddrwg gennym - mae'r alwad hon yn llawn ar hyn o bryd",
|
"join_button_tooltip_call_full": "Mae'n ddrwg gennym - mae'r alwad hon yn llawn ar hyn o bryd",
|
||||||
"join_button_tooltip_connecting": "Yn cysylltu",
|
|
||||||
"legacy_call": "Galwad Etifeddiaeth",
|
"legacy_call": "Galwad Etifeddiaeth",
|
||||||
"maximise": "Llanw'r Sgrin",
|
"maximise": "Llanw'r Sgrin",
|
||||||
"maximise_call": "Mwyhau galwad",
|
"maximise_call": "Mwyhau galwad",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"a11y": {
|
"a11y": {
|
||||||
"emoji_picker": "Emoji-Auswahl",
|
"emoji_picker": "Emoji-Picker",
|
||||||
"jump_first_invite": "Zur ersten Einladung springen.",
|
"jump_first_invite": "Zur ersten Einladung springen.",
|
||||||
"message_composer": "Nachrichteneingabe-Feld",
|
"message_composer": "Nachrichteneingabe-Feld",
|
||||||
"n_unread_messages": {
|
"n_unread_messages": {
|
||||||
@@ -80,12 +80,14 @@
|
|||||||
"maximise": "Maximieren",
|
"maximise": "Maximieren",
|
||||||
"mention": "Erwähnen",
|
"mention": "Erwähnen",
|
||||||
"minimise": "Minimieren",
|
"minimise": "Minimieren",
|
||||||
|
"new_message": "Neue Nachricht",
|
||||||
"new_room": "Neuer Raum",
|
"new_room": "Neuer Raum",
|
||||||
"new_video_room": "Neuer Videoraum",
|
"new_video_room": "Neuer Videoraum",
|
||||||
"next": "Weiter",
|
"next": "Weiter",
|
||||||
"no": "Nein",
|
"no": "Nein",
|
||||||
"ok": "Ok",
|
"ok": "Ok",
|
||||||
"open": "Öffnen",
|
"open": "Öffnen",
|
||||||
|
"open_menu": "Menu öffnen",
|
||||||
"pause": "Pausieren",
|
"pause": "Pausieren",
|
||||||
"pin": "Anheften",
|
"pin": "Anheften",
|
||||||
"play": "Abspielen",
|
"play": "Abspielen",
|
||||||
@@ -405,7 +407,6 @@
|
|||||||
"download_logs": "Protokolle herunterladen",
|
"download_logs": "Protokolle herunterladen",
|
||||||
"downloading_logs": "Lade Protokolle herunter",
|
"downloading_logs": "Lade Protokolle herunter",
|
||||||
"error_empty": "Bitte teile uns mit, was schief lief - oder besser, beschreibe das Problem auf GitHub in einem \"Issue\".",
|
"error_empty": "Bitte teile uns mit, was schief lief - oder besser, beschreibe das Problem auf GitHub in einem \"Issue\".",
|
||||||
"failed_send_logs": "Senden von Protokolldateien fehlgeschlagen: ",
|
|
||||||
"github_issue": "GitHub-Problem",
|
"github_issue": "GitHub-Problem",
|
||||||
"introduction": "Wenn du uns einen Bug auf GitHub gemeldet hast, können uns Debug-Logs helfen, das Problem zu finden. ",
|
"introduction": "Wenn du uns einen Bug auf GitHub gemeldet hast, können uns Debug-Logs helfen, das Problem zu finden. ",
|
||||||
"log_request": "Um uns zu helfen, dies in Zukunft zu vermeiden, <a>sende uns bitte die Protokolldateien</a>.",
|
"log_request": "Um uns zu helfen, dies in Zukunft zu vermeiden, <a>sende uns bitte die Protokolldateien</a>.",
|
||||||
@@ -495,7 +496,6 @@
|
|||||||
"legal": "Rechtliches",
|
"legal": "Rechtliches",
|
||||||
"light": "Hell",
|
"light": "Hell",
|
||||||
"loading": "Lade …",
|
"loading": "Lade …",
|
||||||
"lobby": "Lobby",
|
|
||||||
"location": "Standort",
|
"location": "Standort",
|
||||||
"low_priority": "Niedrige Priorität",
|
"low_priority": "Niedrige Priorität",
|
||||||
"matrix": "Matrix",
|
"matrix": "Matrix",
|
||||||
@@ -1017,8 +1017,8 @@
|
|||||||
"sas_description": "Vergleiche eine einmalige Reihe von Emojis, sofern du an keinem Gerät eine Kamera hast",
|
"sas_description": "Vergleiche eine einmalige Reihe von Emojis, sofern du an keinem Gerät eine Kamera hast",
|
||||||
"sas_emoji_caption_self": "Bestätige, dass die folgenden Emoji auf beiden Geräten in der gleichen Reihenfolge angezeigt werden:",
|
"sas_emoji_caption_self": "Bestätige, dass die folgenden Emoji auf beiden Geräten in der gleichen Reihenfolge angezeigt werden:",
|
||||||
"sas_emoji_caption_user": "Verifiziere diesen Nutzer, indem du bestätigst, dass folgende Emojis auf dessen Bildschirm erscheinen.",
|
"sas_emoji_caption_user": "Verifiziere diesen Nutzer, indem du bestätigst, dass folgende Emojis auf dessen Bildschirm erscheinen.",
|
||||||
"sas_match": "Sie passen zueinander",
|
"sas_match": "Sie stimmen überein",
|
||||||
"sas_no_match": "Sie passen nicht zueinander",
|
"sas_no_match": "Sie stimmen nicht überein",
|
||||||
"sas_prompt": "Vergleiche einzigartige Emojis",
|
"sas_prompt": "Vergleiche einzigartige Emojis",
|
||||||
"scan_qr": "Verifizierung durch Scannen eines QR-Codes",
|
"scan_qr": "Verifizierung durch Scannen eines QR-Codes",
|
||||||
"scan_qr_explainer": "Bitte %(displayName)s, deinen Code zu scannen:",
|
"scan_qr_explainer": "Bitte %(displayName)s, deinen Code zu scannen:",
|
||||||
@@ -1243,6 +1243,7 @@
|
|||||||
"change": "Identitäts-Server wechseln",
|
"change": "Identitäts-Server wechseln",
|
||||||
"change_prompt": "Vom Identitäts-Server <current /> trennen, und stattdessen mit <new /> verbinden?",
|
"change_prompt": "Vom Identitäts-Server <current /> trennen, und stattdessen mit <new /> verbinden?",
|
||||||
"change_server_prompt": "Wenn du <server /> nicht verwenden willst, um Kontakte zu finden und von anderen gefunden zu werden, trage unten einen anderen Identitäts-Server ein.",
|
"change_server_prompt": "Wenn du <server /> nicht verwenden willst, um Kontakte zu finden und von anderen gefunden zu werden, trage unten einen anderen Identitäts-Server ein.",
|
||||||
|
"changed": "Ihr Identitätsserver wurde geändert",
|
||||||
"checking": "Überprüfe Server",
|
"checking": "Überprüfe Server",
|
||||||
"description_connected": "Zurzeit verwendest du <server></server>, um Kontakte zu finden und von anderen gefunden zu werden. Du kannst deinen Identitäts-Server nachfolgend wechseln.",
|
"description_connected": "Zurzeit verwendest du <server></server>, um Kontakte zu finden und von anderen gefunden zu werden. Du kannst deinen Identitäts-Server nachfolgend wechseln.",
|
||||||
"description_disconnected": "Zurzeit benutzt du keinen Identitäts-Server. Trage unten einen Server ein, um Kontakte zu finden und von anderen gefunden zu werden.",
|
"description_disconnected": "Zurzeit benutzt du keinen Identitäts-Server. Trage unten einen Server ein, um Kontakte zu finden und von anderen gefunden zu werden.",
|
||||||
@@ -1285,7 +1286,9 @@
|
|||||||
"title": "Nicht unterstützter Browser",
|
"title": "Nicht unterstützter Browser",
|
||||||
"use_desktop_heading": "Verwende stattdessen %(brand)s Desktop",
|
"use_desktop_heading": "Verwende stattdessen %(brand)s Desktop",
|
||||||
"use_mobile_heading": "Stattdessen %(brand)s am Smartphone benutzen",
|
"use_mobile_heading": "Stattdessen %(brand)s am Smartphone benutzen",
|
||||||
"use_mobile_heading_after_desktop": "Oder verwende die mobile App"
|
"use_mobile_heading_after_desktop": "Oder verwende die mobile App",
|
||||||
|
"windows_64bit": "Windows (64-Bit)",
|
||||||
|
"windows_arm_64bit": "Windows (ARM 64-Bit)"
|
||||||
},
|
},
|
||||||
"info_tooltip_title": "Information",
|
"info_tooltip_title": "Information",
|
||||||
"integration_manager": {
|
"integration_manager": {
|
||||||
@@ -2092,6 +2095,7 @@
|
|||||||
"other": "Betrete %(count)s Räume"
|
"other": "Betrete %(count)s Räume"
|
||||||
},
|
},
|
||||||
"notification_options": "Benachrichtigungsoptionen",
|
"notification_options": "Benachrichtigungsoptionen",
|
||||||
|
"open_space_menu": "Menü „Raum öffnen“",
|
||||||
"redacting_messages_status": {
|
"redacting_messages_status": {
|
||||||
"one": "Entferne Nachrichten in %(count)s Raum",
|
"one": "Entferne Nachrichten in %(count)s Raum",
|
||||||
"other": "Entferne Nachrichten in %(count)s Räumen"
|
"other": "Entferne Nachrichten in %(count)s Räumen"
|
||||||
@@ -2106,6 +2110,10 @@
|
|||||||
"sort_by_activity": "Aktivität",
|
"sort_by_activity": "Aktivität",
|
||||||
"sort_by_alphabet": "A–Z",
|
"sort_by_alphabet": "A–Z",
|
||||||
"sort_unread_first": "Räume mit ungelesenen Nachrichten zuerst zeigen",
|
"sort_unread_first": "Räume mit ungelesenen Nachrichten zuerst zeigen",
|
||||||
|
"space_menu": {
|
||||||
|
"home": "Space-Übersicht",
|
||||||
|
"space_settings": "Raumeinstellungen"
|
||||||
|
},
|
||||||
"space_menu_label": "%(spaceName)s-Menü",
|
"space_menu_label": "%(spaceName)s-Menü",
|
||||||
"sublist_options": "Optionen anzeigen",
|
"sublist_options": "Optionen anzeigen",
|
||||||
"suggested_rooms_heading": "Vorgeschlagene Räume"
|
"suggested_rooms_heading": "Vorgeschlagene Räume"
|
||||||
@@ -2468,12 +2476,14 @@
|
|||||||
"breadcrumb_title_forgot": "Haben Sie Ihren Wiederherstellungsschlüssel vergessen? Sie müssen Ihre Identität zurücksetzen.",
|
"breadcrumb_title_forgot": "Haben Sie Ihren Wiederherstellungsschlüssel vergessen? Sie müssen Ihre Identität zurücksetzen.",
|
||||||
"breadcrumb_warning": "Tun Sie dies nur, wenn Sie glauben, dass Ihr Konto kompromittiert wurde.",
|
"breadcrumb_warning": "Tun Sie dies nur, wenn Sie glauben, dass Ihr Konto kompromittiert wurde.",
|
||||||
"details_title": "Angaben zur Verschlüsselung",
|
"details_title": "Angaben zur Verschlüsselung",
|
||||||
|
"do_not_close_warning": "Schließen Sie dieses Fenster nicht, bis der Reset abgeschlossen ist",
|
||||||
"export_keys": "Schlüssel exportieren",
|
"export_keys": "Schlüssel exportieren",
|
||||||
"import_keys": "Schlüssel importieren",
|
"import_keys": "Schlüssel importieren",
|
||||||
"other_people_device_description": "Senden Sie in verschlüsselten Räumen standardmäßig keine verschlüsselten Nachrichten an Dritte, bis Sie diese verifiziert haben",
|
"other_people_device_description": "Senden Sie in verschlüsselten Räumen standardmäßig keine verschlüsselten Nachrichten an Dritte, bis Sie diese verifiziert haben",
|
||||||
"other_people_device_label": "Senden Sie niemals verschlüsselte Nachrichten an nicht verifizierte Geräte",
|
"other_people_device_label": "Senden Sie niemals verschlüsselte Nachrichten an nicht verifizierte Geräte",
|
||||||
"other_people_device_title": "Geräte anderer Personen",
|
"other_people_device_title": "Geräte anderer Personen",
|
||||||
"reset_identity": "Kryptografische Identität zurücksetzen",
|
"reset_identity": "Kryptografische Identität zurücksetzen",
|
||||||
|
"reset_in_progress": "Der Reset wird ausgeführt",
|
||||||
"session_id": "Sitzungs-ID:",
|
"session_id": "Sitzungs-ID:",
|
||||||
"session_key": "Sitzungsschlüssel:",
|
"session_key": "Sitzungsschlüssel:",
|
||||||
"title": "Advanced"
|
"title": "Advanced"
|
||||||
@@ -3896,7 +3906,6 @@
|
|||||||
"input_devices": "Eingabegeräte",
|
"input_devices": "Eingabegeräte",
|
||||||
"jitsi_call": "Jitsi-Konferenz",
|
"jitsi_call": "Jitsi-Konferenz",
|
||||||
"join_button_tooltip_call_full": "Entschuldigung — dieser Anruf ist aktuell besetzt",
|
"join_button_tooltip_call_full": "Entschuldigung — dieser Anruf ist aktuell besetzt",
|
||||||
"join_button_tooltip_connecting": "Verbinden",
|
|
||||||
"legacy_call": "Legacy-Anruf",
|
"legacy_call": "Legacy-Anruf",
|
||||||
"maximise": "Bildschirm füllen",
|
"maximise": "Bildschirm füllen",
|
||||||
"maximise_call": "Anruf maximieren",
|
"maximise_call": "Anruf maximieren",
|
||||||
|
|||||||
@@ -316,7 +316,6 @@
|
|||||||
"download_logs": "Λήψη αρχείων καταγραφής",
|
"download_logs": "Λήψη αρχείων καταγραφής",
|
||||||
"downloading_logs": "Λήψη αρχείων καταγραφής",
|
"downloading_logs": "Λήψη αρχείων καταγραφής",
|
||||||
"error_empty": "Πείτε μας τι πήγε στραβά ή, καλύτερα, δημιουργήστε ένα ζήτημα στο GitHub που να περιγράφει το πρόβλημα.",
|
"error_empty": "Πείτε μας τι πήγε στραβά ή, καλύτερα, δημιουργήστε ένα ζήτημα στο GitHub που να περιγράφει το πρόβλημα.",
|
||||||
"failed_send_logs": "Αποτυχία αποστολής αρχείων καταγραφής: ",
|
|
||||||
"github_issue": "Ζήτημα GitHub",
|
"github_issue": "Ζήτημα GitHub",
|
||||||
"introduction": "Εάν έχετε υποβάλει ένα σφάλμα μέσω του GitHub, τα αρχεία καταγραφής εντοπισμού σφαλμάτων μπορούν να μας βοηθήσουν να εντοπίσουμε το πρόβλημα. ",
|
"introduction": "Εάν έχετε υποβάλει ένα σφάλμα μέσω του GitHub, τα αρχεία καταγραφής εντοπισμού σφαλμάτων μπορούν να μας βοηθήσουν να εντοπίσουμε το πρόβλημα. ",
|
||||||
"log_request": "Για να μας βοηθήσετε να το αποτρέψουμε αυτό στο μέλλον, <a>στείλτε μας τα αρχεία καταγραφής</a>.",
|
"log_request": "Για να μας βοηθήσετε να το αποτρέψουμε αυτό στο μέλλον, <a>στείλτε μας τα αρχεία καταγραφής</a>.",
|
||||||
@@ -3137,7 +3136,6 @@
|
|||||||
"hangup": "Κλείσιμο",
|
"hangup": "Κλείσιμο",
|
||||||
"hide_sidebar_button": "Απόκρυψη πλαϊνής μπάρας",
|
"hide_sidebar_button": "Απόκρυψη πλαϊνής μπάρας",
|
||||||
"input_devices": "Συσκευές εισόδου",
|
"input_devices": "Συσκευές εισόδου",
|
||||||
"join_button_tooltip_connecting": "Συνδέεται",
|
|
||||||
"maximise": "Γέμισμα οθόνης",
|
"maximise": "Γέμισμα οθόνης",
|
||||||
"misconfigured_server": "Η κλήση απέτυχε λόγω της λανθασμένης διάρθρωσης του διακομιστή",
|
"misconfigured_server": "Η κλήση απέτυχε λόγω της λανθασμένης διάρθρωσης του διακομιστή",
|
||||||
"misconfigured_server_description": "Παρακαλείστε να ρωτήσετε τον διαχειριστή του κεντρικού διακομιστή σας (<code>%(homeserverDomain)s</code>) να ρυθμίσουν έναν διακομιστή πρωτοκόλλου TURN ώστε οι κλήσεις να λειτουργούν απρόσκοπτα.",
|
"misconfigured_server_description": "Παρακαλείστε να ρωτήσετε τον διαχειριστή του κεντρικού διακομιστή σας (<code>%(homeserverDomain)s</code>) να ρυθμίσουν έναν διακομιστή πρωτοκόλλου TURN ώστε οι κλήσεις να λειτουργούν απρόσκοπτα.",
|
||||||
|
|||||||
@@ -407,7 +407,15 @@
|
|||||||
"download_logs": "Download logs",
|
"download_logs": "Download logs",
|
||||||
"downloading_logs": "Downloading logs",
|
"downloading_logs": "Downloading logs",
|
||||||
"error_empty": "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.",
|
"error_empty": "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.",
|
||||||
"failed_send_logs": "Failed to send logs: ",
|
"failed_download_logs": "Failed to download debug logs: ",
|
||||||
|
"failed_send_logs_causes": {
|
||||||
|
"disallowed_app": "Your bug report was rejected. The rageshake server does not support this application.",
|
||||||
|
"rejected_generic": "Your bug report was rejected. The rageshake server rejected the contents of the report due to a policy.",
|
||||||
|
"rejected_recovery_key": "Your bug report was rejected for safety reasons, as it contained a recovery key.",
|
||||||
|
"rejected_version": "Your bug report was rejected as the version you are running is too old.",
|
||||||
|
"server_unknown_error": "The rageshake server encountered an unknown error and could not handle the report.",
|
||||||
|
"unknown_error": "Failed to send logs."
|
||||||
|
},
|
||||||
"github_issue": "GitHub issue",
|
"github_issue": "GitHub issue",
|
||||||
"introduction": "If you've submitted a bug via GitHub, debug logs can help us track down the problem. ",
|
"introduction": "If you've submitted a bug via GitHub, debug logs can help us track down the problem. ",
|
||||||
"log_request": "To help us prevent this in future, please <a>send us logs</a>.",
|
"log_request": "To help us prevent this in future, please <a>send us logs</a>.",
|
||||||
@@ -497,7 +505,6 @@
|
|||||||
"legal": "Legal",
|
"legal": "Legal",
|
||||||
"light": "Light",
|
"light": "Light",
|
||||||
"loading": "Loading…",
|
"loading": "Loading…",
|
||||||
"lobby": "Lobby",
|
|
||||||
"location": "Location",
|
"location": "Location",
|
||||||
"low_priority": "Low priority",
|
"low_priority": "Low priority",
|
||||||
"matrix": "Matrix",
|
"matrix": "Matrix",
|
||||||
@@ -1299,6 +1306,7 @@
|
|||||||
"error_connecting_heading": "Cannot connect to integration manager",
|
"error_connecting_heading": "Cannot connect to integration manager",
|
||||||
"explainer": "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.",
|
"explainer": "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.",
|
||||||
"manage_title": "Manage integrations",
|
"manage_title": "Manage integrations",
|
||||||
|
"toggle_label": "Enable the integration manager",
|
||||||
"use_im": "Use an integration manager to manage bots, widgets, and sticker packs.",
|
"use_im": "Use an integration manager to manage bots, widgets, and sticker packs.",
|
||||||
"use_im_default": "Use an integration manager <b>(%(serverName)s)</b> to manage bots, widgets, and sticker packs."
|
"use_im_default": "Use an integration manager <b>(%(serverName)s)</b> to manage bots, widgets, and sticker packs."
|
||||||
},
|
},
|
||||||
@@ -2091,18 +2099,29 @@
|
|||||||
"failed_add_tag": "Failed to add tag %(tagName)s to room",
|
"failed_add_tag": "Failed to add tag %(tagName)s to room",
|
||||||
"failed_remove_tag": "Failed to remove tag %(tagName)s from room",
|
"failed_remove_tag": "Failed to remove tag %(tagName)s from room",
|
||||||
"failed_set_dm_tag": "Failed to set direct message tag",
|
"failed_set_dm_tag": "Failed to set direct message tag",
|
||||||
|
"filters": {
|
||||||
|
"favourite": "Favourites",
|
||||||
|
"people": "People",
|
||||||
|
"rooms": "Rooms",
|
||||||
|
"unread": "Unread"
|
||||||
|
},
|
||||||
"home_menu_label": "Home options",
|
"home_menu_label": "Home options",
|
||||||
"join_public_room_label": "Join public room",
|
"join_public_room_label": "Join public room",
|
||||||
"joining_rooms_status": {
|
"joining_rooms_status": {
|
||||||
"one": "Currently joining %(count)s room",
|
"one": "Currently joining %(count)s room",
|
||||||
"other": "Currently joining %(count)s rooms"
|
"other": "Currently joining %(count)s rooms"
|
||||||
},
|
},
|
||||||
|
"list_title": "Room list",
|
||||||
"notification_options": "Notification options",
|
"notification_options": "Notification options",
|
||||||
"open_space_menu": "Open space menu",
|
"open_space_menu": "Open space menu",
|
||||||
|
"primary_filters": "Room list filters",
|
||||||
"redacting_messages_status": {
|
"redacting_messages_status": {
|
||||||
"one": "Currently removing messages in %(count)s room",
|
"one": "Currently removing messages in %(count)s room",
|
||||||
"other": "Currently removing messages in %(count)s rooms"
|
"other": "Currently removing messages in %(count)s rooms"
|
||||||
},
|
},
|
||||||
|
"room": {
|
||||||
|
"open_room": "Open room %(roomName)s"
|
||||||
|
},
|
||||||
"show_less": "Show less",
|
"show_less": "Show less",
|
||||||
"show_n_more": {
|
"show_n_more": {
|
||||||
"one": "Show %(count)s more",
|
"one": "Show %(count)s more",
|
||||||
@@ -2479,12 +2498,14 @@
|
|||||||
"breadcrumb_title_forgot": "Forgot your recovery key? You’ll need to reset your identity.",
|
"breadcrumb_title_forgot": "Forgot your recovery key? You’ll need to reset your identity.",
|
||||||
"breadcrumb_warning": "Only do this if you believe your account has been compromised.",
|
"breadcrumb_warning": "Only do this if you believe your account has been compromised.",
|
||||||
"details_title": "Encryption details",
|
"details_title": "Encryption details",
|
||||||
|
"do_not_close_warning": "Do not close this window until the reset is finished",
|
||||||
"export_keys": "Export keys",
|
"export_keys": "Export keys",
|
||||||
"import_keys": "Import keys",
|
"import_keys": "Import keys",
|
||||||
"other_people_device_description": "By default in encrypted rooms, do not send encrypted messages to anyone until you’ve verified them",
|
"other_people_device_description": "By default in encrypted rooms, do not send encrypted messages to anyone until you’ve verified them",
|
||||||
"other_people_device_label": "Never send encrypted messages to unverified devices",
|
"other_people_device_label": "Never send encrypted messages to unverified devices",
|
||||||
"other_people_device_title": "Other people’s devices",
|
"other_people_device_title": "Other people’s devices",
|
||||||
"reset_identity": "Reset cryptographic identity",
|
"reset_identity": "Reset cryptographic identity",
|
||||||
|
"reset_in_progress": "Reset in progress...",
|
||||||
"session_id": "Session ID:",
|
"session_id": "Session ID:",
|
||||||
"session_key": "Session key:",
|
"session_key": "Session key:",
|
||||||
"title": "Advanced"
|
"title": "Advanced"
|
||||||
@@ -3907,7 +3928,6 @@
|
|||||||
"input_devices": "Input devices",
|
"input_devices": "Input devices",
|
||||||
"jitsi_call": "Jitsi Conference",
|
"jitsi_call": "Jitsi Conference",
|
||||||
"join_button_tooltip_call_full": "Sorry — this call is currently full",
|
"join_button_tooltip_call_full": "Sorry — this call is currently full",
|
||||||
"join_button_tooltip_connecting": "Connecting",
|
|
||||||
"legacy_call": "Legacy Call",
|
"legacy_call": "Legacy Call",
|
||||||
"maximise": "Fill screen",
|
"maximise": "Fill screen",
|
||||||
"maximise_call": "Maximise call",
|
"maximise_call": "Maximise call",
|
||||||
|
|||||||
@@ -301,7 +301,6 @@
|
|||||||
"download_logs": "Elŝuti protokolon",
|
"download_logs": "Elŝuti protokolon",
|
||||||
"downloading_logs": "Elŝutante protokolon",
|
"downloading_logs": "Elŝutante protokolon",
|
||||||
"error_empty": "Bonvolu diri al ni kio misokazis, aŭ pli bone raporti problemon per GitHub.",
|
"error_empty": "Bonvolu diri al ni kio misokazis, aŭ pli bone raporti problemon per GitHub.",
|
||||||
"failed_send_logs": "Malsukcesis sendi protokolon: ",
|
|
||||||
"github_issue": "Problemo per GitHub",
|
"github_issue": "Problemo per GitHub",
|
||||||
"log_request": "Por malhelpi tion ose, bonvolu <a>sendi al ni protokolon</a>.",
|
"log_request": "Por malhelpi tion ose, bonvolu <a>sendi al ni protokolon</a>.",
|
||||||
"logs_sent": "Protokolo sendiĝis",
|
"logs_sent": "Protokolo sendiĝis",
|
||||||
@@ -2620,7 +2619,6 @@
|
|||||||
"expand": "Reveni al voko",
|
"expand": "Reveni al voko",
|
||||||
"hangup": "Fini vokon",
|
"hangup": "Fini vokon",
|
||||||
"hide_sidebar_button": "Kaŝi flankan breton",
|
"hide_sidebar_button": "Kaŝi flankan breton",
|
||||||
"join_button_tooltip_connecting": "Konektante",
|
|
||||||
"misconfigured_server": "Voko malsukcesis pro misagordita servilo",
|
"misconfigured_server": "Voko malsukcesis pro misagordita servilo",
|
||||||
"misconfigured_server_description": "Bonvolu peti la administranton de via hejmservilo (<code>%(homeserverDomain)s</code>) agordi TURN-servilon, por ke vokoj funkciu dependeble.",
|
"misconfigured_server_description": "Bonvolu peti la administranton de via hejmservilo (<code>%(homeserverDomain)s</code>) agordi TURN-servilon, por ke vokoj funkciu dependeble.",
|
||||||
"more_button": "Pli",
|
"more_button": "Pli",
|
||||||
|
|||||||