Compare commits
2 Commits
hs/a11y-el
...
robin/call
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
569d525c6e | ||
|
|
7799cb2ec5 |
65
.github/workflows/docker.yaml
vendored
@@ -3,7 +3,6 @@ on:
|
|||||||
workflow_dispatch: {}
|
workflow_dispatch: {}
|
||||||
push:
|
push:
|
||||||
tags: [v*]
|
tags: [v*]
|
||||||
pull_request: {}
|
|
||||||
schedule:
|
schedule:
|
||||||
# This job can take a while, and we have usage limits, so just publish develop only twice a day
|
# This job can take a while, and we have usage limits, so just publish develop only twice a day
|
||||||
- cron: "0 7/12 * * *"
|
- cron: "0 7/12 * * *"
|
||||||
@@ -13,91 +12,42 @@ jobs:
|
|||||||
buildx:
|
buildx:
|
||||||
name: Docker Buildx
|
name: Docker Buildx
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
environment: ${{ github.event_name != 'pull_request' && 'dockerhub' || '' }}
|
environment: dockerhub
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write # needed for signing the images with GitHub OIDC Token
|
id-token: write # needed for signing the images with GitHub OIDC Token
|
||||||
packages: write # needed for publishing packages to GHCR
|
packages: write # needed for publishing packages to GHCR
|
||||||
env:
|
|
||||||
TEST_TAG: vectorim/element-web:test
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
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@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3
|
uses: sigstore/cosign-installer@c56c2d3e59e4281cc41dea2217323ba5694b171e # v3
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3
|
uses: docker/setup-qemu-action@4574d27a4764455b42196d70a065bc6853246a25 # v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3
|
||||||
with:
|
with:
|
||||||
install: true
|
install: true
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
|
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
|
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and load
|
|
||||||
id: test-build
|
|
||||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
load: true
|
|
||||||
|
|
||||||
- name: Test the image
|
|
||||||
env:
|
|
||||||
IMAGEID: ${{ steps.test-build.outputs.imageid }}
|
|
||||||
timeout-minutes: 2
|
|
||||||
run: |
|
|
||||||
set -x
|
|
||||||
|
|
||||||
# Make a fake module to test the image
|
|
||||||
MODULE_PATH="modules/module_name/index.js"
|
|
||||||
mkdir -p $(dirname $MODULE_PATH)
|
|
||||||
echo 'alert("Testing");' > $MODULE_PATH
|
|
||||||
|
|
||||||
# Spin up a container of the image
|
|
||||||
ELEMENT_WEB_PORT=8181
|
|
||||||
CONTAINER_ID=$(
|
|
||||||
docker run \
|
|
||||||
--rm \
|
|
||||||
-e "ELEMENT_WEB_PORT=$ELEMENT_WEB_PORT" \
|
|
||||||
-dp "$ELEMENT_WEB_PORT:$ELEMENT_WEB_PORT" \
|
|
||||||
-v $(pwd)/modules:/tmp/element-web-modules \
|
|
||||||
"$IMAGEID" \
|
|
||||||
)
|
|
||||||
|
|
||||||
# Run some smoke tests
|
|
||||||
wget --retry-connrefused --tries=5 -q --wait=3 --spider "http://localhost:$ELEMENT_WEB_PORT/modules/module_name/index.js"
|
|
||||||
MODULE_0=$(curl "http://localhost:$ELEMENT_WEB_PORT/config.json" | jq -r .modules[0])
|
|
||||||
test "$MODULE_0" = "/${MODULE_PATH}"
|
|
||||||
|
|
||||||
# Check healthcheck
|
|
||||||
until test "$(docker inspect -f {{.State.Health.Status}} $CONTAINER_ID)" == "healthy"; do
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
docker stop "$CONTAINER_ID"
|
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
|
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
vectorim/element-web
|
vectorim/element-web
|
||||||
@@ -110,8 +60,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: build-and-push
|
id: build-and-push
|
||||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
|
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
@@ -123,7 +72,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
DIGEST: ${{ steps.build-and-push.outputs.digest }}
|
DIGEST: ${{ steps.build-and-push.outputs.digest }}
|
||||||
TAGS: ${{ steps.meta.outputs.tags }}
|
TAGS: ${{ steps.meta.outputs.tags }}
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
run: |
|
run: |
|
||||||
images=""
|
images=""
|
||||||
for tag in ${TAGS}; do
|
for tag in ${TAGS}; do
|
||||||
@@ -133,7 +81,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Update repo description
|
- name: Update repo description
|
||||||
uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4
|
uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
id: cpr
|
id: cpr
|
||||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7
|
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
branch: actions/playwright-image-updates
|
branch: actions/playwright-image-updates
|
||||||
|
|||||||
1
.github/workflows/release.yml
vendored
@@ -19,7 +19,6 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
issues: write
|
issues: write
|
||||||
pull-requests: read
|
pull-requests: read
|
||||||
id-token: write
|
|
||||||
secrets:
|
secrets:
|
||||||
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
|
|||||||
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@5ef6e175c333bc629f3718b083c8a2ff6e0bbfbc
|
uses: guibranco/github-status-action-v2@7ca807c2ba3401be532d29a876b93262108099fb
|
||||||
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@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7
|
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
branch: actions/jitsi-update
|
branch: actions/jitsi-update
|
||||||
|
|||||||
62
CHANGELOG.md
@@ -1,65 +1,3 @@
|
|||||||
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)
|
|
||||||
==================================================================================================
|
|
||||||
## ✨ Features
|
|
||||||
|
|
||||||
* [backport] Dynamically load Element Web modules in Docker entrypoint ([#29358](https://github.com/element-hq/element-web/pull/29358)). Contributed by @t3chguy.
|
|
||||||
* ChangeRecoveryKey: error handling ([#29262](https://github.com/element-hq/element-web/pull/29262)). Contributed by @richvdh.
|
|
||||||
* Dehydration: enable dehydrated device on "Set up recovery" ([#29265](https://github.com/element-hq/element-web/pull/29265)). Contributed by @richvdh.
|
|
||||||
* Render reason for invite rejection. ([#29257](https://github.com/element-hq/element-web/pull/29257)). Contributed by @Half-Shot.
|
|
||||||
* New room list: add search section ([#29251](https://github.com/element-hq/element-web/pull/29251)). Contributed by @florianduros.
|
|
||||||
* New room list: hide favourites and people meta spaces ([#29241](https://github.com/element-hq/element-web/pull/29241)). Contributed by @florianduros.
|
|
||||||
* New Room List: Create new labs flag ([#29239](https://github.com/element-hq/element-web/pull/29239)). Contributed by @MidhunSureshR.
|
|
||||||
* Stop URl preview from covering message box ([#29215](https://github.com/element-hq/element-web/pull/29215)). Contributed by @edent.
|
|
||||||
* Rename "security key" into "recovery key" ([#29217](https://github.com/element-hq/element-web/pull/29217)). Contributed by @florianduros.
|
|
||||||
* Add new verification section to user profile ([#29200](https://github.com/element-hq/element-web/pull/29200)). Contributed by @MidhunSureshR.
|
|
||||||
* Initial support for runtime modules ([#29104](https://github.com/element-hq/element-web/pull/29104)). Contributed by @t3chguy.
|
|
||||||
* Add `Forgot recovery key?` button to encryption tab ([#29202](https://github.com/element-hq/element-web/pull/29202)). Contributed by @florianduros.
|
|
||||||
* Add KeyIcon to key storage out of sync toast ([#29201](https://github.com/element-hq/element-web/pull/29201)). Contributed by @florianduros.
|
|
||||||
* Improve rendering of empty topics in the timeline ([#29152](https://github.com/element-hq/element-web/pull/29152)). Contributed by @Half-Shot.
|
|
||||||
|
|
||||||
## 🐛 Bug Fixes
|
|
||||||
|
|
||||||
* Fix font scaling in member list ([#29285](https://github.com/element-hq/element-web/pull/29285)). Contributed by @florianduros.
|
|
||||||
* Grow member list search field when resizing the right panel ([#29267](https://github.com/element-hq/element-web/pull/29267)). Contributed by @langleyd.
|
|
||||||
* Don't reload roomview on offline connectivity check ([#29243](https://github.com/element-hq/element-web/pull/29243)). Contributed by @dbkr.
|
|
||||||
* Respect user's 12/24 hour preference consistently ([#29237](https://github.com/element-hq/element-web/pull/29237)). Contributed by @t3chguy.
|
|
||||||
* Restore the accessibility role on call views ([#29225](https://github.com/element-hq/element-web/pull/29225)). Contributed by @robintown.
|
|
||||||
* Revert `GoToHome` keyboard shortcut to `Ctrl`–`Shift`–`H` on macOS ([#28577](https://github.com/element-hq/element-web/pull/28577)). Contributed by @gy-mate.
|
|
||||||
* Encryption tab: display correct encryption panel when user cancels the reset identity flow ([#29216](https://github.com/element-hq/element-web/pull/29216)). Contributed by @florianduros.
|
|
||||||
|
|
||||||
|
|
||||||
Changes in [1.11.92](https://github.com/element-hq/element-web/releases/tag/v1.11.92) (2025-02-11)
|
Changes in [1.11.92](https://github.com/element-hq/element-web/releases/tag/v1.11.92) (2025-02-11)
|
||||||
==================================================================================================
|
==================================================================================================
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|||||||
10
Dockerfile
@@ -1,5 +1,3 @@
|
|||||||
# 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
|
||||||
|
|
||||||
@@ -10,7 +8,7 @@ ARG JS_SDK_BRANCH="master"
|
|||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
COPY --exclude=docker . /src
|
COPY . /src
|
||||||
RUN /src/scripts/docker-link-repos.sh
|
RUN /src/scripts/docker-link-repos.sh
|
||||||
RUN yarn --network-timeout=200000 install
|
RUN yarn --network-timeout=200000 install
|
||||||
RUN /src/scripts/docker-package.sh
|
RUN /src/scripts/docker-package.sh
|
||||||
@@ -21,15 +19,11 @@ RUN cp /src/config.sample.json /src/webapp/config.json
|
|||||||
# App
|
# App
|
||||||
FROM nginx:alpine-slim
|
FROM nginx:alpine-slim
|
||||||
|
|
||||||
# Install jq and moreutils for sponge, both used by our entrypoints
|
|
||||||
RUN apk add jq moreutils
|
|
||||||
|
|
||||||
COPY --from=builder /src/webapp /app
|
COPY --from=builder /src/webapp /app
|
||||||
|
|
||||||
# Override default nginx config. Templates in `/etc/nginx/templates` are passed
|
# Override default nginx config. Templates in `/etc/nginx/templates` are passed
|
||||||
# through `envsubst` by the nginx docker image entry point.
|
# through `envsubst` by the nginx docker image entry point.
|
||||||
COPY /docker/nginx-templates/* /etc/nginx/templates/
|
COPY /docker/nginx-templates/* /etc/nginx/templates/
|
||||||
COPY /docker/docker-entrypoint.d/* /docker-entrypoint.d/
|
|
||||||
|
|
||||||
# Tell nginx to put its pidfile elsewhere, so it can run as non-root
|
# Tell nginx to put its pidfile elsewhere, so it can run as non-root
|
||||||
RUN sed -i -e 's,/var/run/nginx.pid,/tmp/nginx.pid,' /etc/nginx/nginx.conf
|
RUN sed -i -e 's,/var/run/nginx.pid,/tmp/nginx.pid,' /etc/nginx/nginx.conf
|
||||||
@@ -46,5 +40,3 @@ USER nginx
|
|||||||
|
|
||||||
# HTTP listen port
|
# HTTP listen port
|
||||||
ENV ELEMENT_WEB_PORT=80
|
ENV ELEMENT_WEB_PORT=80
|
||||||
|
|
||||||
HEALTHCHECK --start-period=5s CMD wget -q --spider http://localhost:$ELEMENT_WEB_PORT/config.json
|
|
||||||
|
|||||||
@@ -31,7 +31,5 @@ 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,34 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Loads modules from `/tmp/element-web-modules` into config.json's `modules` field
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
entrypoint_log() {
|
|
||||||
if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then
|
|
||||||
echo "$@"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Copy these config files as a base
|
|
||||||
mkdir -p /tmp/element-web-config
|
|
||||||
cp /app/config*.json /tmp/element-web-config/
|
|
||||||
|
|
||||||
# If there are modules to be loaded
|
|
||||||
if [ -d "/tmp/element-web-modules" ]; then
|
|
||||||
cd /tmp/element-web-modules
|
|
||||||
|
|
||||||
for MODULE in *
|
|
||||||
do
|
|
||||||
# If the module has a package.json, use its main field as the entrypoint
|
|
||||||
ENTRYPOINT="index.js"
|
|
||||||
if [ -f "/tmp/element-web-modules/$MODULE/package.json" ]; then
|
|
||||||
ENTRYPOINT=$(jq -r '.main' "/tmp/element-web-modules/$MODULE/package.json")
|
|
||||||
fi
|
|
||||||
|
|
||||||
entrypoint_log "Loading module $MODULE with entrypoint $ENTRYPOINT"
|
|
||||||
|
|
||||||
# Append the module to the config
|
|
||||||
jq ".modules += [\"/modules/$MODULE/$ENTRYPOINT\"]" /tmp/element-web-config/config.json | sponge /tmp/element-web-config/config.json
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
@@ -18,12 +18,8 @@ server {
|
|||||||
}
|
}
|
||||||
# covers config.json and config.hostname.json requests as it is prefix.
|
# covers config.json and config.hostname.json requests as it is prefix.
|
||||||
location /config {
|
location /config {
|
||||||
root /tmp/element-web-config;
|
|
||||||
add_header Cache-Control "no-cache";
|
add_header Cache-Control "no-cache";
|
||||||
}
|
}
|
||||||
location /modules {
|
|
||||||
alias /tmp/element-web-modules;
|
|
||||||
}
|
|
||||||
# redirect server error pages to the static page /50x.html
|
# redirect server error pages to the static page /50x.html
|
||||||
#
|
#
|
||||||
error_page 500 502 503 504 /50x.html;
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
|||||||
@@ -66,18 +66,6 @@ on other runtimes may require root privileges. To resolve this, either run the
|
|||||||
image as root (`docker run --user 0`) or, better, change the port that nginx
|
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
|
|
||||||
by being made available (e.g. via bind mount) in a directory within `/tmp/element-web-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.
|
|
||||||
These modules will be presented in a `/modules` subdirectory within the webroot, and automatically added to the config.json `modules` field.
|
|
||||||
|
|
||||||
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)
|
|
||||||
but additionally include the following directories:
|
|
||||||
|
|
||||||
- /tmp/element-web-config/
|
|
||||||
- /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
|
||||||
environment variables:
|
environment variables:
|
||||||
|
|
||||||
|
|||||||
1
knip.ts
@@ -19,7 +19,6 @@ 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",
|
||||||
],
|
],
|
||||||
|
|||||||
22
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "element-web",
|
"name": "element-web",
|
||||||
"version": "1.11.95",
|
"version": "1.11.91",
|
||||||
"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.30001701",
|
"caniuse-lite": "1.0.30001699",
|
||||||
"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"
|
||||||
},
|
},
|
||||||
@@ -91,9 +91,9 @@
|
|||||||
"@sentry/browser": "^9.0.0",
|
"@sentry/browser": "^9.0.0",
|
||||||
"@types/png-chunks-extract": "^1.0.2",
|
"@types/png-chunks-extract": "^1.0.2",
|
||||||
"@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": "^3.0.0",
|
||||||
"@vector-im/compound-web": "^7.6.4",
|
"@vector-im/compound-web": "^7.6.1",
|
||||||
"@vector-im/matrix-wysiwyg": "2.38.2",
|
"@vector-im/matrix-wysiwyg": "2.38.0",
|
||||||
"@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.11.2",
|
"re-resizable": "6.10.3",
|
||||||
"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,11 +162,9 @@
|
|||||||
"@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",
|
||||||
@@ -220,12 +218,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": "^10.0.0",
|
"babel-loader": "^9.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": "^13.0.0",
|
"copy-webpack-plugin": "^12.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",
|
||||||
@@ -276,7 +274,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.2",
|
"prettier": "3.5.1",
|
||||||
"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",
|
||||||
@@ -290,7 +288,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.8.2",
|
"typescript": "5.7.3",
|
||||||
"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.51.0-noble
|
FROM mcr.microsoft.com/playwright:v1.50.1-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_LegacyRoomList").getByRole("button", { name: "Start chat" }).click();
|
await page.locator(".mx_RoomList").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,6 +22,18 @@ 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", () => {
|
||||||
@@ -104,40 +116,6 @@ test.describe("Dehydration", () => {
|
|||||||
expect(dehydratedDeviceIds.length).toBe(1);
|
expect(dehydratedDeviceIds.length).toBe(1);
|
||||||
expect(dehydratedDeviceIds[0]).not.toEqual(initialDehydratedDeviceIds[0]);
|
expect(dehydratedDeviceIds[0]).not.toEqual(initialDehydratedDeviceIds[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("'Reset cryptographic identity' removes dehydrated device", async ({ page, homeserver, app, credentials }) => {
|
|
||||||
await logIntoElement(page, credentials);
|
|
||||||
|
|
||||||
// Create a dehydrated device by setting up recovery (see "'Set up
|
|
||||||
// recovery' creates dehydrated device" test above)
|
|
||||||
const settingsDialogLocator = await app.settings.openUserSettings("Encryption");
|
|
||||||
await settingsDialogLocator.getByRole("button", { name: "Set up recovery" }).click();
|
|
||||||
|
|
||||||
// First it displays an informative panel about the recovery key
|
|
||||||
await expect(settingsDialogLocator.getByRole("heading", { name: "Set up recovery" })).toBeVisible();
|
|
||||||
await settingsDialogLocator.getByRole("button", { name: "Continue" }).click();
|
|
||||||
|
|
||||||
// Next, it displays the new recovery key. We click on the copy button.
|
|
||||||
await expect(settingsDialogLocator.getByText("Save your recovery key somewhere safe")).toBeVisible();
|
|
||||||
await settingsDialogLocator.getByRole("button", { name: "Copy" }).click();
|
|
||||||
const recoveryKey = await app.getClipboard();
|
|
||||||
await settingsDialogLocator.getByRole("button", { name: "Continue" }).click();
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
settingsDialogLocator.getByText("Enter your recovery key to confirm", { exact: true }),
|
|
||||||
).toBeVisible();
|
|
||||||
await settingsDialogLocator.getByRole("textbox").fill(recoveryKey);
|
|
||||||
await settingsDialogLocator.getByRole("button", { name: "Finish set up" }).click();
|
|
||||||
|
|
||||||
await expectDehydratedDeviceEnabled(app);
|
|
||||||
|
|
||||||
// After recovery is set up, we reset our cryptographic identity, which
|
|
||||||
// should drop the dehydrated device.
|
|
||||||
await settingsDialogLocator.getByRole("button", { name: "Reset cryptographic identity" }).click();
|
|
||||||
await settingsDialogLocator.getByRole("button", { name: "Continue" }).click();
|
|
||||||
|
|
||||||
await expectDehydratedDeviceDisabled(app);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function getDehydratedDeviceIds(client: Client): Promise<string[]> {
|
async function getDehydratedDeviceIds(client: Client): Promise<string[]> {
|
||||||
@@ -166,16 +144,3 @@ async function expectDehydratedDeviceEnabled(app: ElementAppPage): Promise<void>
|
|||||||
})
|
})
|
||||||
.toEqual(1);
|
.toEqual(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Wait for our user to not have a dehydrated device */
|
|
||||||
async function expectDehydratedDeviceDisabled(app: ElementAppPage): Promise<void> {
|
|
||||||
// It might be nice to do this via the UI, but currently this info is not exposed via the UI.
|
|
||||||
//
|
|
||||||
// Note we might have to wait for the device list to be refreshed, so we wrap in `expect.poll`.
|
|
||||||
await expect
|
|
||||||
.poll(async () => {
|
|
||||||
const dehydratedDeviceIds = await getDehydratedDeviceIds(app.client);
|
|
||||||
return dehydratedDeviceIds.length;
|
|
||||||
})
|
|
||||||
.toEqual(0);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import {
|
|||||||
waitForVerificationRequest,
|
waitForVerificationRequest,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
import { type Bot } from "../../pages/bot";
|
import { type Bot } from "../../pages/bot";
|
||||||
import { Toasts } from "../../pages/toasts.ts";
|
|
||||||
|
|
||||||
test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
||||||
let aliceBotClient: Bot;
|
let aliceBotClient: Bot;
|
||||||
@@ -73,51 +72,6 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
|
|||||||
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, false);
|
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Regression test for https://github.com/element-hq/element-web/issues/29110
|
|
||||||
test("No toast after verification, even if the secrets take a while to arrive", async ({ page, credentials }) => {
|
|
||||||
// Before we log in, the bot creates an encrypted room, so that we can test the toast behaviour that only happens
|
|
||||||
// when we are in an encrypted room.
|
|
||||||
await aliceBotClient.createRoom({
|
|
||||||
initial_state: [
|
|
||||||
{
|
|
||||||
type: "m.room.encryption",
|
|
||||||
state_key: "",
|
|
||||||
content: { algorithm: "m.megolm.v1.aes-sha2" },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
// In order to simulate a real environment more accurately, we need to slow down the arrival of the
|
|
||||||
// `m.secret.send` to-device messages. That's slightly tricky to do directly, so instead we delay the *outgoing*
|
|
||||||
// `m.secret.request` messages.
|
|
||||||
await page.route("**/_matrix/client/v3/sendToDevice/m.secret.request/**", async (route) => {
|
|
||||||
await route.fulfill({ json: {} });
|
|
||||||
await new Promise((f) => setTimeout(f, 1000));
|
|
||||||
await route.fetch();
|
|
||||||
});
|
|
||||||
|
|
||||||
await logIntoElement(page, credentials);
|
|
||||||
|
|
||||||
// Launch the verification request between alice and the bot
|
|
||||||
const verificationRequest = await initiateAliceVerificationRequest(page);
|
|
||||||
|
|
||||||
// Handle emoji SAS verification
|
|
||||||
const infoDialog = page.locator(".mx_InfoDialog");
|
|
||||||
// the bot chooses to do an emoji verification
|
|
||||||
const verifier = await verificationRequest.evaluateHandle((request) => request.startVerification("m.sas.v1"));
|
|
||||||
|
|
||||||
// Handle emoji request and check that emojis are matching
|
|
||||||
await doTwoWaySasVerification(page, verifier);
|
|
||||||
|
|
||||||
await infoDialog.getByRole("button", { name: "They match" }).click();
|
|
||||||
await infoDialog.getByRole("button", { name: "Got it" }).click();
|
|
||||||
|
|
||||||
// There should be no toast (other than the notifications one)
|
|
||||||
const toasts = new Toasts(page);
|
|
||||||
await toasts.rejectToast("Notifications");
|
|
||||||
await toasts.assertNoToasts();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Verify device with QR code during login", async ({ page, app, credentials, homeserver }) => {
|
test("Verify device with QR code during login", async ({ page, app, credentials, homeserver }) => {
|
||||||
// A mode 0x02 verification: "self-verifying in which the current device does not yet trust the master key"
|
// A mode 0x02 verification: "self-verifying in which the current device does not yet trust the master key"
|
||||||
await logIntoElement(page, credentials);
|
await logIntoElement(page, credentials);
|
||||||
|
|||||||
@@ -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_LegacyRoomList").getByRole("button", { name: "Start chat" }).click();
|
await page.locator(".mx_RoomList").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
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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";
|
|
||||||
import type { Page } from "@playwright/test";
|
|
||||||
|
|
||||||
test.describe("Header section of the room list", () => {
|
|
||||||
test.use({
|
|
||||||
labsFlags: ["feature_new_room_list"],
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the header section of the room list
|
|
||||||
* @param page
|
|
||||||
*/
|
|
||||||
function getHeaderSection(page: Page) {
|
|
||||||
return page.getByTestId("room-list-header");
|
|
||||||
}
|
|
||||||
|
|
||||||
test.beforeEach(async ({ page, app, user }) => {
|
|
||||||
// The notification toast is displayed above the search section
|
|
||||||
await app.closeNotificationToast();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should render the header section", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
|
||||||
const roomListHeader = getHeaderSection(page);
|
|
||||||
await expect(roomListHeader).toMatchScreenshot("room-list-header.png");
|
|
||||||
|
|
||||||
const composeMenu = roomListHeader.getByRole("button", { name: "Add" });
|
|
||||||
await composeMenu.click();
|
|
||||||
|
|
||||||
await expect(page.getByRole("menu")).toMatchScreenshot("room-list-header-compose-menu.png");
|
|
||||||
|
|
||||||
// New message should open the direct messages dialog
|
|
||||||
await page.getByRole("menuitem", { name: "New message" }).click();
|
|
||||||
await expect(page.getByRole("heading", { name: "Direct Messages" })).toBeVisible();
|
|
||||||
await app.closeDialog();
|
|
||||||
|
|
||||||
// New room should open the room creation dialog
|
|
||||||
await composeMenu.click();
|
|
||||||
await page.getByRole("menuitem", { name: "New room" }).click();
|
|
||||||
await expect(page.getByRole("heading", { name: "Create a private room" })).toBeVisible();
|
|
||||||
await app.closeDialog();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should render the header section for a space", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
|
||||||
await app.client.createSpace({ name: "MySpace" });
|
|
||||||
await page.getByRole("button", { name: "MySpace" }).click();
|
|
||||||
|
|
||||||
const roomListHeader = getHeaderSection(page);
|
|
||||||
await expect(roomListHeader).toMatchScreenshot("room-list-space-header.png");
|
|
||||||
|
|
||||||
await expect(roomListHeader.getByRole("heading", { name: "MySpace" })).toBeVisible();
|
|
||||||
await expect(roomListHeader.getByRole("button", { name: "Add" })).toBeVisible();
|
|
||||||
|
|
||||||
const spaceMenu = roomListHeader.getByRole("button", { name: "Open space menu" });
|
|
||||||
await spaceMenu.click();
|
|
||||||
|
|
||||||
await expect(page.getByRole("menu")).toMatchScreenshot("room-list-header-space-menu.png");
|
|
||||||
|
|
||||||
// It should open the space home
|
|
||||||
await page.getByRole("menuitem", { name: "Space home" }).click();
|
|
||||||
await expect(page.getByRole("main").getByRole("heading", { name: "MySpace" })).toBeVisible();
|
|
||||||
|
|
||||||
// It should open the invite dialog
|
|
||||||
await spaceMenu.click();
|
|
||||||
await page.getByRole("menuitem", { name: "Invite" }).click();
|
|
||||||
await expect(page.getByRole("heading", { name: "Invite to MySpace" })).toBeVisible();
|
|
||||||
await app.closeDialog();
|
|
||||||
|
|
||||||
// It should open the space preferences
|
|
||||||
await spaceMenu.click();
|
|
||||||
await page.getByRole("menuitem", { name: "Preferences" }).click();
|
|
||||||
await expect(page.getByRole("heading", { name: "Preferences" })).toBeVisible();
|
|
||||||
await app.closeDialog();
|
|
||||||
|
|
||||||
// It should open the space settings
|
|
||||||
await spaceMenu.click();
|
|
||||||
await page.getByRole("menuitem", { name: "Space Settings" }).click();
|
|
||||||
await expect(page.getByRole("heading", { name: "Settings" })).toBeVisible();
|
|
||||||
await app.closeDialog();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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("Room list panel", () => {
|
test.describe("Search section of the room list", () => {
|
||||||
test.use({
|
test.use({
|
||||||
labsFlags: ["feature_new_room_list"],
|
labsFlags: ["feature_new_room_list"],
|
||||||
});
|
});
|
||||||
@@ -19,23 +19,16 @@ test.describe("Room list panel", () => {
|
|||||||
* @param page
|
* @param page
|
||||||
*/
|
*/
|
||||||
function getRoomListView(page: Page) {
|
function getRoomListView(page: Page) {
|
||||||
return page.getByTestId("room-list-panel");
|
return page.getByTestId("room-list-view");
|
||||||
}
|
}
|
||||||
|
|
||||||
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 panel", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
test("should render the room list view", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
||||||
const roomListView = getRoomListView(page);
|
const roomListView = getRoomListView(page);
|
||||||
// Wait for the last room to be visible
|
await expect(roomListView).toMatchScreenshot("room-list-view.png");
|
||||||
await expect(roomListView.getByRole("gridcell", { name: "Open room room19" })).toBeVisible();
|
|
||||||
await expect(roomListView).toMatchScreenshot("room-list-panel.png");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -67,15 +67,6 @@ 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);
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { type Page } from "@playwright/test";
|
import { type Page } from "@playwright/test";
|
||||||
import { type Visibility } from "matrix-js-sdk/src/matrix";
|
|
||||||
|
|
||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
import { type ElementAppPage } from "../../pages/ElementAppPage";
|
import { type ElementAppPage } from "../../pages/ElementAppPage";
|
||||||
@@ -86,15 +85,6 @@ test.describe("Room Header", () => {
|
|||||||
await expect(header).toMatchScreenshot("room-header-long-name.png");
|
await expect(header).toMatchScreenshot("room-header-long-name.png");
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
test("should render room header icon correctly", { tag: "@screenshot" }, async ({ page, app, user }) => {
|
|
||||||
await app.client.createRoom({ name: "Test Room", visibility: "public" as Visibility });
|
|
||||||
await app.viewRoomByName("Test Room");
|
|
||||||
|
|
||||||
const header = page.locator(".mx_RoomHeader");
|
|
||||||
|
|
||||||
await expect(header).toMatchScreenshot("room-header-with-icon.png");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe("with a video room", () => {
|
test.describe("with a video room", () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2023 Suguru Hirahara
|
Copyright 2023 Suguru Hirahara
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -50,8 +50,8 @@ test.describe("Appearance user settings tab", () => {
|
|||||||
// Click "Show advanced" link button
|
// Click "Show advanced" link button
|
||||||
await tab.getByRole("button", { name: "Show advanced" }).click();
|
await tab.getByRole("button", { name: "Show advanced" }).click();
|
||||||
|
|
||||||
await tab.getByLabel("Use bundled emoji font").click();
|
await tab.locator(".mx_Checkbox", { hasText: "Use bundled emoji font" }).click();
|
||||||
await tab.getByLabel("Use a system font").click();
|
await tab.locator(".mx_Checkbox", { hasText: "Use a system font" }).click();
|
||||||
|
|
||||||
// Assert that the font-family value was removed
|
// Assert that the font-family value was removed
|
||||||
await expect(page.locator("body")).toHaveCSS("font-family", '""');
|
await expect(page.locator("body")).toHaveCSS("font-family", '""');
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
/*
|
|
||||||
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");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -32,7 +32,7 @@ test.describe("Security user settings tab", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.describe("AnalyticsLearnMoreDialog", () => {
|
test.describe("AnalyticsLearnMoreDialog", () => {
|
||||||
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
|
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page }) => {
|
||||||
const tab = await app.settings.openUserSettings("Security");
|
const tab = await app.settings.openUserSettings("Security");
|
||||||
await tab.getByRole("button", { name: "Learn more" }).click();
|
await tab.getByRole("button", { name: "Learn more" }).click();
|
||||||
await expect(page.locator(".mx_AnalyticsLearnMoreDialog_wrapper .mx_Dialog")).toMatchScreenshot(
|
await expect(page.locator(".mx_AnalyticsLearnMoreDialog_wrapper .mx_Dialog")).toMatchScreenshot(
|
||||||
@@ -41,57 +41,16 @@ test.describe("Security user settings tab", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should be able to set an ID server", async ({ app, context, user, page }) => {
|
test("should contain section to set ID server", async ({ app }) => {
|
||||||
const tab = await app.settings.openUserSettings("Security");
|
const tab = await app.settings.openUserSettings("Security");
|
||||||
|
|
||||||
await context.route("https://identity.example.org/_matrix/identity/v2", async (route) => {
|
const setIdServer = tab.locator(".mx_SetIdServer");
|
||||||
await route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
json: {},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
await context.route("https://identity.example.org/_matrix/identity/v2/account/register", async (route) => {
|
|
||||||
await route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
json: {
|
|
||||||
token: "AToken",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
await context.route("https://identity.example.org/_matrix/identity/v2/account", async (route) => {
|
|
||||||
await route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
json: {
|
|
||||||
user_id: user.userId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
await context.route("https://identity.example.org/_matrix/identity/v2/terms", async (route) => {
|
|
||||||
await route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
json: {
|
|
||||||
policies: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const setIdServer = tab.locator(".mx_IdentityServerPicker");
|
|
||||||
await setIdServer.scrollIntoViewIfNeeded();
|
await setIdServer.scrollIntoViewIfNeeded();
|
||||||
|
// Assert that an input area for identity server exists
|
||||||
const textElement = setIdServer.getByRole("textbox", { name: "Enter a new identity server" });
|
await expect(setIdServer.getByRole("textbox", { name: "Enter a new identity server" })).toBeVisible();
|
||||||
await textElement.click();
|
|
||||||
await textElement.fill("https://identity.example.org");
|
|
||||||
await setIdServer.getByRole("button", { name: "Change" }).click();
|
|
||||||
|
|
||||||
await expect(setIdServer.getByText("Checking server")).toBeVisible();
|
|
||||||
// Accept terms
|
|
||||||
await page.getByTestId("dialog-primary-button").click();
|
|
||||||
// Check identity has changed.
|
|
||||||
await expect(setIdServer.getByText("Your identity server has been changed")).toBeVisible();
|
|
||||||
// Ensure section title is updated.
|
|
||||||
await expect(tab.getByText(`Identity server (identity.example.org)`, { exact: true })).toBeVisible();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should show integrations as enabled", async ({ app, page, user }) => {
|
test("should enable show integrations as enabled", async ({ app, page }) => {
|
||||||
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,9 +61,7 @@ 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
|
||||||
const toggleswitch = setIntegrationManager.getByLabel("Enable the integration manager");
|
await expect(setIntegrationManager.locator(".mx_ToggleSwitch_enabled")).toBeVisible();
|
||||||
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)",
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -121,10 +121,9 @@ test.describe("Spaces", () => {
|
|||||||
await page.getByRole("button", { name: "Skip for now" }).click();
|
await page.getByRole("button", { name: "Skip for now" }).click();
|
||||||
|
|
||||||
// Assert rooms exist in the room list
|
// Assert rooms exist in the room list
|
||||||
const roomList = page.getByRole("tree", { name: "Rooms" });
|
await expect(page.getByRole("treeitem", { name: "General", exact: true })).toBeVisible();
|
||||||
await expect(roomList.getByRole("treeitem", { name: "General", exact: true })).toBeVisible();
|
await expect(page.getByRole("treeitem", { name: "Random", exact: true })).toBeVisible();
|
||||||
await expect(roomList.getByRole("treeitem", { name: "Random", exact: true })).toBeVisible();
|
await expect(page.getByRole("treeitem", { name: "Projects", exact: true })).toBeVisible();
|
||||||
await expect(roomList.getByRole("treeitem", { name: "Projects", exact: true })).toBeVisible();
|
|
||||||
|
|
||||||
// Assert rooms exist in the space explorer
|
// Assert rooms exist in the space explorer
|
||||||
await expect(
|
await expect(
|
||||||
@@ -156,7 +155,7 @@ test.describe("Spaces", () => {
|
|||||||
|
|
||||||
await page.getByRole("button", { name: "Just me" }).click();
|
await page.getByRole("button", { name: "Just me" }).click();
|
||||||
|
|
||||||
await page.getByRole("checkbox", { name: "Sample Room" }).click();
|
await page.getByText("Sample Room").click({ force: true }); // force click as checkbox size is zero
|
||||||
|
|
||||||
// Temporal implementation as multiple elements with the role "button" and name "Add" are found
|
// Temporal implementation as multiple elements with the role "button" and name "Add" are found
|
||||||
await page.locator(".mx_AddExistingToSpace_footer").getByRole("button", { name: "Add" }).click();
|
await page.locator(".mx_AddExistingToSpace_footer").getByRole("button", { name: "Add" }).click();
|
||||||
@@ -166,50 +165,6 @@ test.describe("Spaces", () => {
|
|||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
|
||||||
"should allow user to add an existing room to a space after creation",
|
|
||||||
{ tag: "@screenshot" },
|
|
||||||
async ({ page, app, user }) => {
|
|
||||||
await app.client.createRoom({
|
|
||||||
name: "Sample Room",
|
|
||||||
});
|
|
||||||
await app.client.createRoom({
|
|
||||||
name: "A Room that will not be selected",
|
|
||||||
});
|
|
||||||
|
|
||||||
const menu = await openSpaceCreateMenu(page);
|
|
||||||
await menu.getByRole("button", { name: "Private" }).click();
|
|
||||||
|
|
||||||
await menu
|
|
||||||
.locator('.mx_SpaceBasicSettings_avatarContainer input[type="file"]')
|
|
||||||
.setInputFiles("playwright/sample-files/riot.png");
|
|
||||||
await expect(menu.getByRole("textbox", { name: "Address" })).not.toBeVisible();
|
|
||||||
await menu
|
|
||||||
.getByRole("textbox", { name: "Description" })
|
|
||||||
.fill("This is a personal space to mourn Riot.im...");
|
|
||||||
await menu.getByRole("textbox", { name: "Name" }).fill("This is my Riot");
|
|
||||||
await menu.getByRole("textbox", { name: "Name" }).press("Enter");
|
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Just me" }).click();
|
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Skip for now" }).click();
|
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Add room" }).click();
|
|
||||||
await page.getByRole("menuitem", { name: "Add existing room" }).click();
|
|
||||||
|
|
||||||
await page.getByRole("checkbox", { name: "Sample Room" }).click();
|
|
||||||
|
|
||||||
await expect(page.getByRole("dialog", { name: "Avatar Add existing rooms" })).toMatchScreenshot(
|
|
||||||
"add-existing-rooms-dialog.png",
|
|
||||||
);
|
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Add" }).click();
|
|
||||||
await expect(
|
|
||||||
page.locator(".mx_SpaceHierarchy_list").getByRole("treeitem", { name: "Sample Room" }),
|
|
||||||
).toBeVisible();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
test("should allow user to invite another to a space", { tag: "@no-webkit" }, async ({ page, app, user, bot }) => {
|
test("should allow user to invite another to a space", { tag: "@no-webkit" }, async ({ page, app, user, bot }) => {
|
||||||
await app.client.createSpace({
|
await app.client.createSpace({
|
||||||
visibility: "public" as any,
|
visibility: "public" as any,
|
||||||
@@ -336,36 +291,4 @@ test.describe("Spaces", () => {
|
|||||||
// Assert we get shown the new room intro, and thus not the soft crash screen
|
// Assert we get shown the new room intro, and thus not the soft crash screen
|
||||||
await expect(page.locator(".mx_NewRoomIntro")).toBeVisible();
|
await expect(page.locator(".mx_NewRoomIntro")).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should render spaces view", { tag: "@screenshot" }, async ({ page, app, user, axe, checkA11y }) => {
|
|
||||||
axe.disableRules([
|
|
||||||
// Disable this check as it triggers on nested roving tab index elements which are in practice fine
|
|
||||||
"nested-interactive",
|
|
||||||
// XXX: We have some known contrast issues here
|
|
||||||
"color-contrast",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const childSpaceId1 = await app.client.createSpace({
|
|
||||||
name: "Child Space 1",
|
|
||||||
initial_state: [],
|
|
||||||
});
|
|
||||||
const childSpaceId2 = await app.client.createSpace({
|
|
||||||
name: "Child Space 2",
|
|
||||||
initial_state: [],
|
|
||||||
});
|
|
||||||
const childSpaceId3 = await app.client.createSpace({
|
|
||||||
name: "Child Space 3",
|
|
||||||
initial_state: [],
|
|
||||||
});
|
|
||||||
await app.client.createSpace({
|
|
||||||
name: "Root Space",
|
|
||||||
initial_state: [
|
|
||||||
spaceChildInitialState(childSpaceId1),
|
|
||||||
spaceChildInitialState(childSpaceId2),
|
|
||||||
spaceChildInitialState(childSpaceId3),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
await app.viewSpaceByName("Root Space");
|
|
||||||
await expect(page.locator(".mx_SpaceRoomView")).toMatchScreenshot("space-room-view.png");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -875,40 +875,6 @@ 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 |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before 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:26e0d9c5ca96218243432d48a9f8596e4c1bc10b748f0a1bddf9916b914d1216";
|
const TAG = "develop@sha256:fa3090607a5e07a4ff245247aa3b598c6bbcff9231fd89a558de97c37adbd744";
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {
|
const DEFAULT_CONFIG = {
|
||||||
server_name: "localhost",
|
server_name: "localhost",
|
||||||
@@ -144,7 +144,6 @@ 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>;
|
||||||
|
|||||||
@@ -589,21 +589,18 @@ legend {
|
|||||||
* in the app look the same by being AccessibleButtons, or possibly by having explict button classes.
|
* in the app look the same by being AccessibleButtons, or possibly by having explict button classes.
|
||||||
* We should go through and have one consistent set of styles for buttons throughout the app.
|
* We should go through and have one consistent set of styles for buttons throughout the app.
|
||||||
* For now, I am duplicating the selectors here for mx_Dialog and mx_DialogButtons.
|
* For now, I am duplicating the selectors here for mx_Dialog and mx_DialogButtons.
|
||||||
|
*
|
||||||
|
* Elements that should not be styled like a dialog button are mentioned in a :not() pseudo-class.
|
||||||
|
* For the widest browser support, we use multiple :not pseudo-classes instead of :not(.a, .b).
|
||||||
*/
|
*/
|
||||||
.mx_Dialog
|
.mx_Dialog
|
||||||
button:not(
|
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
|
||||||
.mx_EncryptionUserSettingsTab button,
|
.mx_UserProfileSettings button
|
||||||
.mx_UserProfileSettings button,
|
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
|
||||||
.mx_ShareDialog button,
|
.mx_EncryptionUserSettingsTab button
|
||||||
.mx_UnpinAllDialog button,
|
|
||||||
.mx_ThemeChoicePanel_CustomTheme button,
|
|
||||||
.mx_Dialog_nonDialogButton,
|
|
||||||
.mx_AccessibleButton,
|
|
||||||
.mx_IdentityServerPicker button,
|
|
||||||
[class|="maplibregl"]
|
|
||||||
),
|
),
|
||||||
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton, .mx_AccessibleButton),
|
|
||||||
.mx_Dialog input[type="submit"],
|
.mx_Dialog input[type="submit"],
|
||||||
|
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton),
|
||||||
.mx_Dialog_buttons input[type="submit"] {
|
.mx_Dialog_buttons input[type="submit"] {
|
||||||
@mixin mx_DialogButton;
|
@mixin mx_DialogButton;
|
||||||
margin-left: 0px;
|
margin-left: 0px;
|
||||||
@@ -619,46 +616,32 @@ legend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mx_Dialog
|
.mx_Dialog
|
||||||
button:not(
|
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
|
||||||
.mx_Dialog_nonDialogButton,
|
.mx_UserProfileSettings button
|
||||||
[class|="maplibregl"],
|
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
|
||||||
.mx_AccessibleButton,
|
|
||||||
.mx_UserProfileSettings button,
|
|
||||||
.mx_ThemeChoicePanel_CustomTheme button,
|
|
||||||
.mx_UnpinAllDialog button,
|
|
||||||
.mx_ShareDialog button,
|
|
||||||
.mx_EncryptionUserSettingsTab button
|
.mx_EncryptionUserSettingsTab button
|
||||||
):last-child {
|
):last-child {
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Dialog
|
.mx_Dialog
|
||||||
button:not(
|
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
|
||||||
.mx_Dialog_nonDialogButton,
|
.mx_UserProfileSettings button
|
||||||
[class|="maplibregl"],
|
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
|
||||||
.mx_AccessibleButton,
|
|
||||||
.mx_UserProfileSettings button,
|
|
||||||
.mx_ThemeChoicePanel_CustomTheme button,
|
|
||||||
.mx_UnpinAllDialog button,
|
|
||||||
.mx_ShareDialog button,
|
|
||||||
.mx_EncryptionUserSettingsTab button
|
.mx_EncryptionUserSettingsTab button
|
||||||
):focus,
|
):focus,
|
||||||
.mx_Dialog input[type="submit"]:focus,
|
.mx_Dialog input[type="submit"]:focus,
|
||||||
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton, .mx_AccessibleButton):focus,
|
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):focus,
|
||||||
.mx_Dialog_buttons input[type="submit"]:focus {
|
.mx_Dialog_buttons input[type="submit"]:focus {
|
||||||
filter: brightness($focus-brightness);
|
filter: brightness($focus-brightness);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Dialog button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton, [class|="maplibregl"]),
|
.mx_Dialog button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]),
|
||||||
.mx_Dialog input[type="submit"].mx_Dialog_primary,
|
.mx_Dialog input[type="submit"].mx_Dialog_primary,
|
||||||
.mx_Dialog_buttons
|
.mx_Dialog_buttons
|
||||||
button:not(
|
button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(
|
||||||
.mx_Dialog_nonDialogButton,
|
.mx_UserProfileSettings button
|
||||||
.mx_AccessibleButton,
|
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
|
||||||
.mx_UserProfileSettings button,
|
|
||||||
.mx_ThemeChoicePanel_CustomTheme button,
|
|
||||||
.mx_UnpinAllDialog button,
|
|
||||||
.mx_ShareDialog button,
|
|
||||||
.mx_EncryptionUserSettingsTab button
|
.mx_EncryptionUserSettingsTab button
|
||||||
),
|
),
|
||||||
.mx_Dialog_buttons input[type="submit"].mx_Dialog_primary {
|
.mx_Dialog_buttons input[type="submit"].mx_Dialog_primary {
|
||||||
@@ -668,43 +651,32 @@ legend {
|
|||||||
min-width: 156px;
|
min-width: 156px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Dialog button.danger:not(.mx_Dialog_nonDialogButton, [class|="maplibregl"]),
|
.mx_Dialog button.danger:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]),
|
||||||
.mx_Dialog input[type="submit"].danger,
|
.mx_Dialog input[type="submit"].danger,
|
||||||
.mx_Dialog_buttons
|
.mx_Dialog_buttons
|
||||||
button.danger:not(
|
button.danger:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(.mx_UserProfileSettings button):not(
|
||||||
.mx_Dialog_nonDialogButton,
|
.mx_ThemeChoicePanel_CustomTheme button
|
||||||
.mx_AccessibleButton,
|
):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(.mx_EncryptionUserSettingsTab button),
|
||||||
.mx_UserProfileSettings button,
|
|
||||||
.mx_ThemeChoicePanel_CustomTheme button,
|
|
||||||
.mx_UnpinAllDialog button,
|
|
||||||
.mx_ShareDialog button,
|
|
||||||
.mx_EncryptionUserSettingsTab button
|
|
||||||
),
|
|
||||||
.mx_Dialog_buttons input[type="submit"].danger {
|
.mx_Dialog_buttons input[type="submit"].danger {
|
||||||
background-color: var(--cpd-color-bg-critical-primary);
|
background-color: var(--cpd-color-bg-critical-primary);
|
||||||
border: solid 1px var(--cpd-color-bg-critical-primary);
|
border: solid 1px var(--cpd-color-bg-critical-primary);
|
||||||
color: var(--cpd-color-text-on-solid-primary);
|
color: var(--cpd-color-text-on-solid-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Dialog button.warning:not(.mx_Dialog_nonDialogButton, [class|="maplibregl"]),
|
.mx_Dialog button.warning:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]),
|
||||||
.mx_Dialog input[type="submit"].warning {
|
.mx_Dialog input[type="submit"].warning {
|
||||||
border: solid 1px var(--cpd-color-border-critical-subtle);
|
border: solid 1px var(--cpd-color-border-critical-subtle);
|
||||||
color: var(--cpd-color-text-critical-primary);
|
color: var(--cpd-color-text-critical-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Dialog
|
.mx_Dialog
|
||||||
button:not(
|
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
|
||||||
.mx_Dialog_nonDialogButton,
|
.mx_UserProfileSettings button
|
||||||
[class|="maplibregl"],
|
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
|
||||||
.mx_AccessibleButton,
|
|
||||||
.mx_UserProfileSettings button,
|
|
||||||
.mx_ThemeChoicePanel_CustomTheme button,
|
|
||||||
.mx_UnpinAllDialog button,
|
|
||||||
.mx_ShareDialog button,
|
|
||||||
.mx_EncryptionUserSettingsTab button
|
.mx_EncryptionUserSettingsTab button
|
||||||
):disabled,
|
):disabled,
|
||||||
.mx_Dialog input[type="submit"]:disabled,
|
.mx_Dialog input[type="submit"]:disabled,
|
||||||
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton, .mx_AccessibleButton):disabled,
|
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):disabled,
|
||||||
.mx_Dialog_buttons input[type="submit"]:disabled {
|
.mx_Dialog_buttons input[type="submit"]:disabled {
|
||||||
background-color: $light-fg-color;
|
background-color: $light-fg-color;
|
||||||
border: solid 1px $light-fg-color;
|
border: solid 1px $light-fg-color;
|
||||||
|
|||||||
@@ -127,6 +127,7 @@
|
|||||||
@import "./views/dialogs/_AddExistingToSpaceDialog.pcss";
|
@import "./views/dialogs/_AddExistingToSpaceDialog.pcss";
|
||||||
@import "./views/dialogs/_AnalyticsLearnMoreDialog.pcss";
|
@import "./views/dialogs/_AnalyticsLearnMoreDialog.pcss";
|
||||||
@import "./views/dialogs/_BugReportDialog.pcss";
|
@import "./views/dialogs/_BugReportDialog.pcss";
|
||||||
|
@import "./views/dialogs/_BulkRedactDialog.pcss";
|
||||||
@import "./views/dialogs/_ChangelogDialog.pcss";
|
@import "./views/dialogs/_ChangelogDialog.pcss";
|
||||||
@import "./views/dialogs/_CompoundDialog.pcss";
|
@import "./views/dialogs/_CompoundDialog.pcss";
|
||||||
@import "./views/dialogs/_ConfirmSpaceUserActionDialog.pcss";
|
@import "./views/dialogs/_ConfirmSpaceUserActionDialog.pcss";
|
||||||
@@ -210,6 +211,7 @@
|
|||||||
@import "./views/elements/_ServerPicker.pcss";
|
@import "./views/elements/_ServerPicker.pcss";
|
||||||
@import "./views/elements/_SettingsFlag.pcss";
|
@import "./views/elements/_SettingsFlag.pcss";
|
||||||
@import "./views/elements/_Spinner.pcss";
|
@import "./views/elements/_Spinner.pcss";
|
||||||
|
@import "./views/elements/_StyledCheckbox.pcss";
|
||||||
@import "./views/elements/_StyledRadioButton.pcss";
|
@import "./views/elements/_StyledRadioButton.pcss";
|
||||||
@import "./views/elements/_SyntaxHighlight.pcss";
|
@import "./views/elements/_SyntaxHighlight.pcss";
|
||||||
@import "./views/elements/_TagComposer.pcss";
|
@import "./views/elements/_TagComposer.pcss";
|
||||||
@@ -267,11 +269,8 @@
|
|||||||
@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/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/_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,7 +288,6 @@
|
|||||||
@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";
|
||||||
@@ -314,6 +312,7 @@
|
|||||||
@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";
|
||||||
@@ -349,6 +348,7 @@
|
|||||||
@import "./views/settings/_PowerLevelSelector.pcss";
|
@import "./views/settings/_PowerLevelSelector.pcss";
|
||||||
@import "./views/settings/_RoomProfileSettings.pcss";
|
@import "./views/settings/_RoomProfileSettings.pcss";
|
||||||
@import "./views/settings/_SecureBackupPanel.pcss";
|
@import "./views/settings/_SecureBackupPanel.pcss";
|
||||||
|
@import "./views/settings/_SetIdServer.pcss";
|
||||||
@import "./views/settings/_SetIntegrationManager.pcss";
|
@import "./views/settings/_SetIntegrationManager.pcss";
|
||||||
@import "./views/settings/_SettingsFieldset.pcss";
|
@import "./views/settings/_SettingsFieldset.pcss";
|
||||||
@import "./views/settings/_SettingsHeader.pcss";
|
@import "./views/settings/_SettingsHeader.pcss";
|
||||||
@@ -360,7 +360,6 @@
|
|||||||
@import "./views/settings/encryption/_AdvancedPanel.pcss";
|
@import "./views/settings/encryption/_AdvancedPanel.pcss";
|
||||||
@import "./views/settings/encryption/_ChangeRecoveryKey.pcss";
|
@import "./views/settings/encryption/_ChangeRecoveryKey.pcss";
|
||||||
@import "./views/settings/encryption/_EncryptionCard.pcss";
|
@import "./views/settings/encryption/_EncryptionCard.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/encryption/_ResetIdentityPanel.pcss";
|
||||||
@import "./views/settings/tabs/_SettingsBanner.pcss";
|
@import "./views/settings/tabs/_SettingsBanner.pcss";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -16,9 +16,9 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
.mx_SelectableDeviceTile_checkbox {
|
.mx_SelectableDeviceTile_checkbox {
|
||||||
flex: 1 0;
|
flex: 1 0;
|
||||||
|
|
||||||
> div {
|
.mx_Checkbox_background + div {
|
||||||
margin-top: auto;
|
flex: 1 0;
|
||||||
margin-bottom: auto;
|
/* override more specific selector */
|
||||||
margin-right: var(--cpd-space-1x);
|
margin-left: $spacing-16 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -70,26 +70,38 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--cpd-color-text-secondary);
|
color: var(--cpd-color-text-secondary);
|
||||||
margin: 20px 0 12px;
|
margin: 20px 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_QuickSettingsButton_pinToSidebarHeading {
|
||||||
|
padding-left: 24px;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
}
|
||||||
|
|
||||||
|
.mx_Checkbox {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_QuickSettingsButton_favouritesCheckbox,
|
||||||
|
.mx_QuickSettingsButton_peopleCheckbox {
|
||||||
|
.mx_Checkbox_background + div {
|
||||||
|
padding-left: 22px;
|
||||||
|
position: relative;
|
||||||
|
margin-left: 6px;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
color: var(--cpd-color-text-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_QuickSettingsButton_moreOptionsButton {
|
.mx_QuickSettingsButton_moreOptionsButton {
|
||||||
margin-left: var(--cpd-space-7x);
|
padding-left: 22px;
|
||||||
|
margin-left: 22px;
|
||||||
font-size: $font-15px;
|
font-size: $font-15px;
|
||||||
line-height: $font-24px;
|
line-height: $font-24px;
|
||||||
color: var(--cpd-color-text-primary);
|
color: var(--cpd-color-text-primary);
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_QuickSettingsButton_option {
|
|
||||||
margin-bottom: var(--cpd-space-3x);
|
|
||||||
label {
|
|
||||||
/* Correctly line up icons and text. */
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_QuickSettingsButton_ContextMenuWrapper_new_room_list {
|
.mx_QuickSettingsButton_ContextMenuWrapper_new_room_list {
|
||||||
@@ -99,10 +111,15 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mx_QuickSettingsButton_icon {
|
.mx_QuickSettingsButton_icon {
|
||||||
margin-right: var(--cpd-space-1x);
|
// TODO remove when all icons have fill=currentColor
|
||||||
|
* {
|
||||||
|
fill: $secondary-content;
|
||||||
|
}
|
||||||
color: $secondary-content;
|
color: $secondary-content;
|
||||||
width: 18px;
|
width: 16px;
|
||||||
height: 18px;
|
height: 16px;
|
||||||
margin-top: auto;
|
position: absolute;
|
||||||
margin-bottom: auto;
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -77,7 +77,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
height: 16px;
|
height: 16px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
left: 0;
|
left: 0;
|
||||||
background-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
|
background-image: url("@vector-im/compound-design-tokens/icons/error.svg");
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
@@ -247,6 +247,15 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
.mx_AccessibleButton_kind_primary_outline {
|
.mx_AccessibleButton_kind_primary_outline {
|
||||||
padding: 3px 16px; /* to account for the 1px border */
|
padding: 3px 16px; /* to account for the 1px border */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_Checkbox {
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
label {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageContextMenu_iconReport::before {
|
.mx_MessageContextMenu_iconReport::before {
|
||||||
mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
|
mask-image: url("@vector-im/compound-design-tokens/icons/error.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageContextMenu_iconLink::before {
|
.mx_MessageContextMenu_iconLink::before {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -32,11 +32,6 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
.mx_AddExistingToSpace_section {
|
.mx_AddExistingToSpace_section {
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// provides space for scrollbar so that checkbox and scrollbar do not collide
|
// provides space for scrollbar so that checkbox and scrollbar do not collide
|
||||||
|
|
||||||
&:not(:first-child) {
|
&:not(:first-child) {
|
||||||
@@ -219,12 +214,6 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
|
|
||||||
form {
|
|
||||||
/* Align checkboxes. */
|
|
||||||
margin-top: auto;
|
|
||||||
margin-bottom: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_DecoratedRoomAvatar, /* we can't target .mx_BaseAvatar here as it'll break the decorated avatar styling */ {
|
.mx_DecoratedRoomAvatar, /* we can't target .mx_BaseAvatar here as it'll break the decorated avatar styling */ {
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
}
|
}
|
||||||
@@ -238,4 +227,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_Checkbox {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
res/css/views/dialogs/_BulkRedactDialog.pcss
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 New Vector Ltd.
|
||||||
|
Copyright 2021 Robin Townsend <robin@robin.town>
|
||||||
|
|
||||||
|
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_BulkRedactDialog {
|
||||||
|
.mx_Checkbox,
|
||||||
|
.mx_BulkRedactDialog_checkboxMicrocopy {
|
||||||
|
line-height: $font-20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BulkRedactDialog_checkboxMicrocopy {
|
||||||
|
margin-left: 26px;
|
||||||
|
color: $secondary-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -43,6 +43,11 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
.mx_Field_valid.mx_Field:focus-within {
|
.mx_Field_valid.mx_Field:focus-within {
|
||||||
border-color: $input-border-color;
|
border-color: $input-border-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_Checkbox input[type="checkbox"]:checked + label > .mx_Checkbox_background {
|
||||||
|
background: $info-plinth-fg-color;
|
||||||
|
border-color: $info-plinth-fg-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ExportDialog_progress {
|
.mx_ExportDialog_progress {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -74,6 +74,10 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
line-height: $font-15px;
|
line-height: $font-15px;
|
||||||
color: $tertiary-content;
|
color: $tertiary-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_Checkbox {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -19,6 +19,13 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
font-size: $font-15px;
|
font-size: $font-15px;
|
||||||
line-height: $font-15px;
|
line-height: $font-15px;
|
||||||
|
|
||||||
|
.mx_WidgetCapabilitiesPromptDialog_byline {
|
||||||
|
color: $muted-fg-color;
|
||||||
|
margin-left: 26px;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Dialog_buttons {
|
.mx_Dialog_buttons {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
|
|
||||||
&.mx_AccessSecretStorageDialog_resetBadge::before {
|
&.mx_AccessSecretStorageDialog_resetBadge::before {
|
||||||
/* The image isn't capable of masking, so we use a background instead. */
|
/* The image isn't capable of masking, so we use a background instead. */
|
||||||
background-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
|
background-image: url("@vector-im/compound-design-tokens/icons/error.svg");
|
||||||
background-size: 24px;
|
background-size: 24px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
width: 16px;
|
width: 16px;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 2px; /* alignment */
|
top: 2px; /* alignment */
|
||||||
background-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
|
background-image: url("@vector-im/compound-design-tokens/icons/error.svg");
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,5 +29,5 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mx_InfoTooltip_icon_warning::before {
|
.mx_InfoTooltip_icon_warning::before {
|
||||||
mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
|
mask-image: url("@vector-im/compound-design-tokens/icons/error.svg");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -7,5 +7,26 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_LabelledCheckbox {
|
.mx_LabelledCheckbox {
|
||||||
margin-top: var(--cpd-space-2x);
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.mx_Checkbox {
|
||||||
|
margin-top: 3px; /* visually align with label text */
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_LabelledCheckbox_labels {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.mx_LabelledCheckbox_label {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_LabelledCheckbox_byline {
|
||||||
|
display: block;
|
||||||
|
padding-top: $spacing-4;
|
||||||
|
color: $muted-fg-color;
|
||||||
|
font-size: $font-11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
98
res/css/views/elements/_StyledCheckbox.pcss
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 New Vector Ltd.
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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_Checkbox {
|
||||||
|
$size: $font-16px;
|
||||||
|
$border-radius: 0.27rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
& + label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
& + label > .mx_Checkbox_background {
|
||||||
|
display: inline-flex;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
height: $size;
|
||||||
|
width: $size;
|
||||||
|
size: 0.5rem;
|
||||||
|
border: 1px solid var(--cpd-color-border-interactive-primary);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
|
||||||
|
.mx_Checkbox_checkmark {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
mask-image: url("@vector-im/compound-design-tokens/icons/check.svg");
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: 100%;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + label > .mx_Checkbox_background .mx_Checkbox_checkmark {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
& + label > *:not(.mx_Checkbox_background) {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled + label {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
& + label .mx_Checkbox_background {
|
||||||
|
@mixin unreal-focus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Checkbox.mx_Checkbox_kind_solid input[type="checkbox"] {
|
||||||
|
& + label > .mx_Checkbox_background .mx_Checkbox_checkmark {
|
||||||
|
background: var(--cpd-color-icon-on-solid-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + label > .mx_Checkbox_background {
|
||||||
|
background: var(--cpd-color-bg-accent-rest);
|
||||||
|
border-color: var(--cpd-color-bg-accent-rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked:disabled + label > .mx_Checkbox_background {
|
||||||
|
background: var(--cpd-color-bg-action-primary-disabled);
|
||||||
|
border-color: var(--cpd-color-bg-action-primary-disabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Checkbox.mx_Checkbox_kind_outline input[type="checkbox"] {
|
||||||
|
& + label > .mx_Checkbox_background .mx_Checkbox_checkmark {
|
||||||
|
background: var(--cpd-color-bg-accent-rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + label > .mx_Checkbox_background {
|
||||||
|
background: transparent;
|
||||||
|
border-color: var(--cpd-color-bg-accent-rest);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -100,7 +100,3 @@ 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);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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_RoomListHeaderView {
|
|
||||||
flex: 0 0 60px;
|
|
||||||
padding: 0 var(--cpd-space-3x);
|
|
||||||
|
|
||||||
.mx_RoomListHeaderView_title {
|
|
||||||
min-width: 0;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
all: unset;
|
|
||||||
font: var(--cpd-font-heading-sm-semibold);
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
color: var(--cpd-color-icon-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SpaceMenu_button {
|
|
||||||
svg {
|
|
||||||
transition: transform 0.1s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SpaceMenu_button[aria-expanded="true"] {
|
|
||||||
svg {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,9 +7,9 @@
|
|||||||
|
|
||||||
.mx_RoomListSearch {
|
.mx_RoomListSearch {
|
||||||
/* From figma, this should be aligned with the room header */
|
/* From figma, this should be aligned with the room header */
|
||||||
flex: 0 0 64px;
|
height: 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: 1px solid var(--cpd-color-bg-subtle-primary);
|
||||||
padding: 0 var(--cpd-space-3x);
|
padding: 0 var(--cpd-space-3x);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomListSearch_button:hover {
|
.mx_RoomListSearch_explore:hover {
|
||||||
svg {
|
svg {
|
||||||
fill: var(--cpd-color-icon-primary);
|
fill: var(--cpd-color-icon-primary);
|
||||||
}
|
}
|
||||||
@@ -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_RoomListPanel {
|
.mx_RoomListView {
|
||||||
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);
|
||||||
@@ -683,7 +683,6 @@ $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 {
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
|
|
||||||
.mx_RoomHeader_icon {
|
.mx_RoomHeader_icon {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: var(--cpd-space-1x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomHeader .mx_FacePile {
|
.mx_RoomHeader .mx_FacePile {
|
||||||
|
|||||||
@@ -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_LegacyRoomList {
|
.mx_RoomList {
|
||||||
padding-right: 7px; /* width of the scrollbar, to line things up */
|
padding-right: 7px; /* width of the scrollbar, to line things up */
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_LegacyRoomList_iconPlus::before {
|
.mx_RoomList_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_LegacyRoomList_iconNewRoom::before {
|
.mx_RoomList_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_LegacyRoomList_iconNewVideoRoom::before {
|
.mx_RoomList_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_LegacyRoomList_iconAddExistingRoom::before {
|
.mx_RoomList_iconAddExistingRoom::before {
|
||||||
mask-image: url("$(res)/img/element-icons/roomlist/hash.svg");
|
mask-image: url("$(res)/img/element-icons/roomlist/hash.svg");
|
||||||
}
|
}
|
||||||
.mx_LegacyRoomList_iconExplore::before {
|
.mx_RoomList_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_LegacyRoomList_iconDialpad::before {
|
.mx_RoomList_iconDialpad::before {
|
||||||
mask-image: url("$(res)/img/element-icons/roomlist/dialpad.svg");
|
mask-image: url("$(res)/img/element-icons/roomlist/dialpad.svg");
|
||||||
}
|
}
|
||||||
.mx_LegacyRoomList_iconStartChat::before {
|
.mx_RoomList_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_LegacyRoomList_iconInvite::before {
|
.mx_RoomList_iconInvite::before {
|
||||||
mask-image: url("$(res)/img/element-icons/room/share.svg");
|
mask-image: url("$(res)/img/element-icons/room/share.svg");
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -393,7 +393,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_StyledRadioButton {
|
.mx_StyledRadioButton,
|
||||||
|
.mx_Checkbox {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
res/css/views/settings/_SetIdServer.pcss
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 New Vector Ltd.
|
||||||
|
Copyright 2019-2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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_SetIdServer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: $spacing-8;
|
||||||
|
|
||||||
|
.mx_Field {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SetIdServer_tooltip {
|
||||||
|
max-width: var(--SettingsTab_tooltip-max-width);
|
||||||
|
}
|
||||||
@@ -7,13 +7,19 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_SetIntegrationManager {
|
.mx_SetIntegrationManager {
|
||||||
|
.mx_SettingsFlag {
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.mx_SetIntegrationManager_heading_manager {
|
.mx_SetIntegrationManager_heading_manager {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
column-gap: $spacing-4;
|
column-gap: $spacing-4;
|
||||||
}
|
}
|
||||||
form {
|
|
||||||
margin-top: var(--cpd-space-3x);
|
.mx_ToggleSwitch {
|
||||||
|
align-self: flex-start;
|
||||||
|
min-width: var(--ToggleSwitch-min-width); /* avoid compression */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.mx_EncryptionCard_emphasisedContent {
|
|
||||||
span {
|
|
||||||
font: var(--cpd-font-body-md-medium);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,15 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Red text for the "Do not close this window" warning
|
.mx_ResetIdentityPanel {
|
||||||
.mx_ResetIdentityPanel_warning {
|
.mx_ResetIdentityPanel_content {
|
||||||
color: var(--cpd-color-text-critical-primary);
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--cpd-space-3x);
|
||||||
|
|
||||||
|
> span {
|
||||||
|
font: var(--cpd-font-body-md-medium);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -14,12 +14,17 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SidebarUserSettingsTab_icon {
|
.mx_SidebarUserSettingsTab_checkbox {
|
||||||
margin-right: var(--cpd-space-2x);
|
margin-bottom: $spacing-8;
|
||||||
margin-top: auto;
|
/* override checkbox styles */
|
||||||
margin-bottom: auto;
|
label {
|
||||||
}
|
align-items: flex-start !important;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_SidebarUserSettingsTab_checkbox label {
|
svg {
|
||||||
display: flex;
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
margin-right: $spacing-8;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/@types/global.d.ts
vendored
@@ -47,7 +47,6 @@ 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 */
|
||||||
|
|
||||||
@@ -100,7 +99,6 @@ 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;
|
||||||
|
|||||||
@@ -15,10 +15,9 @@ import {
|
|||||||
type SyncState,
|
type SyncState,
|
||||||
ClientStoppedError,
|
ClientStoppedError,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
import { logger as baseLogger, LogSpan } from "matrix-js-sdk/src/logger";
|
import { logger as baseLogger } from "matrix-js-sdk/src/logger";
|
||||||
import { CryptoEvent, type KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
|
import { CryptoEvent, type KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
|
||||||
import { type CryptoSessionStateChange } from "@matrix-org/analytics-events/types/typescript/CryptoSessionStateChange";
|
import { type CryptoSessionStateChange } from "@matrix-org/analytics-events/types/typescript/CryptoSessionStateChange";
|
||||||
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
|
|
||||||
|
|
||||||
import { PosthogAnalytics } from "./PosthogAnalytics";
|
import { PosthogAnalytics } from "./PosthogAnalytics";
|
||||||
import dis from "./dispatcher/dispatcher";
|
import dis from "./dispatcher/dispatcher";
|
||||||
@@ -97,7 +96,6 @@ export default class DeviceListener {
|
|||||||
this.client.on(ClientEvent.AccountData, this.onAccountData);
|
this.client.on(ClientEvent.AccountData, this.onAccountData);
|
||||||
this.client.on(ClientEvent.Sync, this.onSync);
|
this.client.on(ClientEvent.Sync, this.onSync);
|
||||||
this.client.on(RoomStateEvent.Events, this.onRoomStateEvents);
|
this.client.on(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||||
this.client.on(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
|
|
||||||
this.shouldRecordClientInformation = SettingsStore.getValue("deviceClientInformationOptIn");
|
this.shouldRecordClientInformation = SettingsStore.getValue("deviceClientInformationOptIn");
|
||||||
// only configurable in config, so we don't need to watch the value
|
// only configurable in config, so we don't need to watch the value
|
||||||
this.enableBulkUnverifiedSessionsReminder = SettingsStore.getValue(UIFeature.BulkUnverifiedSessionsReminder);
|
this.enableBulkUnverifiedSessionsReminder = SettingsStore.getValue(UIFeature.BulkUnverifiedSessionsReminder);
|
||||||
@@ -120,7 +118,6 @@ export default class DeviceListener {
|
|||||||
this.client.removeListener(ClientEvent.AccountData, this.onAccountData);
|
this.client.removeListener(ClientEvent.AccountData, this.onAccountData);
|
||||||
this.client.removeListener(ClientEvent.Sync, this.onSync);
|
this.client.removeListener(ClientEvent.Sync, this.onSync);
|
||||||
this.client.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
|
this.client.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||||
this.client.removeListener(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
|
|
||||||
}
|
}
|
||||||
SettingsStore.unwatchSetting(this.deviceClientInformationSettingWatcherRef);
|
SettingsStore.unwatchSetting(this.deviceClientInformationSettingWatcherRef);
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
@@ -228,11 +225,6 @@ export default class DeviceListener {
|
|||||||
this.updateClientInformation();
|
this.updateClientInformation();
|
||||||
};
|
};
|
||||||
|
|
||||||
private onToDeviceEvent = (event: MatrixEvent): void => {
|
|
||||||
// Receiving a 4S secret can mean we are in sync where we were not before.
|
|
||||||
if (event.getType() === EventType.SecretSend) this.recheck();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the key backup information from the server.
|
* Fetch the key backup information from the server.
|
||||||
*
|
*
|
||||||
@@ -281,29 +273,18 @@ export default class DeviceListener {
|
|||||||
|
|
||||||
private async doRecheck(): Promise<void> {
|
private async doRecheck(): Promise<void> {
|
||||||
if (!this.running || !this.client) return; // we have been stopped
|
if (!this.running || !this.client) return; // we have been stopped
|
||||||
const logSpan = new LogSpan(logger, "check_" + secureRandomString(4));
|
|
||||||
|
|
||||||
const cli = this.client;
|
const cli = this.client;
|
||||||
|
|
||||||
// cross-signing support was added to Matrix in MSC1756, which landed in spec v1.1
|
// cross-signing support was added to Matrix in MSC1756, which landed in spec v1.1
|
||||||
if (!(await cli.isVersionSupported("v1.1"))) {
|
if (!(await cli.isVersionSupported("v1.1"))) return;
|
||||||
logSpan.debug("cross-signing not supported");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const crypto = cli.getCrypto();
|
const crypto = cli.getCrypto();
|
||||||
if (!crypto) {
|
if (!crypto) return;
|
||||||
logSpan.debug("crypto not enabled");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't recheck until the initial sync is complete: lots of account data events will fire
|
// don't recheck until the initial sync is complete: lots of account data events will fire
|
||||||
// while the initial sync is processing and we don't need to recheck on each one of them
|
// while the initial sync is processing and we don't need to recheck on each one of them
|
||||||
// (we add a listener on sync to do once check after the initial sync is done)
|
// (we add a listener on sync to do once check after the initial sync is done)
|
||||||
if (!cli.isInitialSyncComplete()) {
|
if (!cli.isInitialSyncComplete()) return;
|
||||||
logSpan.debug("initial sync not yet complete");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const crossSigningReady = await crypto.isCrossSigningReady();
|
const crossSigningReady = await crypto.isCrossSigningReady();
|
||||||
const secretStorageReady = await crypto.isSecretStorageReady();
|
const secretStorageReady = await crypto.isSecretStorageReady();
|
||||||
@@ -325,7 +306,6 @@ export default class DeviceListener {
|
|||||||
await this.reportCryptoSessionStateToAnalytics(cli);
|
await this.reportCryptoSessionStateToAnalytics(cli);
|
||||||
|
|
||||||
if (this.dismissedThisDeviceToast || allSystemsReady) {
|
if (this.dismissedThisDeviceToast || allSystemsReady) {
|
||||||
logSpan.info("No toast needed");
|
|
||||||
hideSetupEncryptionToast();
|
hideSetupEncryptionToast();
|
||||||
|
|
||||||
this.checkKeyBackupStatus();
|
this.checkKeyBackupStatus();
|
||||||
@@ -336,33 +316,27 @@ export default class DeviceListener {
|
|||||||
if (!crossSigningReady) {
|
if (!crossSigningReady) {
|
||||||
// This account is legacy and doesn't have cross-signing set up at all.
|
// This account is legacy and doesn't have cross-signing set up at all.
|
||||||
// Prompt the user to set it up.
|
// Prompt the user to set it up.
|
||||||
logSpan.info("Cross-signing not ready: showing SET_UP_ENCRYPTION toast");
|
logger.info("Cross-signing not ready: showing SET_UP_ENCRYPTION toast");
|
||||||
showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION);
|
showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION);
|
||||||
} else if (!isCurrentDeviceTrusted) {
|
} else if (!isCurrentDeviceTrusted) {
|
||||||
// cross signing is ready but the current device is not trusted: prompt the user to verify
|
// cross signing is ready but the current device is not trusted: prompt the user to verify
|
||||||
logSpan.info("Current device not verified: showing VERIFY_THIS_SESSION toast");
|
logger.info("Current device not verified: showing VERIFY_THIS_SESSION toast");
|
||||||
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
|
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
|
||||||
} else if (!allCrossSigningSecretsCached) {
|
} else if (!allCrossSigningSecretsCached) {
|
||||||
// cross signing ready & device trusted, but we are missing secrets from our local cache.
|
// cross signing ready & device trusted, but we are missing secrets from our local cache.
|
||||||
// prompt the user to enter their recovery key.
|
// prompt the user to enter their recovery key.
|
||||||
logSpan.info(
|
logger.info("Some secrets not cached: showing KEY_STORAGE_OUT_OF_SYNC toast");
|
||||||
"Some secrets not cached: showing KEY_STORAGE_OUT_OF_SYNC toast",
|
|
||||||
crossSigningStatus.privateKeysCachedLocally,
|
|
||||||
);
|
|
||||||
showSetupEncryptionToast(SetupKind.KEY_STORAGE_OUT_OF_SYNC);
|
showSetupEncryptionToast(SetupKind.KEY_STORAGE_OUT_OF_SYNC);
|
||||||
} else if (defaultKeyId === null) {
|
} else if (defaultKeyId === null) {
|
||||||
// the user just hasn't set up 4S yet: prompt them to do so (unless they've explicitly said no to key storage)
|
// the user just hasn't set up 4S yet: prompt them to do so (unless they've explicitly said no to key storage)
|
||||||
const disabledEvent = cli.getAccountData(BACKUP_DISABLED_ACCOUNT_DATA_KEY);
|
const disabledEvent = cli.getAccountData(BACKUP_DISABLED_ACCOUNT_DATA_KEY);
|
||||||
if (!disabledEvent?.getContent().disabled) {
|
if (!disabledEvent?.getContent().disabled) {
|
||||||
logSpan.info("No default 4S key: showing SET_UP_RECOVERY toast");
|
|
||||||
showSetupEncryptionToast(SetupKind.SET_UP_RECOVERY);
|
showSetupEncryptionToast(SetupKind.SET_UP_RECOVERY);
|
||||||
} else {
|
|
||||||
logSpan.info("No default 4S key but backup disabled: no toast needed");
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// some other condition... yikes! Show the 'set up encryption' toast: this is what we previously did
|
// some other condition... yikes! Show the 'set up encryption' toast: this is what we previously did
|
||||||
// in 'other' situations. Possibly we should consider prompting for a full reset in this case?
|
// in 'other' situations. Possibly we should consider prompting for a full reset in this case?
|
||||||
logSpan.warn("Couldn't match encryption state to a known case: showing 'setup encryption' prompt", {
|
logger.warn("Couldn't match encryption state to a known case: showing 'setup encryption' prompt", {
|
||||||
crossSigningReady,
|
crossSigningReady,
|
||||||
secretStorageReady,
|
secretStorageReady,
|
||||||
allCrossSigningSecretsCached,
|
allCrossSigningSecretsCached,
|
||||||
@@ -371,8 +345,6 @@ export default class DeviceListener {
|
|||||||
});
|
});
|
||||||
showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION);
|
showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
logSpan.info("Not yet ready, but shouldShowSetupEncryptionToast==false");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This needs to be done after awaiting on getUserDeviceInfo() above, so
|
// This needs to be done after awaiting on getUserDeviceInfo() above, so
|
||||||
@@ -405,9 +377,9 @@ export default class DeviceListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logSpan.debug("Old unverified sessions: " + Array.from(oldUnverifiedDeviceIds).join(","));
|
logger.debug("Old unverified sessions: " + Array.from(oldUnverifiedDeviceIds).join(","));
|
||||||
logSpan.debug("New unverified sessions: " + Array.from(newUnverifiedDeviceIds).join(","));
|
logger.debug("New unverified sessions: " + Array.from(newUnverifiedDeviceIds).join(","));
|
||||||
logSpan.debug("Currently showing toasts for: " + Array.from(this.displayingToastsForDeviceIds).join(","));
|
logger.debug("Currently showing toasts for: " + Array.from(this.displayingToastsForDeviceIds).join(","));
|
||||||
|
|
||||||
const isBulkUnverifiedSessionsReminderSnoozed = isBulkUnverifiedDeviceReminderSnoozed();
|
const isBulkUnverifiedSessionsReminderSnoozed = isBulkUnverifiedDeviceReminderSnoozed();
|
||||||
|
|
||||||
@@ -432,7 +404,7 @@ export default class DeviceListener {
|
|||||||
// ...and hide any we don't need any more
|
// ...and hide any we don't need any more
|
||||||
for (const deviceId of this.displayingToastsForDeviceIds) {
|
for (const deviceId of this.displayingToastsForDeviceIds) {
|
||||||
if (!newUnverifiedDeviceIds.has(deviceId)) {
|
if (!newUnverifiedDeviceIds.has(deviceId)) {
|
||||||
logSpan.debug("Hiding unverified session toast for " + deviceId);
|
logger.debug("Hiding unverified session toast for " + deviceId);
|
||||||
hideUnverifiedSessionsToast(deviceId);
|
hideUnverifiedSessionsToast(deviceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -521,7 +521,8 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
|
|||||||
[KeyBindingAction.GoToHome]: {
|
[KeyBindingAction.GoToHome]: {
|
||||||
default: {
|
default: {
|
||||||
ctrlKey: true,
|
ctrlKey: true,
|
||||||
altKey: true,
|
altKey: !IS_MAC,
|
||||||
|
shiftKey: IS_MAC,
|
||||||
key: Key.H,
|
key: Key.H,
|
||||||
},
|
},
|
||||||
displayName: _td("keyboard|go_home_view"),
|
displayName: _td("keyboard|go_home_view"),
|
||||||
@@ -584,7 +585,7 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
|
|||||||
},
|
},
|
||||||
[KeyBindingAction.ToggleHiddenEventVisibility]: {
|
[KeyBindingAction.ToggleHiddenEventVisibility]: {
|
||||||
default: {
|
default: {
|
||||||
ctrlKey: true,
|
ctrlOrCmdKey: true,
|
||||||
shiftKey: true,
|
shiftKey: true,
|
||||||
key: Key.H,
|
key: Key.H,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2018 New Vector Ltd
|
Copyright 2018 New Vector Ltd
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
@@ -16,12 +16,13 @@ import { KeyBindingAction } from "../KeyboardShortcuts";
|
|||||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||||
|
|
||||||
interface IProps extends React.ComponentProps<typeof StyledCheckbox> {
|
interface IProps extends React.ComponentProps<typeof StyledCheckbox> {
|
||||||
|
label?: string;
|
||||||
onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent
|
onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent
|
||||||
onClose(): void; // gets called after onChange on KeyBindingAction.ActivateSelectedButton
|
onClose(): void; // gets called after onChange on KeyBindingAction.ActivateSelectedButton
|
||||||
}
|
}
|
||||||
|
|
||||||
// Semantic component for representing a styled role=menuitemcheckbox
|
// Semantic component for representing a styled role=menuitemcheckbox
|
||||||
export const StyledMenuItemCheckbox: React.FC<IProps> = ({ children, onChange, onClose, ...props }) => {
|
export const StyledMenuItemCheckbox: React.FC<IProps> = ({ children, label, onChange, onClose, ...props }) => {
|
||||||
const [onFocus, isActive, ref] = useRovingTabIndex<HTMLInputElement>();
|
const [onFocus, isActive, ref] = useRovingTabIndex<HTMLInputElement>();
|
||||||
|
|
||||||
const onKeyDown = (e: React.KeyboardEvent): void => {
|
const onKeyDown = (e: React.KeyboardEvent): void => {
|
||||||
@@ -62,6 +63,7 @@ export const StyledMenuItemCheckbox: React.FC<IProps> = ({ children, onChange, o
|
|||||||
<StyledCheckbox
|
<StyledCheckbox
|
||||||
{...props}
|
{...props}
|
||||||
role="menuitemcheckbox"
|
role="menuitemcheckbox"
|
||||||
|
aria-label={label}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
onKeyUp={onKeyUp}
|
onKeyUp={onKeyUp}
|
||||||
|
|||||||
@@ -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 { RoomListPanel } from "../views/rooms/RoomListPanel";
|
import { RoomListView } from "../views/rooms/RoomListView";
|
||||||
|
|
||||||
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">
|
||||||
<RoomListPanel activeSpace={this.state.activeSpace} />
|
<RoomListView activeSpace={this.state.activeSpace} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2021-2023 The Matrix.org Foundation C.I.C.
|
Copyright 2021-2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -16,7 +16,6 @@ import React, {
|
|||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
useId,
|
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
@@ -117,7 +116,6 @@ const Tile: React.FC<ITileProps> = ({
|
|||||||
const [showChildren, toggleShowChildren] = useStateToggle(true);
|
const [showChildren, toggleShowChildren] = useStateToggle(true);
|
||||||
const [onFocus, isActive, ref, nodeRef] = useRovingTabIndex();
|
const [onFocus, isActive, ref, nodeRef] = useRovingTabIndex();
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
const checkboxLabelId = useId();
|
|
||||||
|
|
||||||
const onPreviewClick = (ev: ButtonEvent): void => {
|
const onPreviewClick = (ev: ButtonEvent): void => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
@@ -174,14 +172,7 @@ const Tile: React.FC<ITileProps> = ({
|
|||||||
let checkbox: ReactElement | undefined;
|
let checkbox: ReactElement | undefined;
|
||||||
if (onToggleClick) {
|
if (onToggleClick) {
|
||||||
if (hasPermissions) {
|
if (hasPermissions) {
|
||||||
checkbox = (
|
checkbox = <StyledCheckbox checked={!!selected} onChange={onToggleClick} tabIndex={isActive ? 0 : -1} />;
|
||||||
<StyledCheckbox
|
|
||||||
role="presentation"
|
|
||||||
aria-labelledby={checkboxLabelId}
|
|
||||||
checked={!!selected}
|
|
||||||
tabIndex={-1}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
checkbox = (
|
checkbox = (
|
||||||
<TextWithTooltip
|
<TextWithTooltip
|
||||||
@@ -190,12 +181,7 @@ const Tile: React.FC<ITileProps> = ({
|
|||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StyledCheckbox
|
<StyledCheckbox disabled={true} tabIndex={isActive ? 0 : -1} />
|
||||||
role="presentation"
|
|
||||||
aria-labelledby={checkboxLabelId}
|
|
||||||
disabled={true}
|
|
||||||
tabIndex={-1}
|
|
||||||
/>
|
|
||||||
</TextWithTooltip>
|
</TextWithTooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -262,7 +248,7 @@ const Tile: React.FC<ITileProps> = ({
|
|||||||
<div className="mx_SpaceHierarchy_roomTile_item">
|
<div className="mx_SpaceHierarchy_roomTile_item">
|
||||||
<div className="mx_SpaceHierarchy_roomTile_avatar">{avatar}</div>
|
<div className="mx_SpaceHierarchy_roomTile_avatar">{avatar}</div>
|
||||||
<div className="mx_SpaceHierarchy_roomTile_name">
|
<div className="mx_SpaceHierarchy_roomTile_name">
|
||||||
<span id={checkboxLabelId}>{name}</span>
|
{name}
|
||||||
{joinedSection}
|
{joinedSection}
|
||||||
{suggestedSection}
|
{suggestedSection}
|
||||||
</div>
|
</div>
|
||||||
@@ -344,14 +330,11 @@ const Tile: React.FC<ITileProps> = ({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldToggle = hasPermissions && onToggleClick;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
className="mx_SpaceHierarchy_roomTileWrapper"
|
className="mx_SpaceHierarchy_roomTileWrapper"
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
aria-selected={selected}
|
aria-selected={selected}
|
||||||
aria-labelledby={checkboxLabelId}
|
|
||||||
aria-expanded={children ? showChildren : undefined}
|
aria-expanded={children ? showChildren : undefined}
|
||||||
>
|
>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
@@ -359,7 +342,7 @@ const Tile: React.FC<ITileProps> = ({
|
|||||||
mx_SpaceHierarchy_subspace: room.room_type === RoomType.Space,
|
mx_SpaceHierarchy_subspace: room.room_type === RoomType.Space,
|
||||||
mx_SpaceHierarchy_joining: busy,
|
mx_SpaceHierarchy_joining: busy,
|
||||||
})}
|
})}
|
||||||
onClick={shouldToggle ? onToggleClick : onPreviewClick}
|
onClick={hasPermissions && onToggleClick ? onToggleClick : onPreviewClick}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
|
|||||||
@@ -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_LegacyRoomList_iconNewRoom"
|
iconClassName="mx_RoomList_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_LegacyRoomList_iconNewVideoRoom"
|
iconClassName="mx_RoomList_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_LegacyRoomList_iconAddExistingRoom"
|
iconClassName="mx_RoomList_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_LegacyRoomList_iconPlus"
|
iconClassName="mx_RoomList_iconPlus"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type FlexProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any
|
|||||||
* The alignment of the flex children
|
* The alignment of the flex children
|
||||||
* @default start
|
* @default start
|
||||||
*/
|
*/
|
||||||
align?: "start" | "center" | "end" | "baseline" | "stretch" | "normal";
|
align?: "start" | "center" | "end" | "baseline" | "stretch";
|
||||||
/**
|
/**
|
||||||
* The justification of the flex children
|
* The justification of the flex children
|
||||||
* @default start
|
* @default start
|
||||||
|
|||||||
@@ -1,215 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 { JoinRule, type Room, RoomEvent, RoomType } from "matrix-js-sdk/src/matrix";
|
|
||||||
|
|
||||||
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
|
|
||||||
import { UIComponent } from "../../../settings/UIFeature";
|
|
||||||
import { useFeatureEnabled } from "../../../hooks/useSettings";
|
|
||||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
|
||||||
import PosthogTrackers from "../../../PosthogTrackers";
|
|
||||||
import { Action } from "../../../dispatcher/actions";
|
|
||||||
import { useEventEmitterState, useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
|
|
||||||
import {
|
|
||||||
getMetaSpaceName,
|
|
||||||
type MetaSpace,
|
|
||||||
type SpaceKey,
|
|
||||||
UPDATE_HOME_BEHAVIOUR,
|
|
||||||
UPDATE_SELECTED_SPACE,
|
|
||||||
} from "../../../stores/spaces";
|
|
||||||
import SpaceStore from "../../../stores/spaces/SpaceStore";
|
|
||||||
import {
|
|
||||||
shouldShowSpaceSettings,
|
|
||||||
showCreateNewRoom,
|
|
||||||
showSpaceInvite,
|
|
||||||
showSpacePreferences,
|
|
||||||
showSpaceSettings,
|
|
||||||
} from "../../../utils/space";
|
|
||||||
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
|
|
||||||
import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to get the active space and its title.
|
|
||||||
*/
|
|
||||||
function useSpace(): { activeSpace: Room | null; title: string } {
|
|
||||||
const [spaceKey, activeSpace] = useEventEmitterState<[SpaceKey, Room | null]>(
|
|
||||||
SpaceStore.instance,
|
|
||||||
UPDATE_SELECTED_SPACE,
|
|
||||||
() => [SpaceStore.instance.activeSpace, SpaceStore.instance.activeSpaceRoom],
|
|
||||||
);
|
|
||||||
const spaceName = useTypedEventEmitterState(activeSpace ?? undefined, RoomEvent.Name, () => activeSpace?.name);
|
|
||||||
const allRoomsInHome = useEventEmitterState(
|
|
||||||
SpaceStore.instance,
|
|
||||||
UPDATE_HOME_BEHAVIOUR,
|
|
||||||
() => SpaceStore.instance.allRoomsInHome,
|
|
||||||
);
|
|
||||||
|
|
||||||
const title = spaceName ?? getMetaSpaceName(spaceKey as MetaSpace, allRoomsInHome);
|
|
||||||
|
|
||||||
return {
|
|
||||||
activeSpace,
|
|
||||||
title,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RoomListHeaderViewState {
|
|
||||||
/**
|
|
||||||
* The title of the room list
|
|
||||||
*/
|
|
||||||
title: string;
|
|
||||||
/**
|
|
||||||
* Whether to display the compose menu
|
|
||||||
* True if the user can create rooms
|
|
||||||
*/
|
|
||||||
displayComposeMenu: boolean;
|
|
||||||
/**
|
|
||||||
* Whether to display the space menu
|
|
||||||
* True if there is an active space
|
|
||||||
*/
|
|
||||||
displaySpaceMenu: boolean;
|
|
||||||
/**
|
|
||||||
* Whether the user can create rooms
|
|
||||||
*/
|
|
||||||
canCreateRoom: boolean;
|
|
||||||
/**
|
|
||||||
* Whether the user can create video rooms
|
|
||||||
*/
|
|
||||||
canCreateVideoRoom: boolean;
|
|
||||||
/**
|
|
||||||
* Whether the user can invite in the active space
|
|
||||||
*/
|
|
||||||
canInviteInSpace: boolean;
|
|
||||||
/**
|
|
||||||
* Whether the user can access space settings
|
|
||||||
*/
|
|
||||||
canAccessSpaceSettings: boolean;
|
|
||||||
/**
|
|
||||||
* Create a chat room
|
|
||||||
* @param e - The click event
|
|
||||||
*/
|
|
||||||
createChatRoom: (e: Event) => void;
|
|
||||||
/**
|
|
||||||
* Create a room
|
|
||||||
* @param e - The click event
|
|
||||||
*/
|
|
||||||
createRoom: (e: Event) => void;
|
|
||||||
/**
|
|
||||||
* Create a video room
|
|
||||||
*/
|
|
||||||
createVideoRoom: () => void;
|
|
||||||
/**
|
|
||||||
* Open the active space home
|
|
||||||
*/
|
|
||||||
openSpaceHome: () => void;
|
|
||||||
/**
|
|
||||||
* Display the space invite dialog
|
|
||||||
*/
|
|
||||||
inviteInSpace: () => void;
|
|
||||||
/**
|
|
||||||
* Open the space preferences
|
|
||||||
*/
|
|
||||||
openSpacePreferences: () => void;
|
|
||||||
/**
|
|
||||||
* Open the space settings
|
|
||||||
*/
|
|
||||||
openSpaceSettings: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* View model for the RoomListHeader.
|
|
||||||
*/
|
|
||||||
export function useRoomListHeaderViewModel(): RoomListHeaderViewState {
|
|
||||||
const matrixClient = useMatrixClientContext();
|
|
||||||
const { activeSpace, title } = useSpace();
|
|
||||||
|
|
||||||
const canCreateRoom = shouldShowComponent(UIComponent.CreateRooms);
|
|
||||||
const canCreateVideoRoom = useFeatureEnabled("feature_video_rooms");
|
|
||||||
const displayComposeMenu = canCreateRoom;
|
|
||||||
const displaySpaceMenu = Boolean(activeSpace);
|
|
||||||
const canInviteInSpace = Boolean(
|
|
||||||
activeSpace?.getJoinRule() === JoinRule.Public || activeSpace?.canInvite(matrixClient.getSafeUserId()),
|
|
||||||
);
|
|
||||||
const canAccessSpaceSettings = Boolean(activeSpace && shouldShowSpaceSettings(activeSpace));
|
|
||||||
|
|
||||||
/* Actions */
|
|
||||||
|
|
||||||
const createChatRoom = useCallback((e: Event) => {
|
|
||||||
defaultDispatcher.fire(Action.CreateChat);
|
|
||||||
PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateChatItem", e);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const createRoom = useCallback(
|
|
||||||
(e: Event) => {
|
|
||||||
if (activeSpace) {
|
|
||||||
showCreateNewRoom(activeSpace);
|
|
||||||
} else {
|
|
||||||
defaultDispatcher.fire(Action.CreateRoom);
|
|
||||||
}
|
|
||||||
PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateRoomItem", e);
|
|
||||||
},
|
|
||||||
[activeSpace],
|
|
||||||
);
|
|
||||||
|
|
||||||
const elementCallVideoRoomsEnabled = useFeatureEnabled("feature_element_call_video_rooms");
|
|
||||||
const createVideoRoom = useCallback(() => {
|
|
||||||
const type = elementCallVideoRoomsEnabled ? RoomType.UnstableCall : RoomType.ElementVideo;
|
|
||||||
if (activeSpace) {
|
|
||||||
showCreateNewRoom(activeSpace, type);
|
|
||||||
} else {
|
|
||||||
defaultDispatcher.dispatch({
|
|
||||||
action: Action.CreateRoom,
|
|
||||||
type,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [activeSpace, elementCallVideoRoomsEnabled]);
|
|
||||||
|
|
||||||
const openSpaceHome = useCallback(() => {
|
|
||||||
// openSpaceHome is only available when there is an active space
|
|
||||||
if (!activeSpace) return;
|
|
||||||
defaultDispatcher.dispatch<ViewRoomPayload>({
|
|
||||||
action: Action.ViewRoom,
|
|
||||||
room_id: activeSpace.roomId,
|
|
||||||
metricsTrigger: undefined,
|
|
||||||
});
|
|
||||||
}, [activeSpace]);
|
|
||||||
|
|
||||||
const inviteInSpace = useCallback(() => {
|
|
||||||
// inviteInSpace is only available when there is an active space
|
|
||||||
if (!activeSpace) return;
|
|
||||||
showSpaceInvite(activeSpace);
|
|
||||||
}, [activeSpace]);
|
|
||||||
|
|
||||||
const openSpacePreferences = useCallback(() => {
|
|
||||||
// openSpacePreferences is only available when there is an active space
|
|
||||||
if (!activeSpace) return;
|
|
||||||
showSpacePreferences(activeSpace);
|
|
||||||
}, [activeSpace]);
|
|
||||||
|
|
||||||
const openSpaceSettings = useCallback(() => {
|
|
||||||
// openSpaceSettings is only available when there is an active space
|
|
||||||
if (!activeSpace) return;
|
|
||||||
showSpaceSettings(activeSpace);
|
|
||||||
}, [activeSpace]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
title,
|
|
||||||
displayComposeMenu,
|
|
||||||
displaySpaceMenu,
|
|
||||||
canCreateRoom,
|
|
||||||
canCreateVideoRoom,
|
|
||||||
canInviteInSpace,
|
|
||||||
canAccessSpaceSettings,
|
|
||||||
createChatRoom,
|
|
||||||
createRoom,
|
|
||||||
createVideoRoom,
|
|
||||||
openSpaceHome,
|
|
||||||
inviteInSpace,
|
|
||||||
openSpacePreferences,
|
|
||||||
openSpaceSettings,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
/*
|
|
||||||
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 };
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,7 @@ import React, { createRef, type ReactNode } from "react";
|
|||||||
import { ClientRendezvousFailureReason, MSC4108FailureReason } from "matrix-js-sdk/src/rendezvous";
|
import { ClientRendezvousFailureReason, MSC4108FailureReason } from "matrix-js-sdk/src/rendezvous";
|
||||||
import ChevronLeftIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-left";
|
import ChevronLeftIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-left";
|
||||||
import CheckCircleSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/check-circle-solid";
|
import CheckCircleSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/check-circle-solid";
|
||||||
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
|
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
|
||||||
import { Heading, MFAInput, Text } from "@vector-im/compound-web";
|
import { Heading, MFAInput, Text } from "@vector-im/compound-web";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { QrCodeIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
import { QrCodeIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
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.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { type ReactElement, type ReactNode, useContext, useId, useMemo, useRef, useState } from "react";
|
import React, { type ReactElement, type ReactNode, useContext, useMemo, useRef, useState } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { type Room, EventType } from "matrix-js-sdk/src/matrix";
|
import { type Room, EventType } from "matrix-js-sdk/src/matrix";
|
||||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||||
@@ -53,9 +53,8 @@ export const Entry: React.FC<{
|
|||||||
checked: boolean;
|
checked: boolean;
|
||||||
onChange?(value: boolean): void;
|
onChange?(value: boolean): void;
|
||||||
}> = ({ room, checked, onChange }) => {
|
}> = ({ room, checked, onChange }) => {
|
||||||
const id = useId();
|
|
||||||
return (
|
return (
|
||||||
<li id={id} className="mx_AddExistingToSpace_entry" aria-label={room.name}>
|
<label className="mx_AddExistingToSpace_entry">
|
||||||
{room?.isSpaceRoom() ? (
|
{room?.isSpaceRoom() ? (
|
||||||
<RoomAvatar room={room} size="32px" />
|
<RoomAvatar room={room} size="32px" />
|
||||||
) : (
|
) : (
|
||||||
@@ -63,12 +62,11 @@ export const Entry: React.FC<{
|
|||||||
)}
|
)}
|
||||||
<span className="mx_AddExistingToSpace_entry_name">{room.name}</span>
|
<span className="mx_AddExistingToSpace_entry_name">{room.name}</span>
|
||||||
<StyledCheckbox
|
<StyledCheckbox
|
||||||
aria-labelledby={id}
|
|
||||||
onChange={onChange ? (e) => onChange(e.currentTarget.checked) : undefined}
|
onChange={onChange ? (e) => onChange(e.currentTarget.checked) : undefined}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
disabled={!onChange}
|
disabled={!onChange}
|
||||||
/>
|
/>
|
||||||
</li>
|
</label>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -359,7 +357,6 @@ const defaultRendererFactory =
|
|||||||
<div className="mx_AddExistingToSpace_section">
|
<div className="mx_AddExistingToSpace_section">
|
||||||
<h3>{_t(title)}</h3>
|
<h3>{_t(title)}</h3>
|
||||||
<LazyRenderList
|
<LazyRenderList
|
||||||
element="ul"
|
|
||||||
itemHeight={ROW_HEIGHT}
|
itemHeight={ROW_HEIGHT}
|
||||||
items={rooms}
|
items={rooms}
|
||||||
scrollTop={scrollTop}
|
scrollTop={scrollTop}
|
||||||
|
|||||||
@@ -9,13 +9,12 @@ 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, { type ReactNode } from "react";
|
import React 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, RageshakeError } from "../../../rageshake/submit-rageshake";
|
import sendBugReport, { downloadBugReport } 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";
|
||||||
@@ -27,7 +26,7 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
|
|||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import { getBrowserSupport } from "../../../SupportedBrowser";
|
import { getBrowserSupport } from "../../../SupportedBrowser";
|
||||||
|
|
||||||
export interface BugReportDialogProps {
|
interface IProps {
|
||||||
onFinished: (success: boolean) => void;
|
onFinished: (success: boolean) => void;
|
||||||
initialText?: string;
|
initialText?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
@@ -37,7 +36,7 @@ export interface BugReportDialogProps {
|
|||||||
interface IState {
|
interface IState {
|
||||||
sendLogs: boolean;
|
sendLogs: boolean;
|
||||||
busy: boolean;
|
busy: boolean;
|
||||||
err: ReactNode | null;
|
err: string | null;
|
||||||
issueUrl: string;
|
issueUrl: string;
|
||||||
text: string;
|
text: string;
|
||||||
progress: string | null;
|
progress: string | null;
|
||||||
@@ -45,11 +44,11 @@ interface IState {
|
|||||||
downloadProgress: string | null;
|
downloadProgress: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class BugReportDialog extends React.Component<BugReportDialogProps, IState> {
|
export default class BugReportDialog extends React.Component<IProps, IState> {
|
||||||
private unmounted: boolean;
|
private unmounted: boolean;
|
||||||
private issueRef: React.RefObject<Field>;
|
private issueRef: React.RefObject<Field>;
|
||||||
|
|
||||||
public constructor(props: BugReportDialogProps) {
|
public constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -90,42 +89,6 @@ export default class BugReportDialog extends React.Component<BugReportDialogProp
|
|||||||
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({
|
||||||
@@ -163,7 +126,7 @@ export default class BugReportDialog extends React.Component<BugReportDialogProp
|
|||||||
this.setState({
|
this.setState({
|
||||||
busy: false,
|
busy: false,
|
||||||
progress: null,
|
progress: null,
|
||||||
err: this.getErrorText(err),
|
err: _t("bug_reporting|failed_send_logs") + `${err.message}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -192,7 +155,7 @@ export default class BugReportDialog extends React.Component<BugReportDialogProp
|
|||||||
this.setState({
|
this.setState({
|
||||||
downloadBusy: false,
|
downloadBusy: false,
|
||||||
downloadProgress:
|
downloadProgress:
|
||||||
_t("bug_reporting|failed_download_logs") + `${err instanceof Error ? err.message : ""}`,
|
_t("bug_reporting|failed_send_logs") + `${err instanceof Error ? err.message : ""}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -113,13 +113,12 @@ const BulkRedactDialog: React.FC<Props> = (props) => {
|
|||||||
<div className="mx_Dialog_content" id="mx_Dialog_content">
|
<div className="mx_Dialog_content" id="mx_Dialog_content">
|
||||||
<p>{_t("user_info|redact|confirm_description_1", { count, user })}</p>
|
<p>{_t("user_info|redact|confirm_description_1", { count, user })}</p>
|
||||||
<p>{_t("user_info|redact|confirm_description_2")}</p>
|
<p>{_t("user_info|redact|confirm_description_2")}</p>
|
||||||
<StyledCheckbox
|
<StyledCheckbox checked={keepStateEvents} onChange={(e) => setKeepStateEvents(e.target.checked)}>
|
||||||
description={_t("user_info|redact|confirm_keep_state_explainer")}
|
|
||||||
checked={keepStateEvents}
|
|
||||||
onChange={(e) => setKeepStateEvents(e.target.checked)}
|
|
||||||
>
|
|
||||||
{_t("user_info|redact|confirm_keep_state_label")}
|
{_t("user_info|redact|confirm_keep_state_label")}
|
||||||
</StyledCheckbox>
|
</StyledCheckbox>
|
||||||
|
<div className="mx_BulkRedactDialog_checkboxMicrocopy">
|
||||||
|
{_t("user_info|redact|confirm_keep_state_explainer")}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t("user_info|redact|confirm_button", { count })}
|
primaryButton={_t("user_info|redact|confirm_button", { count })}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -78,6 +78,7 @@ const GenericFeatureFeedbackDialog: React.FC<IProps> = ({
|
|||||||
}}
|
}}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StyledCheckbox
|
<StyledCheckbox
|
||||||
checked={canContact}
|
checked={canContact}
|
||||||
onChange={(e) => setCanContact((e.target as HTMLInputElement).checked)}
|
onChange={(e) => setCanContact((e.target as HTMLInputElement).checked)}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -44,23 +44,22 @@ const Entry: React.FC<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_ManageRestrictedJoinRuleDialog_entry">
|
<label className="mx_ManageRestrictedJoinRuleDialog_entry">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
{localRoom ? <RoomAvatar room={room} size="20px" /> : <RoomAvatar oobData={room} size="20px" />}
|
||||||
|
<span className="mx_ManageRestrictedJoinRuleDialog_entry_name">{room.name}</span>
|
||||||
|
</div>
|
||||||
|
{description && (
|
||||||
|
<div className="mx_ManageRestrictedJoinRuleDialog_entry_description">{description}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<StyledCheckbox
|
<StyledCheckbox
|
||||||
onChange={onChange ? (e) => onChange(e.target.checked) : undefined}
|
onChange={onChange ? (e) => onChange(e.target.checked) : undefined}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
disabled={!onChange}
|
disabled={!onChange}
|
||||||
description={description}
|
/>
|
||||||
>
|
</label>
|
||||||
<div>
|
|
||||||
{localRoom ? (
|
|
||||||
<RoomAvatar role="none" room={room} size="20px" />
|
|
||||||
) : (
|
|
||||||
<RoomAvatar oobData={room} size="20px" />
|
|
||||||
)}
|
|
||||||
<span className="mx_ManageRestrictedJoinRuleDialog_entry_name">{room.name}</span>
|
|
||||||
</div>
|
|
||||||
</StyledCheckbox>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024,2025 New Vector Ltd.
|
Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
@@ -45,13 +45,14 @@ const SpacePreferencesAppearanceTab: React.FC<Pick<IProps, "space">> = ({ space
|
|||||||
!showPeople,
|
!showPeople,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
description={_t("space|preferences|show_people_in_space", {
|
|
||||||
spaceName: space.name,
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
{_t("common|people")}
|
{_t("common|people")}
|
||||||
</StyledCheckbox>
|
</StyledCheckbox>
|
||||||
<SettingsSubsectionText />
|
<SettingsSubsectionText>
|
||||||
|
{_t("space|preferences|show_people_in_space", {
|
||||||
|
spaceName: space.name,
|
||||||
|
})}
|
||||||
|
</SettingsSubsectionText>
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
</SettingsTab>
|
</SettingsTab>
|
||||||
|
|||||||