Compare commits
18 Commits
midhun/mod
...
t3chguy/mo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01ffd72b9d | ||
|
|
0eee4e3a18 | ||
|
|
98ed46b0f3 | ||
|
|
78690f032d | ||
|
|
607dbb03df | ||
|
|
07e6f0af35 | ||
|
|
be318f9cb8 | ||
|
|
5ff42e8548 | ||
|
|
6599601000 | ||
|
|
6fb9098c30 | ||
|
|
9e50e59168 | ||
|
|
008b004976 | ||
|
|
68a2748813 | ||
|
|
61e266e8e5 | ||
|
|
a7c7c27ae8 | ||
|
|
1414b8ff54 | ||
|
|
c6a5d288ed | ||
|
|
8c2a035302 |
@@ -1,6 +1,11 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
|
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
|
||||||
extends: ["plugin:matrix-org/babel", "plugin:matrix-org/react", "plugin:matrix-org/a11y"],
|
extends: [
|
||||||
|
"plugin:matrix-org/babel",
|
||||||
|
"plugin:matrix-org/react",
|
||||||
|
"plugin:matrix-org/a11y",
|
||||||
|
"plugin:storybook/recommended",
|
||||||
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: ["./tsconfig.json"],
|
project: ["./tsconfig.json"],
|
||||||
},
|
},
|
||||||
|
|||||||
9
.github/CODEOWNERS
vendored
@@ -17,16 +17,9 @@
|
|||||||
/playwright/e2e/crypto/ @element-hq/element-crypto-web-reviewers
|
/playwright/e2e/crypto/ @element-hq/element-crypto-web-reviewers
|
||||||
/playwright/e2e/settings/encryption-user-tab/ @element-hq/element-crypto-web-reviewers
|
/playwright/e2e/settings/encryption-user-tab/ @element-hq/element-crypto-web-reviewers
|
||||||
|
|
||||||
|
|
||||||
/src/models/Call.ts @element-hq/element-call-reviewers
|
|
||||||
/src/call-types.ts @element-hq/element-call-reviewers
|
|
||||||
/src/components/views/voip @element-hq/element-call-reviewers
|
|
||||||
/playwright/e2e/voip/element-call.spec.ts @element-hq/element-call-reviewers
|
|
||||||
|
|
||||||
# Ignore translations as those will be updated by GHA for Localazy download
|
# Ignore translations as those will be updated by GHA for Localazy download
|
||||||
/src/i18n/strings
|
/src/i18n/strings
|
||||||
/src/i18n/strings/en_EN.json @element-hq/element-web-reviewers
|
/src/i18n/strings/en_EN.json @element-hq/element-web-reviewers
|
||||||
# Ignore the synapse & mas plugins as this is updated by GHA for docker image updating
|
# Ignore the synapse plugin as this is updated by GHA for docker image updating
|
||||||
/playwright/testcontainers/synapse.ts
|
/playwright/testcontainers/synapse.ts
|
||||||
/playwright/testcontainers/mas.ts
|
|
||||||
|
|
||||||
|
|||||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
- [ ] I have read through [review guidelines](../docs/review.md) and [CONTRIBUTING.md](../CONTRIBUTING.md).
|
|
||||||
- [ ] Tests written for new code (and old code if feasible).
|
- [ ] Tests written for new code (and old code if feasible).
|
||||||
- [ ] New or updated `public`/`exported` symbols have accurate [TSDoc](https://tsdoc.org/) documentation.
|
- [ ] New or updated `public`/`exported` symbols have accurate [TSDoc](https://tsdoc.org/) documentation.
|
||||||
- [ ] Linter and other CI checks pass.
|
- [ ] Linter and other CI checks pass.
|
||||||
|
|||||||
23
.github/workflows/build.yml
vendored
@@ -10,7 +10,8 @@ concurrency:
|
|||||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
# develop pushes and repository_dispatch handled in build_develop.yaml
|
# develop pushes and repository_dispatch handled in build_develop.yaml
|
||||||
env:
|
env:
|
||||||
# This must be set for fetchdep.sh to get the right branch
|
# These must be set for fetchdep.sh to get the right branch
|
||||||
|
REPOSITORY: ${{ github.repository }}
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
permissions: {} # No permissions required
|
permissions: {} # No permissions required
|
||||||
jobs:
|
jobs:
|
||||||
@@ -42,9 +43,9 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
# Disable cache on Windows as it is slower than not caching
|
# Disable cache on Windows as it is slower than not caching
|
||||||
# https://github.com/actions/setup-node/issues/975
|
# https://github.com/actions/setup-node/issues/975
|
||||||
@@ -55,7 +56,15 @@ jobs:
|
|||||||
- run: yarn config set network-timeout 300000
|
- run: yarn config set network-timeout 300000
|
||||||
|
|
||||||
- name: Fetch layered build
|
- name: Fetch layered build
|
||||||
run: ./scripts/layered.sh
|
id: layered_build
|
||||||
|
env:
|
||||||
|
# tell layered.sh to check out the right sha of the JS-SDK & EW, if they were given one
|
||||||
|
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
|
||||||
|
run: |
|
||||||
|
scripts/layered.sh
|
||||||
|
JSSDK_SHA=$(git -C matrix-js-sdk rev-parse --short=12 HEAD)
|
||||||
|
VECTOR_SHA=$(git rev-parse --short=12 HEAD)
|
||||||
|
echo "VERSION=$VECTOR_SHA--js-$JSSDK_SHA" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Copy config
|
- name: Copy config
|
||||||
run: cp element.io/develop/config.json config.json
|
run: cp element.io/develop/config.json config.json
|
||||||
@@ -63,10 +72,12 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
CI_PACKAGE: true
|
CI_PACKAGE: true
|
||||||
run: VERSION=$(scripts/get-version-from-git.sh) yarn build
|
VERSION: "${{ steps.layered_build.outputs.VERSION }}"
|
||||||
|
run: |
|
||||||
|
yarn build
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
with:
|
with:
|
||||||
name: webapp-${{ matrix.image }}
|
name: webapp-${{ matrix.image }}
|
||||||
path: webapp
|
path: webapp
|
||||||
|
|||||||
4
.github/workflows/build_debian.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
R2_URL: ${{ vars.CF_R2_S3_API }}
|
R2_URL: ${{ vars.CF_R2_S3_API }}
|
||||||
VERSION: ${{ github.ref_name }}
|
VERSION: ${{ github.ref_name }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
- name: Download package
|
- name: Download package
|
||||||
run: |
|
run: |
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
dpkg-gencontrol -v"$VERSION" -ldebian/tmp/DEBIAN/changelog
|
dpkg-gencontrol -v"$VERSION" -ldebian/tmp/DEBIAN/changelog
|
||||||
dpkg-deb -Zxz --root-owner-group --build debian/tmp element-web.deb
|
dpkg-deb -Zxz --root-owner-group --build debian/tmp element-web.deb
|
||||||
|
|
||||||
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
with:
|
with:
|
||||||
name: element-web.deb
|
name: element-web.deb
|
||||||
path: element-web.deb
|
path: element-web.deb
|
||||||
|
|||||||
6
.github/workflows/build_develop.yml
vendored
@@ -26,9 +26,9 @@ jobs:
|
|||||||
R2_URL: ${{ vars.CF_R2_S3_API }}
|
R2_URL: ${{ vars.CF_R2_S3_API }}
|
||||||
R2_PUBLIC_URL: "https://element-web-develop.element.io"
|
R2_PUBLIC_URL: "https://element-web-develop.element.io"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
|
|
||||||
- run: mv dist/element-*.tar.gz dist/develop.tar.gz
|
- run: mv dist/element-*.tar.gz dist/develop.tar.gz
|
||||||
|
|
||||||
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
with:
|
with:
|
||||||
name: webapp
|
name: webapp
|
||||||
path: dist/develop.tar.gz
|
path: dist/develop.tar.gz
|
||||||
|
|||||||
2
.github/workflows/deploy.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
SITE: ${{ inputs.site || 'staging.element.io' }}
|
SITE: ${{ inputs.site || 'staging.element.io' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
- name: Load GPG key
|
- name: Load GPG key
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
10
.github/workflows/docker.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
TEST_TAG: vectorim/element-web:test
|
TEST_TAG: vectorim/element-web:test
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 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
|
||||||
|
|
||||||
@@ -37,14 +37,14 @@ jobs:
|
|||||||
install: true
|
install: true
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
|
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3
|
||||||
if: github.event_name != 'pull_request'
|
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@5e57cd118135c172c3672efd75eb46360885c0ef # v3
|
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
@@ -132,7 +132,7 @@ jobs:
|
|||||||
cosign sign --yes ${images}
|
cosign sign --yes ${images}
|
||||||
|
|
||||||
- name: Update repo description
|
- name: Update repo description
|
||||||
uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5
|
uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
@@ -141,7 +141,7 @@ jobs:
|
|||||||
repository: vectorim/element-web
|
repository: vectorim/element-web
|
||||||
|
|
||||||
- name: Repository Dispatch
|
- name: Repository Dispatch
|
||||||
uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4
|
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
with:
|
with:
|
||||||
repository: element-hq/element-web-pro
|
repository: element-hq/element-web-pro
|
||||||
|
|||||||
10
.github/workflows/docs.yml
vendored
@@ -17,23 +17,23 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Fetch element-desktop
|
- name: Fetch element-desktop
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
repository: element-hq/element-desktop
|
repository: element-hq/element-desktop
|
||||||
path: element-desktop
|
path: element-desktop
|
||||||
|
|
||||||
- name: Fetch element-web
|
- name: Fetch element-web
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
path: element-web
|
path: element-web
|
||||||
|
|
||||||
- name: Fetch matrix-js-sdk
|
- name: Fetch matrix-js-sdk
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
repository: matrix-org/matrix-js-sdk
|
repository: matrix-org/matrix-js-sdk
|
||||||
path: matrix-js-sdk
|
path: matrix-js-sdk
|
||||||
|
|
||||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
cache-dependency-path: element-web/yarn.lock
|
cache-dependency-path: element-web/yarn.lock
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
run: mdbook build
|
run: mdbook build
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4
|
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
|
||||||
with:
|
with:
|
||||||
path: ./book
|
path: ./book
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
actions: read
|
actions: read
|
||||||
steps:
|
steps:
|
||||||
- name: Download HTML report
|
- name: Download HTML report
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run-id: ${{ github.event.workflow_run.id }}
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
|||||||
37
.github/workflows/end-to-end-tests.yaml
vendored
@@ -50,20 +50,25 @@ jobs:
|
|||||||
runners-matrix: ${{ steps.runner-vars.outputs.matrix }}
|
runners-matrix: ${{ steps.runner-vars.outputs.matrix }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
repository: element-hq/element-web
|
repository: element-hq/element-web
|
||||||
|
|
||||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
|
|
||||||
- name: Fetch layered build
|
- name: Fetch layered build
|
||||||
|
id: layered_build
|
||||||
env:
|
env:
|
||||||
# tell layered.sh to check out the right sha of the JS-SDK & EW, if they were given one
|
# tell layered.sh to check out the right sha of the JS-SDK & EW, if they were given one
|
||||||
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
|
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
|
||||||
run: scripts/layered.sh
|
run: |
|
||||||
|
scripts/layered.sh
|
||||||
|
JSSDK_SHA=$(git -C matrix-js-sdk rev-parse --short=12 HEAD)
|
||||||
|
VECTOR_SHA=$(git rev-parse --short=12 HEAD)
|
||||||
|
echo "VERSION=$VECTOR_SHA--js-$JSSDK_SHA" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Copy config
|
- name: Copy config
|
||||||
run: cp element.io/develop/config.json config.json
|
run: cp element.io/develop/config.json config.json
|
||||||
@@ -71,10 +76,12 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
CI_PACKAGE: true
|
CI_PACKAGE: true
|
||||||
run: VERSION=$(scripts/get-version-from-git.sh) yarn build
|
VERSION: "${{ steps.layered_build.outputs.VERSION }}"
|
||||||
|
run: |
|
||||||
|
yarn build
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
with:
|
with:
|
||||||
name: webapp
|
name: webapp
|
||||||
path: webapp
|
path: webapp
|
||||||
@@ -82,7 +89,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Calculate runner variables
|
- name: Calculate runner variables
|
||||||
id: runner-vars
|
id: runner-vars
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const numRunners = parseInt(process.env.NUM_RUNNERS, 10);
|
const numRunners = parseInt(process.env.NUM_RUNNERS, 10);
|
||||||
@@ -122,18 +129,18 @@ jobs:
|
|||||||
- runAllTests: false
|
- runAllTests: false
|
||||||
project: Pinecone
|
project: Pinecone
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
repository: element-hq/element-web
|
repository: element-hq/element-web
|
||||||
|
|
||||||
- name: 📥 Download artifact
|
- name: 📥 Download artifact
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||||
with:
|
with:
|
||||||
name: webapp
|
name: webapp
|
||||||
path: webapp
|
path: webapp
|
||||||
|
|
||||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
cache-dependency-path: yarn.lock
|
cache-dependency-path: yarn.lock
|
||||||
@@ -147,7 +154,7 @@ jobs:
|
|||||||
run: echo "version=$(yarn list --pattern @playwright/test --depth=0 --json --non-interactive --no-progress | jq -r '.data.trees[].name')" >> $GITHUB_OUTPUT
|
run: echo "version=$(yarn list --pattern @playwright/test --depth=0 --json --non-interactive --no-progress | jq -r '.data.trees[].name')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Cache playwright binaries
|
- name: Cache playwright binaries
|
||||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
|
||||||
id: playwright-cache
|
id: playwright-cache
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/ms-playwright
|
path: ~/.cache/ms-playwright
|
||||||
@@ -172,7 +179,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload blob report to GitHub Actions Artifacts
|
- name: Upload blob report to GitHub Actions Artifacts
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
with:
|
with:
|
||||||
name: all-blob-reports-${{ matrix.project }}-${{ matrix.runner }}
|
name: all-blob-reports-${{ matrix.project }}-${{ matrix.runner }}
|
||||||
path: blob-report
|
path: blob-report
|
||||||
@@ -194,13 +201,13 @@ jobs:
|
|||||||
if: always()
|
if: always()
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
if: inputs.skip != true
|
if: inputs.skip != true
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
repository: element-hq/element-web
|
repository: element-hq/element-web
|
||||||
|
|
||||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
if: inputs.skip != true
|
if: inputs.skip != true
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
@@ -212,7 +219,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Download blob reports from GitHub Actions Artifacts
|
- name: Download blob reports from GitHub Actions Artifacts
|
||||||
if: inputs.skip != true
|
if: inputs.skip != true
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||||
with:
|
with:
|
||||||
pattern: all-blob-reports-*
|
pattern: all-blob-reports-*
|
||||||
path: all-blob-reports
|
path: all-blob-reports
|
||||||
@@ -228,7 +235,7 @@ jobs:
|
|||||||
# Upload the HTML report even if one of our reporters fails, this can happen when stale screenshots are detected
|
# Upload the HTML report even if one of our reporters fails, this can happen when stale screenshots are detected
|
||||||
- name: Upload HTML report
|
- name: Upload HTML report
|
||||||
if: always() && inputs.skip != true
|
if: always() && inputs.skip != true
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
with:
|
with:
|
||||||
name: html-report
|
name: html-report
|
||||||
path: playwright-report
|
path: playwright-report
|
||||||
|
|||||||
4
.github/workflows/issue_closed.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
name: Tidy closed issues
|
name: Tidy closed issues
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||||
id: main
|
id: main
|
||||||
with:
|
with:
|
||||||
# PAT needed as the GITHUB_TOKEN won't be able to see cross-references from other orgs (matrix-org)
|
# PAT needed as the GITHUB_TOKEN won't be able to see cross-references from other orgs (matrix-org)
|
||||||
@@ -142,7 +142,7 @@ jobs:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||||
name: Close duplicate as Not Planned
|
name: Close duplicate as Not Planned
|
||||||
if: steps.main.outputs.closeAsNotPlanned
|
if: steps.main.outputs.closeAsNotPlanned
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/netlify.yaml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
Exercise caution. Use test accounts.
|
Exercise caution. Use test accounts.
|
||||||
|
|
||||||
- name: 📥 Download artifact
|
- name: 📥 Download artifact
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run-id: ${{ github.event.workflow_run.id }}
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
|||||||
2
.github/workflows/pending-reviews.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
URL: "https://github.com/pulls?q=is%3Apr+is%3Aopen+repo%3Amatrix-org%2Fmatrix-js-sdk+repo%3Amatrix-org%2Fmatrix-react-sdk+repo%3Aelement-hq%2Felement-web+repo%3Aelement-hq%2Felement-desktop+review-requested%3A%40me+sort%3Aupdated-desc+"
|
URL: "https://github.com/pulls?q=is%3Apr+is%3Aopen+repo%3Amatrix-org%2Fmatrix-js-sdk+repo%3Amatrix-org%2Fmatrix-react-sdk+repo%3Aelement-hq%2Felement-web+repo%3Aelement-hq%2Felement-desktop+review-requested%3A%40me+sort%3Aupdated-desc+"
|
||||||
RELEASE_BLOCKERS_URL: "https://github.com/pulls?q=is%3Aopen+repo%3Amatrix-org%2Fmatrix-js-sdk+repo%3Amatrix-org%2Fmatrix-react-sdk+repo%3Aelement-hq%2Felement-web+repo%3Aelement-hq%2Felement-desktop+sort%3Aupdated-desc+label%3AX-Release-Blocker+"
|
RELEASE_BLOCKERS_URL: "https://github.com/pulls?q=is%3Aopen+repo%3Amatrix-org%2Fmatrix-js-sdk+repo%3Amatrix-org%2Fmatrix-react-sdk+repo%3Aelement-hq%2Felement-web+repo%3Aelement-hq%2Felement-desktop+sort%3Aupdated-desc+label%3AX-Release-Blocker+"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||||
env:
|
env:
|
||||||
HS_URL: ${{ secrets.BETABOT_HS_URL }}
|
HS_URL: ${{ secrets.BETABOT_HS_URL }}
|
||||||
ROOM_ID: ${{ secrets.ROOM_ID }}
|
ROOM_ID: ${{ secrets.ROOM_ID }}
|
||||||
|
|||||||
11
.github/workflows/playwright-image-updates.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
- name: Update synapse image
|
- name: Update synapse image
|
||||||
run: |
|
run: |
|
||||||
@@ -21,15 +21,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
IMAGE: ghcr.io/element-hq/synapse:develop
|
IMAGE: ghcr.io/element-hq/synapse:develop
|
||||||
|
|
||||||
- name: Update MAS image
|
|
||||||
run: |
|
|
||||||
docker pull "$IMAGE"
|
|
||||||
INSPECT=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE")
|
|
||||||
DIGEST=${INSPECT#*@}
|
|
||||||
sed -i "s/const TAG.*/const TAG = \"main@$DIGEST\";/" playwright/testcontainers/mas.ts
|
|
||||||
env:
|
|
||||||
IMAGE: ghcr.io/element-hq/matrix-authentication-service:main
|
|
||||||
|
|
||||||
- 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@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ jobs:
|
|||||||
name: Check PR base branch
|
name: Check PR base branch
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const baseBranch = context.payload.pull_request.base.ref;
|
const baseBranch = context.payload.pull_request.base.ref;
|
||||||
|
|||||||
6
.github/workflows/release_prepare.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
|||||||
REPOS: matrix-js-sdk element-web element-desktop
|
REPOS: matrix-js-sdk element-web element-desktop
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Element Desktop
|
- name: Checkout Element Desktop
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
if: inputs.element-desktop
|
if: inputs.element-desktop
|
||||||
with:
|
with:
|
||||||
repository: element-hq/element-desktop
|
repository: element-hq/element-desktop
|
||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
fetch-tags: true
|
fetch-tags: true
|
||||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
- name: Checkout Element Web
|
- name: Checkout Element Web
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
if: inputs.element-web
|
if: inputs.element-web
|
||||||
with:
|
with:
|
||||||
repository: element-hq/element-web
|
repository: element-hq/element-web
|
||||||
@@ -61,7 +61,7 @@ jobs:
|
|||||||
fetch-tags: true
|
fetch-tags: true
|
||||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
- name: Checkout Matrix JS SDK
|
- name: Checkout Matrix JS SDK
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
if: inputs.matrix-js-sdk
|
if: inputs.matrix-js-sdk
|
||||||
with:
|
with:
|
||||||
repository: matrix-org/matrix-js-sdk
|
repository: matrix-org/matrix-js-sdk
|
||||||
|
|||||||
34
.github/workflows/shared-component-publish.yaml
vendored
@@ -1,34 +0,0 @@
|
|||||||
name: Publish shared component npm package
|
|
||||||
on:
|
|
||||||
workflow_dispatch: {}
|
|
||||||
|
|
||||||
concurrency: release
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
name: "Publish"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 🧮 Checkout code
|
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
|
||||||
|
|
||||||
- name: 🔧 Set up node environment
|
|
||||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
|
||||||
with:
|
|
||||||
cache: "yarn"
|
|
||||||
node-version-file: ".node-version"
|
|
||||||
registry-url: "https://registry.npmjs.org"
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.ELEMENT_NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: 🛠️ Setup
|
|
||||||
# When running `install` it also calls the `prepare` step which generates
|
|
||||||
# a build
|
|
||||||
run: yarn --cwd packages/shared-components install --pure-lockfile
|
|
||||||
|
|
||||||
- name: 🚀 Publish to npm
|
|
||||||
working-directory: packages/shared-components
|
|
||||||
run: npm publish --access public --provenance
|
|
||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
run: "sudo apt-get install -y tree"
|
run: "sudo apt-get install -y tree"
|
||||||
|
|
||||||
- name: Download Diffs
|
- name: Download Diffs
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run-id: ${{ github.event.workflow_run.id }}
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
|||||||
@@ -21,56 +21,50 @@ jobs:
|
|||||||
issues: read
|
issues: read
|
||||||
pull-requests: read
|
pull-requests: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
repository: element-hq/element-web
|
repository: element-hq/element-web
|
||||||
|
|
||||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
|
|
||||||
- name: Install element web dependencies
|
|
||||||
run: yarn install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Build Element Web resources
|
|
||||||
# Needed to prepare language files
|
|
||||||
run: "yarn build:res"
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
working-directory: packages/shared-components
|
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Get installed Playwright version
|
- name: Get installed Playwright version
|
||||||
working-directory: packages/shared-components
|
|
||||||
id: playwright
|
id: playwright
|
||||||
run: echo "version=$(yarn list --pattern @playwright/test --depth=0 --json --non-interactive --no-progress | jq -r '.data.trees[].name')" >> $GITHUB_OUTPUT
|
run: echo "version=$(yarn list --pattern @playwright/test --depth=0 --json --non-interactive --no-progress | jq -r '.data.trees[].name')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Cache playwright binaries
|
- name: Cache playwright binaries
|
||||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
|
||||||
id: playwright-cache
|
id: playwright-cache
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/ms-playwright
|
path: ~/.cache/ms-playwright
|
||||||
key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }}-onlyshell
|
key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }}-onlyshell
|
||||||
|
|
||||||
- name: Install Playwright browsers
|
- name: Install Playwright browsers
|
||||||
working-directory: packages/shared-components
|
|
||||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||||
run: "yarn playwright install --with-deps --only-shell"
|
run: "yarn playwright install --with-deps --only-shell"
|
||||||
|
|
||||||
|
- name: Build Element Web resources
|
||||||
|
# Needed to prepare language files
|
||||||
|
run: "yarn build:res"
|
||||||
|
|
||||||
- name: Build storybook dependencies
|
- name: Build storybook dependencies
|
||||||
# When the first test is ran, it will fail because the dependencies are not yet built.
|
# When the first test is ran, it will fail because the dependencies are not yet built.
|
||||||
# This step is to ensure that the dependencies are built before running the tests.
|
# This step is to ensure that the dependencies are built before running the tests.
|
||||||
run: "yarn --cwd packages/shared-components test:storybook:ci"
|
run: "yarn test:storybook:ci"
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Run Visual tests
|
- name: Run Visual tests
|
||||||
run: "yarn --cwd packages/shared-components test:storybook:ci"
|
run: "yarn test:storybook:ci"
|
||||||
|
|
||||||
- name: Upload received images & diffs
|
- name: Upload received images & diffs
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
with:
|
with:
|
||||||
name: received-images
|
name: received-images
|
||||||
path: packages/shared-components/playwright/shared-component-received
|
path: playwright/shared-component-received
|
||||||
|
|||||||
46
.github/workflows/static_analysis.yaml
vendored
@@ -12,7 +12,8 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# This must be set for fetchdep.sh to get the right branch
|
# These must be set for fetchdep.sh to get the right branch
|
||||||
|
REPOSITORY: ${{ github.repository }}
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
|
||||||
permissions: {} # No permissions required
|
permissions: {} # No permissions required
|
||||||
@@ -22,9 +23,9 @@ jobs:
|
|||||||
name: "Typescript Syntax Check"
|
name: "Typescript Syntax Check"
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
@@ -35,16 +36,6 @@ jobs:
|
|||||||
- name: Typecheck
|
- name: Typecheck
|
||||||
run: "yarn run lint:types"
|
run: "yarn run lint:types"
|
||||||
|
|
||||||
- name: Build Element Web resources
|
|
||||||
# Needed to prepare language files for shared components
|
|
||||||
run: "yarn build:res"
|
|
||||||
|
|
||||||
- name: Install Shared Component Dependencies
|
|
||||||
run: "yarn --cwd packages/shared-components install"
|
|
||||||
|
|
||||||
- name: Typecheck Shared Components
|
|
||||||
run: "yarn --cwd packages/shared-components run lint:types"
|
|
||||||
|
|
||||||
i18n_lint:
|
i18n_lint:
|
||||||
name: "i18n Check"
|
name: "i18n Check"
|
||||||
uses: matrix-org/matrix-web-i18n/.github/workflows/i18n_check.yml@main
|
uses: matrix-org/matrix-web-i18n/.github/workflows/i18n_check.yml@main
|
||||||
@@ -61,13 +52,12 @@ jobs:
|
|||||||
error|misconfigured
|
error|misconfigured
|
||||||
welcome_to_element
|
welcome_to_element
|
||||||
devtools|settings|elementCallUrl
|
devtools|settings|elementCallUrl
|
||||||
labs|sliding_sync_description
|
|
||||||
|
|
||||||
rethemendex_lint:
|
rethemendex_lint:
|
||||||
name: "Rethemendex Check"
|
name: "Rethemendex Check"
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
- run: ./res/css/rethemendex.sh
|
- run: ./res/css/rethemendex.sh
|
||||||
|
|
||||||
@@ -77,9 +67,9 @@ jobs:
|
|||||||
name: "ESLint"
|
name: "ESLint"
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
@@ -91,23 +81,13 @@ jobs:
|
|||||||
- name: Run Linter
|
- name: Run Linter
|
||||||
run: "yarn run lint:js"
|
run: "yarn run lint:js"
|
||||||
|
|
||||||
- name: Build Element Web resources
|
|
||||||
# Needed to prepare language files for shared components
|
|
||||||
run: "yarn build:res"
|
|
||||||
|
|
||||||
- name: Install Shared Component Deps
|
|
||||||
run: "yarn --cwd packages/shared-components install --frozen-lockfile"
|
|
||||||
|
|
||||||
- name: Run Linter
|
|
||||||
run: "yarn --cwd packages/shared-components run lint:js"
|
|
||||||
|
|
||||||
style_lint:
|
style_lint:
|
||||||
name: "Style Lint"
|
name: "Style Lint"
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
@@ -123,9 +103,9 @@ jobs:
|
|||||||
name: "Workflow Lint"
|
name: "Workflow Lint"
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
@@ -141,9 +121,9 @@ jobs:
|
|||||||
name: "Analyse Dead Code"
|
name: "Analyse Dead Code"
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
|
|||||||
10
.github/workflows/tests.yml
vendored
@@ -39,12 +39,12 @@ jobs:
|
|||||||
runner: [1, 2]
|
runner: [1, 2]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
with:
|
with:
|
||||||
repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }}
|
repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }}
|
||||||
|
|
||||||
- name: Yarn cache
|
- name: Yarn cache
|
||||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
|
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
|
||||||
|
|
||||||
- name: Jest Cache
|
- name: Jest Cache
|
||||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
|
||||||
with:
|
with:
|
||||||
path: /tmp/jest_cache
|
path: /tmp/jest_cache
|
||||||
key: ${{ hashFiles('**/yarn.lock') }}
|
key: ${{ hashFiles('**/yarn.lock') }}
|
||||||
@@ -84,7 +84,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
if: env.ENABLE_COVERAGE == 'true'
|
if: env.ENABLE_COVERAGE == 'true'
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
with:
|
with:
|
||||||
name: coverage-${{ matrix.runner }}
|
name: coverage-${{ matrix.runner }}
|
||||||
path: |
|
path: |
|
||||||
@@ -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@5530c593759f489bba08272e96986ffc571c1ea1
|
uses: guibranco/github-status-action-v2@741ea90ba6c3ca76fe0d43ba11a90cda97d5e685
|
||||||
with:
|
with:
|
||||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||||
state: success
|
state: success
|
||||||
|
|||||||
2
.github/workflows/triage-assigned.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
contains(github.event.issue.assignees.*.login, 'dbkr') ||
|
contains(github.event.issue.assignees.*.login, 'dbkr') ||
|
||||||
contains(github.event.issue.assignees.*.login, 'MidhunSureshR')
|
contains(github.event.issue.assignees.*.login, 'MidhunSureshR')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
- uses: actions/add-to-project@main
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/element-hq/projects/67
|
project-url: https://github.com/orgs/element-hq/projects/67
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
|
|||||||
2
.github/workflows/triage-incoming.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
automate-project-columns:
|
automate-project-columns:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
- uses: actions/add-to-project@main
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/element-hq/projects/120
|
project-url: https://github.com/orgs/element-hq/projects/120
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
|
|||||||
16
.github/workflows/triage-labelled.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
|||||||
contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor') ||
|
contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor') ||
|
||||||
contains(github.event.issue.labels.*.name, 'A-Element-Call')
|
contains(github.event.issue.labels.*.name, 'A-Element-Call')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.addLabels({
|
github.rest.issues.addLabels({
|
||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
contains(github.event.issue.labels.*.name, 'good first issue') ||
|
contains(github.event.issue.labels.*.name, 'good first issue') ||
|
||||||
contains(github.event.issue.labels.*.name, 'Hacktoberfest')
|
contains(github.event.issue.labels.*.name, 'Hacktoberfest')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.addLabels({
|
github.rest.issues.addLabels({
|
||||||
@@ -112,7 +112,7 @@ jobs:
|
|||||||
contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
contains(github.event.issue.labels.*.name, 'O-Frequent') ||
|
||||||
contains(github.event.issue.labels.*.name, 'A11y'))
|
contains(github.event.issue.labels.*.name, 'A11y'))
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
- uses: actions/add-to-project@main
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/element-hq/projects/18
|
project-url: https://github.com/orgs/element-hq/projects/18
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
@@ -123,7 +123,7 @@ jobs:
|
|||||||
if: >
|
if: >
|
||||||
contains(github.event.issue.labels.*.name, 'X-Needs-Product')
|
contains(github.event.issue.labels.*.name, 'X-Needs-Product')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
- uses: actions/add-to-project@main
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/element-hq/projects/28
|
project-url: https://github.com/orgs/element-hq/projects/28
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
@@ -134,7 +134,7 @@ jobs:
|
|||||||
if: >
|
if: >
|
||||||
contains(github.event.issue.labels.*.name, 'A-New-Search-Experience')
|
contains(github.event.issue.labels.*.name, 'A-New-Search-Experience')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
- uses: actions/add-to-project@main
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/element-hq/projects/48
|
project-url: https://github.com/orgs/element-hq/projects/48
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
@@ -145,7 +145,7 @@ jobs:
|
|||||||
if: >
|
if: >
|
||||||
contains(github.event.issue.labels.*.name, 'Team: VoIP')
|
contains(github.event.issue.labels.*.name, 'Team: VoIP')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
- uses: actions/add-to-project@main
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/element-hq/projects/41
|
project-url: https://github.com/orgs/element-hq/projects/41
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
@@ -156,7 +156,7 @@ jobs:
|
|||||||
if: >
|
if: >
|
||||||
contains(github.event.issue.labels.*.name, 'Team: Crypto')
|
contains(github.event.issue.labels.*.name, 'Team: Crypto')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
- uses: actions/add-to-project@main
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/element-hq/projects/76
|
project-url: https://github.com/orgs/element-hq/projects/76
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
@@ -172,7 +172,7 @@ jobs:
|
|||||||
contains(github.event.issue.labels.*.name, 'A-Testing') ||
|
contains(github.event.issue.labels.*.name, 'A-Testing') ||
|
||||||
contains(github.event.issue.labels.*.name, 'Z-Flaky-Test')
|
contains(github.event.issue.labels.*.name, 'Z-Flaky-Test')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/add-to-project@244f685bbc3b7adfa8466e08b698b5577571133e # v1.0.2
|
- uses: actions/add-to-project@main
|
||||||
with:
|
with:
|
||||||
project-url: https://github.com/orgs/element-hq/projects/101
|
project-url: https://github.com/orgs/element-hq/projects/101
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ jobs:
|
|||||||
name: Move PRs asking for design review to the design board
|
name: Move PRs asking for design review to the design board
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2
|
- uses: octokit/graphql-action@v2.x
|
||||||
id: find_team_members
|
id: find_team_members
|
||||||
with:
|
with:
|
||||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
env:
|
env:
|
||||||
TEAM: "design"
|
TEAM: "design"
|
||||||
- uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2
|
- uses: octokit/graphql-action@v2.x
|
||||||
id: add_to_project
|
id: add_to_project
|
||||||
if: steps.any_matching_reviewers.outputs.match == 'true'
|
if: steps.any_matching_reviewers.outputs.match == 'true'
|
||||||
with:
|
with:
|
||||||
@@ -76,7 +76,7 @@ jobs:
|
|||||||
name: Move PRs asking for design review to the design board
|
name: Move PRs asking for design review to the design board
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2
|
- uses: octokit/graphql-action@v2.x
|
||||||
id: find_team_members
|
id: find_team_members
|
||||||
with:
|
with:
|
||||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||||
@@ -119,7 +119,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
env:
|
env:
|
||||||
TEAM: "product"
|
TEAM: "product"
|
||||||
- uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2
|
- uses: octokit/graphql-action@v2.x
|
||||||
id: add_to_project
|
id: add_to_project
|
||||||
if: steps.any_matching_reviewers.outputs.match == 'true'
|
if: steps.any_matching_reviewers.outputs.match == 'true'
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/triage-stale.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10
|
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
|
||||||
with:
|
with:
|
||||||
operations-per-run: 100
|
operations-per-run: 100
|
||||||
|
|
||||||
|
|||||||
51
.github/workflows/triage-unlabelled.yml
vendored
@@ -5,25 +5,44 @@ on:
|
|||||||
types: [unlabeled]
|
types: [unlabeled]
|
||||||
permissions: {}
|
permissions: {}
|
||||||
jobs:
|
jobs:
|
||||||
move_no_longer_needs_info_issues:
|
Move_Unabeled_Issue_On_Project_Board:
|
||||||
name: Move no longer X-Needs-Info issues to Triaged
|
name: Move no longer X-Needs-Info issues to Triaged
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
|
permissions:
|
||||||
|
repository-projects: read
|
||||||
if: >
|
if: >
|
||||||
!contains(github.event.issue.labels.*.name, 'X-Needs-Info')
|
${{
|
||||||
steps:
|
!contains(github.event.issue.labels.*.name, 'X-Needs-Info') }}
|
||||||
- id: set_fields
|
|
||||||
uses: nipe0324/update-project-v2-item-field@c4af58452d1c5a788c1ea4f20e073fa722ec4a6b #v2.0.2
|
|
||||||
with:
|
|
||||||
project-url: ${{ env.PROJECT_URL }}
|
|
||||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
|
||||||
skip-update-script: |
|
|
||||||
const isIssue = item.type === 'ISSUE'
|
|
||||||
const status = item.fieldValues['Status']
|
|
||||||
return !isIssue || status !== 'Needs info'
|
|
||||||
field-name: Status
|
|
||||||
field-value: "Triaged"
|
|
||||||
env:
|
env:
|
||||||
PROJECT_URL: https://github.com/orgs/element-hq/projects/120
|
BOARD_NAME: "Issue triage"
|
||||||
|
OWNER: ${{ github.repository_owner }}
|
||||||
|
REPO: ${{ github.event.repository.name }}
|
||||||
|
ISSUE: ${{ github.event.issue.number }}
|
||||||
|
steps:
|
||||||
|
- name: Check if issue is already in "${{ env.BOARD_NAME }}"
|
||||||
|
run: |
|
||||||
|
json=$(curl -s -H 'Content-Type: application/json' -H "Authorization: bearer ${{ secrets.GITHUB_TOKEN }}" -X POST -d '{"query": "query($issue: Int!, $owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { issue(number: $issue) { projectCards { nodes { project { name } isArchived } } } } } ", "variables" : "{ \"issue\": '${ISSUE}', \"owner\": \"'${OWNER}'\", \"repo\": \"'${REPO}'\" }" }' https://api.github.com/graphql)
|
||||||
|
if echo $json | jq '.data.repository.issue.projectCards.nodes | length'; then
|
||||||
|
if [[ $(echo $json | jq '.data.repository.issue.projectCards.nodes[0].project.name') =~ "${BOARD_NAME}" ]]; then
|
||||||
|
if [[ $(echo $json | jq '.data.repository.issue.projectCards.nodes[0].isArchived') == 'true' ]]; then
|
||||||
|
echo "Issue is already in Project '$BOARD_NAME', but is archived - skipping workflow";
|
||||||
|
echo "SKIP_ACTION=true" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "Issue is already in Project '$BOARD_NAME', proceeding";
|
||||||
|
echo "ALREADY_IN_BOARD=true" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Issue is not in project '$BOARD_NAME', cancelling this workflow"
|
||||||
|
echo "ALREADY_IN_BOARD=false" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
- name: Move issue
|
||||||
|
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36
|
||||||
|
if: ${{ env.ALREADY_IN_BOARD == 'true' && env.SKIP_ACTION != 'true' }}
|
||||||
|
with:
|
||||||
|
project: Issue triage
|
||||||
|
column: Triaged
|
||||||
|
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
|
|
||||||
remove_Z-Labs_label:
|
remove_Z-Labs_label:
|
||||||
name: Remove Z-Labs label when features behind labs flags are removed
|
name: Remove Z-Labs label when features behind labs flags are removed
|
||||||
@@ -43,7 +62,7 @@ jobs:
|
|||||||
contains(github.event.issue.labels.*.name, 'A-Element-Call')) &&
|
contains(github.event.issue.labels.*.name, 'A-Element-Call')) &&
|
||||||
contains(github.event.issue.labels.*.name, 'Z-Labs')
|
contains(github.event.issue.labels.*.name, 'Z-Labs')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.removeLabel({
|
github.rest.issues.removeLabel({
|
||||||
|
|||||||
4
.github/workflows/update-jitsi.yml
vendored
@@ -9,9 +9,9 @@ jobs:
|
|||||||
update:
|
update:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||||
|
|
||||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
|
|||||||
2
.github/workflows/update-topics.yaml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
environment: Matrix
|
environment: Matrix
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||||
env:
|
env:
|
||||||
HS_URL: ${{ secrets.BETABOT_HS_URL }}
|
HS_URL: ${{ secrets.BETABOT_HS_URL }}
|
||||||
LOBBY_ROOM_ID: ${{ secrets.ROOM_ID }}
|
LOBBY_ROOM_ID: ${{ secrets.ROOM_ID }}
|
||||||
|
|||||||
4
.gitignore
vendored
@@ -4,6 +4,7 @@
|
|||||||
/key.pem
|
/key.pem
|
||||||
/lib
|
/lib
|
||||||
/node_modules
|
/node_modules
|
||||||
|
/packages/
|
||||||
/webapp
|
/webapp
|
||||||
/.npmrc
|
/.npmrc
|
||||||
/*.log
|
/*.log
|
||||||
@@ -33,6 +34,3 @@ electron/pub
|
|||||||
|
|
||||||
*storybook.log
|
*storybook.log
|
||||||
storybook-static
|
storybook-static
|
||||||
|
|
||||||
/packages/shared-components/node_modules
|
|
||||||
/packages/shared-components/dist
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
24
|
22
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { GlobeIcon } from "@storybook/icons";
|
|||||||
|
|
||||||
// We can't import `shared/i18n.tsx` directly here.
|
// We can't import `shared/i18n.tsx` directly here.
|
||||||
// The storybook addon doesn't seem to benefit the vite config of storybook and we can't resolve the alias in i18n.tsx.
|
// The storybook addon doesn't seem to benefit the vite config of storybook and we can't resolve the alias in i18n.tsx.
|
||||||
import json from "../../../webapp/i18n/languages.json";
|
import json from "../webapp/i18n/languages.json";
|
||||||
const languages = Object.keys(json).filter((lang) => lang !== "default");
|
const languages = Object.keys(json).filter((lang) => lang !== "default");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,9 +11,9 @@ import { nodePolyfills } from "vite-plugin-node-polyfills";
|
|||||||
import { mergeConfig } from "vite";
|
import { mergeConfig } from "vite";
|
||||||
|
|
||||||
const config: StorybookConfig = {
|
const config: StorybookConfig = {
|
||||||
stories: ["../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
stories: ["../src/shared-components/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||||
staticDirs: ["../../../webapp"],
|
staticDirs: ["../webapp"],
|
||||||
addons: ["@storybook/addon-docs", "@storybook/addon-designs", "@storybook/addon-a11y"],
|
addons: ["@storybook/addon-docs", "@storybook/addon-designs"],
|
||||||
framework: "@storybook/react-vite",
|
framework: "@storybook/react-vite",
|
||||||
core: {
|
core: {
|
||||||
disableTelemetry: true,
|
disableTelemetry: true,
|
||||||
@@ -26,7 +26,7 @@ const config: StorybookConfig = {
|
|||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
// Alias used by i18n.tsx
|
// Alias used by i18n.tsx
|
||||||
$webapp: path.resolve("../../webapp"),
|
$webapp: path.resolve("webapp"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Needed for counterpart to work
|
// Needed for counterpart to work
|
||||||
@@ -36,11 +36,5 @@ const config: StorybookConfig = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
refs: {
|
|
||||||
"compound-web": {
|
|
||||||
title: "Compound Web",
|
|
||||||
url: "https://element-hq.github.io/compound-web/",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
export default config;
|
export default config;
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
import type { ArgTypes, Preview, Decorator, ReactRenderer, StrictArgs } from "@storybook/react-vite";
|
import type { ArgTypes, Preview, Decorator } from "@storybook/react-vite";
|
||||||
|
import { addons } from "storybook/preview-api";
|
||||||
|
|
||||||
import "../../../res/css/shared.pcss";
|
import "../res/css/shared.pcss";
|
||||||
import "./preview.css";
|
import "./preview.css";
|
||||||
import React, { useLayoutEffect } from "react";
|
import React, { useLayoutEffect } from "react";
|
||||||
import { setLanguage } from "../src/utils/i18n";
|
import { FORCE_RE_RENDER } from "storybook/internal/core-events";
|
||||||
|
import { setLanguage } from "../src/shared-components/utils/i18n";
|
||||||
import { TooltipProvider } from "@vector-im/compound-web";
|
import { TooltipProvider } from "@vector-im/compound-web";
|
||||||
import { StoryContext } from "storybook/internal/csf";
|
|
||||||
|
|
||||||
export const globalTypes = {
|
export const globalTypes = {
|
||||||
theme: {
|
theme: {
|
||||||
@@ -58,9 +59,29 @@ const withThemeProvider: Decorator = (Story, context) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function languageLoader(context: StoryContext<ReactRenderer, StrictArgs>): Promise<void> {
|
const LanguageSwitcher: React.FC<{
|
||||||
await setLanguage(context.globals.language);
|
language: string;
|
||||||
}
|
}> = ({ language }) => {
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const changeLanguage = async (language: string) => {
|
||||||
|
await setLanguage(language);
|
||||||
|
// Force the component to re-render to apply the new language
|
||||||
|
addons.getChannel().emit(FORCE_RE_RENDER);
|
||||||
|
};
|
||||||
|
changeLanguage(language);
|
||||||
|
}, [language]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const withLanguageProvider: Decorator = (Story, context) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<LanguageSwitcher language={context.globals.language} />
|
||||||
|
<Story />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const withTooltipProvider: Decorator = (Story) => {
|
const withTooltipProvider: Decorator = (Story) => {
|
||||||
return (
|
return (
|
||||||
@@ -72,22 +93,14 @@ const withTooltipProvider: Decorator = (Story) => {
|
|||||||
|
|
||||||
const preview: Preview = {
|
const preview: Preview = {
|
||||||
tags: ["autodocs"],
|
tags: ["autodocs"],
|
||||||
decorators: [withThemeProvider, withTooltipProvider],
|
decorators: [withThemeProvider, withLanguageProvider, withTooltipProvider],
|
||||||
parameters: {
|
parameters: {
|
||||||
options: {
|
options: {
|
||||||
storySort: {
|
storySort: {
|
||||||
method: "alphabetical",
|
method: "alphabetical",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
a11y: {
|
|
||||||
/*
|
|
||||||
* Configure test behavior
|
|
||||||
* See: https://storybook.js.org/docs/next/writing-tests/accessibility-testing#test-behavior
|
|
||||||
*/
|
|
||||||
test: "error",
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
loaders: [languageLoader],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default preview;
|
export default preview;
|
||||||
@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
import { waitForPageReady } from "@storybook/test-runner";
|
import { waitForPageReady } from "@storybook/test-runner";
|
||||||
import { toMatchImageSnapshot } from "jest-image-snapshot";
|
import { toMatchImageSnapshot } from "jest-image-snapshot";
|
||||||
|
|
||||||
const customSnapshotsDir = `${process.cwd()}/playwright/snapshots/`;
|
const customSnapshotsDir = `${process.cwd()}/playwright/shared-component-snapshots/`;
|
||||||
const customReceivedDir = `${process.cwd()}/playwright/received/`;
|
const customReceivedDir = `${process.cwd()}/playwright/shared-component-received/`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('@storybook/test-runner').TestRunnerConfig}
|
* @type {import('@storybook/test-runner').TestRunnerConfig}
|
||||||
175
CHANGELOG.md
@@ -1,178 +1,3 @@
|
|||||||
Changes in [1.12.2](https://github.com/element-hq/element-web/releases/tag/v1.12.2) (2025-10-21)
|
|
||||||
================================================================================================
|
|
||||||
## ✨ Features
|
|
||||||
|
|
||||||
* Room List: Extend the viewport to avoid so many black spots when scrolling the room list ([#30867](https://github.com/element-hq/element-web/pull/30867)). Contributed by @langleyd.
|
|
||||||
* Hide calling buttons in room header before a room is created ([#30816](https://github.com/element-hq/element-web/pull/30816)). Contributed by @Half-Shot.
|
|
||||||
* Improve invite dialog ui - Part 2 ([#30836](https://github.com/element-hq/element-web/pull/30836)). Contributed by @florianduros.
|
|
||||||
|
|
||||||
## 🐛 Bug Fixes
|
|
||||||
|
|
||||||
* Fix platform settings race condition and make auto-launch tri-state ([#30977](https://github.com/element-hq/element-web/pull/30977)). Contributed by @t3chguy.
|
|
||||||
* Fix: member count in header and member list ([#30982](https://github.com/element-hq/element-web/pull/30982)). Contributed by @florianduros.
|
|
||||||
* Fix duration of voice message in timeline ([#30973](https://github.com/element-hq/element-web/pull/30973)). Contributed by @florianduros.
|
|
||||||
* Fix voice notes rendering at 00:00 when playback had not begun. ([#30961](https://github.com/element-hq/element-web/pull/30961)). Contributed by @Half-Shot.
|
|
||||||
* Improve handling of animated images, add support for AVIF animations ([#30932](https://github.com/element-hq/element-web/pull/30932)). Contributed by @t3chguy.
|
|
||||||
* Update key storage toggle when key storage status changes ([#30934](https://github.com/element-hq/element-web/pull/30934)). Contributed by @uhoreg.
|
|
||||||
* Fix jitsi widget popout ([#30908](https://github.com/element-hq/element-web/pull/30908)). Contributed by @dbkr.
|
|
||||||
* Improve keyboard navigation on invite dialog ([#30930](https://github.com/element-hq/element-web/pull/30930)). Contributed by @florianduros.
|
|
||||||
* Prefer UIA flows with supported UIA stages ([#30926](https://github.com/element-hq/element-web/pull/30926)). Contributed by @richvdh.
|
|
||||||
* Enhance accessibility of dropdown ([#30928](https://github.com/element-hq/element-web/pull/30928)). Contributed by @florianduros.
|
|
||||||
* Improve accessibility of the `\<AvatarSetting> component ([#30907](https://github.com/element-hq/element-web/pull/30907)). Contributed by @MidhunSureshR.
|
|
||||||
|
|
||||||
|
|
||||||
Changes in [1.12.1](https://github.com/element-hq/element-web/releases/tag/v1.12.1) (2025-10-07)
|
|
||||||
================================================================================================
|
|
||||||
## ✨ Features
|
|
||||||
|
|
||||||
* New Room List: Change the order of filters to match those on mobile ([#30905](https://github.com/element-hq/element-web/pull/30905)). Contributed by @langleyd.
|
|
||||||
* New Room List: Don't clear filters on space change ([#30903](https://github.com/element-hq/element-web/pull/30903)). Contributed by @langleyd.
|
|
||||||
* Add release announcement for the sounds ([#30900](https://github.com/element-hq/element-web/pull/30900)). Contributed by @langleyd.
|
|
||||||
* Rich Text Editor: Add emoji suggestion support ([#30873](https://github.com/element-hq/element-web/pull/30873)). Contributed by @langleyd.
|
|
||||||
* feat: Disable session lock when running in element-desktop ([#30643](https://github.com/element-hq/element-web/pull/30643)). Contributed by @kaylendog.
|
|
||||||
* Improve invite dialog ui - Part 1 ([#30764](https://github.com/element-hq/element-web/pull/30764)). Contributed by @florianduros.
|
|
||||||
* Update Message Sound for Element ([#30804](https://github.com/element-hq/element-web/pull/30804)). Contributed by @beatdemon.
|
|
||||||
* Add new and improved ringtone ([#30761](https://github.com/element-hq/element-web/pull/30761)). Contributed by @Half-Shot.
|
|
||||||
* Disable RTE formatting buttons when the content contains a slash command ([#30802](https://github.com/element-hq/element-web/pull/30802)). Contributed by @langleyd.
|
|
||||||
|
|
||||||
## 🐛 Bug Fixes
|
|
||||||
|
|
||||||
* New Room List: Improve robustness of keyboard navigation ([#30888](https://github.com/element-hq/element-web/pull/30888)). Contributed by @langleyd.
|
|
||||||
* Fix a11y issue on list in invite dialog ([#30878](https://github.com/element-hq/element-web/pull/30878)). Contributed by @florianduros.
|
|
||||||
* Switch Export and Import Icons to match intuition ([#30805](https://github.com/element-hq/element-web/pull/30805)). Contributed by @micartey.
|
|
||||||
* Hide breadcrumb option when new room list is enabled ([#30869](https://github.com/element-hq/element-web/pull/30869)). Contributed by @florianduros.
|
|
||||||
* Avoid creating multiple call objects for the same widget ([#30839](https://github.com/element-hq/element-web/pull/30839)). Contributed by @robintown.
|
|
||||||
* Add a test for #29882, which is fixed by matrix-org/matrix-js-sdk#5016 ([#30835](https://github.com/element-hq/element-web/pull/30835)). Contributed by @andybalaam.
|
|
||||||
* fix: use `help_encryption_url` of config instead of hardcoded `https://element.io/help#encryption5` ([#30746](https://github.com/element-hq/element-web/pull/30746)). Contributed by @florianduros.
|
|
||||||
* Fix html export when feature\_jump\_to\_date is enabled ([#30828](https://github.com/element-hq/element-web/pull/30828)). Contributed by @langleyd.
|
|
||||||
* Fix #30439: "Forgot recovery key" should go to "reset" ([#30771](https://github.com/element-hq/element-web/pull/30771)). Contributed by @andybalaam.
|
|
||||||
|
|
||||||
|
|
||||||
Changes in [1.12.0](https://github.com/element-hq/element-web/releases/tag/v1.12.0) (2025-09-23)
|
|
||||||
================================================================================================
|
|
||||||
## 🦖 Deprecations
|
|
||||||
|
|
||||||
* Remove remaining support for outdated .well-known settings ([#30702](https://github.com/element-hq/element-web/pull/30702)). Contributed by @richvdh.
|
|
||||||
|
|
||||||
## ✨ Features
|
|
||||||
|
|
||||||
* Add decline button to call notification toast (use new notification event) ([#30729](https://github.com/element-hq/element-web/pull/30729)). Contributed by @toger5.
|
|
||||||
* Use the new room list by default ([#30640](https://github.com/element-hq/element-web/pull/30640)). Contributed by @langleyd.
|
|
||||||
* "Verify this device" redesign ([#30596](https://github.com/element-hq/element-web/pull/30596)). Contributed by @uhoreg.
|
|
||||||
* Set Element Call "intents" when starting and answering DM calls. ([#30730](https://github.com/element-hq/element-web/pull/30730)). Contributed by @Half-Shot.
|
|
||||||
* Add axe compliance for new room list ([#30700](https://github.com/element-hq/element-web/pull/30700)). Contributed by @langleyd.
|
|
||||||
* Stop ringing and remove toast if another device answers a RTC call. ([#30728](https://github.com/element-hq/element-web/pull/30728)). Contributed by @Half-Shot.
|
|
||||||
* Automatically adjust history visibility when making a room private ([#30713](https://github.com/element-hq/element-web/pull/30713)). Contributed by @Half-Shot.
|
|
||||||
* Release announcement for new room list ([#30675](https://github.com/element-hq/element-web/pull/30675)). Contributed by @dbkr.
|
|
||||||
|
|
||||||
## 🐛 Bug Fixes
|
|
||||||
|
|
||||||
* [Backport staging] Room list: make the filter resize correctly ([#30795](https://github.com/element-hq/element-web/pull/30795)). Contributed by @RiotRobot.
|
|
||||||
* [Backport staging] Avoid flicker of the room list filter on resize ([#30794](https://github.com/element-hq/element-web/pull/30794)). Contributed by @RiotRobot.
|
|
||||||
* Don't show release announcements while toasts are displayed ([#30770](https://github.com/element-hq/element-web/pull/30770)). Contributed by @dbkr.
|
|
||||||
* Fix enabling key backup not working if there is an untrusted key backup ([#30707](https://github.com/element-hq/element-web/pull/30707)). Contributed by @Half-Shot.
|
|
||||||
* Force `preload` to be false when setting an intent on an Element Call. ([#30759](https://github.com/element-hq/element-web/pull/30759)). Contributed by @Half-Shot.
|
|
||||||
* Fix handling of 413 server response when uploading media ([#30737](https://github.com/element-hq/element-web/pull/30737)). Contributed by @hughns.
|
|
||||||
* Make landmark navigation work with new room list ([#30747](https://github.com/element-hq/element-web/pull/30747)). Contributed by @dbkr.
|
|
||||||
* Prevent voice message from displaying spurious errors ([#30736](https://github.com/element-hq/element-web/pull/30736)). Contributed by @florianduros.
|
|
||||||
* Align default avatar and fix colors in composer pills ([#30739](https://github.com/element-hq/element-web/pull/30739)). Contributed by @florianduros.
|
|
||||||
* Use configured URL for link to desktop app in message search settings ([#30742](https://github.com/element-hq/element-web/pull/30742)). Contributed by @t3chguy.
|
|
||||||
* Fix history visibility when creating space rooms ([#30745](https://github.com/element-hq/element-web/pull/30745)). Contributed by @dbkr.
|
|
||||||
* Check HTML-encoded quotes when handling translations for embedded pages (such as welcome.html) ([#30743](https://github.com/element-hq/element-web/pull/30743)). Contributed by @Half-Shot.
|
|
||||||
* Fix local room encryption status always not enabled ([#30461](https://github.com/element-hq/element-web/pull/30461)). Contributed by @BillCarsonFr.
|
|
||||||
* fix: make url in topic in room intro clickable ([#30686](https://github.com/element-hq/element-web/pull/30686)). Contributed by @florianduros.
|
|
||||||
* Block change recovery key button while a change is ongoing. ([#30664](https://github.com/element-hq/element-web/pull/30664)). Contributed by @Half-Shot.
|
|
||||||
* Hide advanced settings during room creation when `UIFeature.advancedSettings=false` ([#30684](https://github.com/element-hq/element-web/pull/30684)). Contributed by @florianduros.
|
|
||||||
* A11y: improve accessibility of pinned messages ([#30558](https://github.com/element-hq/element-web/pull/30558)). Contributed by @florianduros.
|
|
||||||
|
|
||||||
|
|
||||||
Changes in [1.11.112](https://github.com/element-hq/element-web/releases/tag/v1.11.112) (2025-09-16)
|
|
||||||
====================================================================================================
|
|
||||||
Fix [CVE-2025-59161](https://www.cve.org/CVERecord?id=CVE-2025-59161) / [GHSA-m6c8-98f4-75rr](https://github.com/element-hq/element-web/security/advisories/GHSA-m6c8-98f4-75rr)
|
|
||||||
|
|
||||||
|
|
||||||
Changes in [1.11.111](https://github.com/element-hq/element-web/releases/tag/v1.11.111) (2025-09-10)
|
|
||||||
====================================================================================================
|
|
||||||
## ✨ Features
|
|
||||||
|
|
||||||
* Do not hide media from your own user by default ([#29797](https://github.com/element-hq/element-web/pull/29797)). Contributed by @Half-Shot.
|
|
||||||
* Remember whether sidebar is shown for calls when switching rooms ([#30262](https://github.com/element-hq/element-web/pull/30262)). Contributed by @bojidar-bg.
|
|
||||||
* Open the proper integration settings on integrations disabled error ([#30538](https://github.com/element-hq/element-web/pull/30538)). Contributed by @Half-Shot.
|
|
||||||
* Show a "progress" dialog while invites are being sent ([#30561](https://github.com/element-hq/element-web/pull/30561)). Contributed by @richvdh.
|
|
||||||
* Move the room list to the new ListView(backed by react-virtuoso) ([#30515](https://github.com/element-hq/element-web/pull/30515)). Contributed by @langleyd.
|
|
||||||
|
|
||||||
## 🐛 Bug Fixes
|
|
||||||
|
|
||||||
* [Backport staging] Ensure container starts if it is mounted with an empty /modules directory. ([#30705](https://github.com/element-hq/element-web/pull/30705)). Contributed by @RiotRobot.
|
|
||||||
* Fix room joining over federation not specifying vias or using aliases ([#30641](https://github.com/element-hq/element-web/pull/30641)). Contributed by @t3chguy.
|
|
||||||
* Fix stable-suffixed MSC4133 support ([#30649](https://github.com/element-hq/element-web/pull/30649)). Contributed by @dbkr.
|
|
||||||
* Fix i18n of message when a setting is disabled ([#30646](https://github.com/element-hq/element-web/pull/30646)). Contributed by @dbkr.
|
|
||||||
* ListView should not handle the arrow keys if there is a modifier applied ([#30633](https://github.com/element-hq/element-web/pull/30633)). Contributed by @langleyd.
|
|
||||||
* Make BaseDialog's div keyboard focusable and fix test. ([#30631](https://github.com/element-hq/element-web/pull/30631)). Contributed by @langleyd.
|
|
||||||
* Fix: Allow triple-click text selection to flow around pills ([#30349](https://github.com/element-hq/element-web/pull/30349)). Contributed by @AlirezaMrtz.
|
|
||||||
* Watch for a 'join' action to know when the call is connected ([#29492](https://github.com/element-hq/element-web/pull/29492)). Contributed by @robintown.
|
|
||||||
* Fix: add missing tooltip and aria-label to lock icon next to composer ([#30623](https://github.com/element-hq/element-web/pull/30623)). Contributed by @florianduros.
|
|
||||||
* Don't render context menu when scrolling ([#30613](https://github.com/element-hq/element-web/pull/30613)). Contributed by @langleyd.
|
|
||||||
|
|
||||||
|
|
||||||
Changes in [1.11.110](https://github.com/element-hq/element-web/releases/tag/v1.11.110) (2025-08-27)
|
|
||||||
====================================================================================================
|
|
||||||
## ✨ Features
|
|
||||||
|
|
||||||
* Hide recovery key when re-entering it while creating or changing it ([#30499](https://github.com/element-hq/element-web/pull/30499)). Contributed by @andybalaam.
|
|
||||||
* Add `?no_universal_links=true` to OIDC url so EX doesn't try to handle it ([#29439](https://github.com/element-hq/element-web/pull/29439)). Contributed by @t3chguy.
|
|
||||||
* Show a blue lock for unencrypted rooms and hide the grey shield for encrypted rooms ([#30440](https://github.com/element-hq/element-web/pull/30440)). Contributed by @langleyd.
|
|
||||||
* Add support for Module API 1.4 ([#30185](https://github.com/element-hq/element-web/pull/30185)). Contributed by @t3chguy.
|
|
||||||
* MVVM - Introduce some helpers for snapshot management ([#30398](https://github.com/element-hq/element-web/pull/30398)). Contributed by @MidhunSureshR.
|
|
||||||
|
|
||||||
## 🐛 Bug Fixes
|
|
||||||
|
|
||||||
* A11y: move focus to right panel when opened ([#30553](https://github.com/element-hq/element-web/pull/30553)). Contributed by @florianduros.
|
|
||||||
* Fix e2e warning icon should be white ([#30539](https://github.com/element-hq/element-web/pull/30539)). Contributed by @florianduros.
|
|
||||||
* Remove NoOneHere disabled reason. ([#30524](https://github.com/element-hq/element-web/pull/30524)). Contributed by @toger5.
|
|
||||||
* Fix downloading files with authenticated media API ([#30520](https://github.com/element-hq/element-web/pull/30520)). Contributed by @t3chguy.
|
|
||||||
* Fix call permissions check confusion around element call ([#30521](https://github.com/element-hq/element-web/pull/30521)). Contributed by @t3chguy.
|
|
||||||
* Fix line wrap around emoji verification ([#30523](https://github.com/element-hq/element-web/pull/30523)). Contributed by @t3chguy.
|
|
||||||
* Don't highlight redacted events ([#30519](https://github.com/element-hq/element-web/pull/30519)). Contributed by @t3chguy.
|
|
||||||
* Fix matrix.to links not being handled in the app ([#30522](https://github.com/element-hq/element-web/pull/30522)). Contributed by @t3chguy.
|
|
||||||
* Fix issue of new room list taking up the full width ([#30459](https://github.com/element-hq/element-web/pull/30459)). Contributed by @langleyd.
|
|
||||||
* Fix widget persistence in React development mode ([#30509](https://github.com/element-hq/element-web/pull/30509)). Contributed by @robintown.
|
|
||||||
* Fix widget initialization in React development mode ([#30463](https://github.com/element-hq/element-web/pull/30463)). Contributed by @robintown.
|
|
||||||
|
|
||||||
|
|
||||||
Changes in [1.11.109](https://github.com/element-hq/element-web/releases/tag/v1.11.109) (2025-08-11)
|
|
||||||
====================================================================================================
|
|
||||||
This release supports the upcoming v12 ("hydra") Matrix room version and is necessary to view and participate in these rooms.
|
|
||||||
|
|
||||||
## ✨ Features
|
|
||||||
|
|
||||||
* [Backport staging] Allow /upgraderoom command without developer mode enabled ([#30529](https://github.com/element-hq/element-web/pull/30529)). Contributed by @RiotRobot.
|
|
||||||
* [Backport staging] Support for creator/owner power level ([#30526](https://github.com/element-hq/element-web/pull/30526)). Contributed by @RiotRobot.
|
|
||||||
* New room list: change icon and label of menu item for to start a DM ([#30470](https://github.com/element-hq/element-web/pull/30470)). Contributed by @florianduros.
|
|
||||||
* Implement the member list with virtuoso ([#29869](https://github.com/element-hq/element-web/pull/29869)). Contributed by @langleyd.
|
|
||||||
* Add labs option for history sharing on invite ([#30313](https://github.com/element-hq/element-web/pull/30313)). Contributed by @richvdh.
|
|
||||||
* Bump wysiwyg to 2.39.0 adding support for pasting rich text content in the Rich Text Edtior ([#30421](https://github.com/element-hq/element-web/pull/30421)). Contributed by @langleyd.
|
|
||||||
* Support `EventShieldReason.MISMATCHED_SENDER` ([#30403](https://github.com/element-hq/element-web/pull/30403)). Contributed by @richvdh.
|
|
||||||
* Change unencrypted and public pills to blue ([#30399](https://github.com/element-hq/element-web/pull/30399)). Contributed by @florianduros.
|
|
||||||
* Change color of public room icon ([#30390](https://github.com/element-hq/element-web/pull/30390)). Contributed by @florianduros.
|
|
||||||
* Script for updating storybook screenshots ([#30340](https://github.com/element-hq/element-web/pull/30340)). Contributed by @dbkr.
|
|
||||||
* Add toggle to hide empty state in devtools ([#30352](https://github.com/element-hq/element-web/pull/30352)). Contributed by @toger5.
|
|
||||||
|
|
||||||
## 🐛 Bug Fixes
|
|
||||||
|
|
||||||
* [Backport staging] Use userId to filter users in non-federated rooms when showing the InviteDialog ([#30537](https://github.com/element-hq/element-web/pull/30537)). Contributed by @RiotRobot.
|
|
||||||
* [Backport staging] Catch error when encountering invalid m.room.pinned\_events event ([#30536](https://github.com/element-hq/element-web/pull/30536)). Contributed by @RiotRobot.
|
|
||||||
* Update for compatibility with v12 rooms ([#30452](https://github.com/element-hq/element-web/pull/30452)). Contributed by @dbkr.
|
|
||||||
* New room list: fix tooltip on presence ([#30474](https://github.com/element-hq/element-web/pull/30474)). Contributed by @florianduros.
|
|
||||||
* New room list: add tooltip for presence and room status ([#30472](https://github.com/element-hq/element-web/pull/30472)). Contributed by @florianduros.
|
|
||||||
* Fix: Clicking on an item in the member list causes it to scroll to the top rather than show the profile view ([#30455](https://github.com/element-hq/element-web/pull/30455)). Contributed by @langleyd.
|
|
||||||
* Put the 'decrypting' tooltip back ([#30446](https://github.com/element-hq/element-web/pull/30446)). Contributed by @dbkr.
|
|
||||||
* Use server name explicitly for via. ([#30362](https://github.com/element-hq/element-web/pull/30362)). Contributed by @Half-Shot.
|
|
||||||
* fix: replace hardcoded string in poll history dialog ([#30402](https://github.com/element-hq/element-web/pull/30402)). Contributed by @florianduros.
|
|
||||||
* fix: replace hardcoded string on qr code back button ([#30401](https://github.com/element-hq/element-web/pull/30401)). Contributed by @florianduros.
|
|
||||||
* Fix color of icon button with outline ([#30361](https://github.com/element-hq/element-web/pull/30361)). Contributed by @florianduros.
|
|
||||||
|
|
||||||
|
|
||||||
Changes in [1.11.108](https://github.com/element-hq/element-web/releases/tag/v1.11.108) (2025-07-30)
|
Changes in [1.11.108](https://github.com/element-hq/element-web/releases/tag/v1.11.108) (2025-07-30)
|
||||||
====================================================================================================
|
====================================================================================================
|
||||||
## 🐛 Bug Fixes
|
## 🐛 Bug Fixes
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
|
|
||||||
Everyone is welcome to contribute code to Element Web, provided that they are willing to license their contributions to Element under a [Contributor License Agreement](https://cla-assistant.io/element-hq/element-web) (CLA). This ensures that their contribution will be made available under an OSI-approved open-source license, currently licensed under Affero General Public License v3 (AGPLv3) or General Public License v3 (GPLv3) at your choice.
|
Everyone is welcome to contribute code to Element Web, provided that they are willing to license their contributions to Element under a [Contributor License Agreement](https://cla-assistant.io/element-hq/element-web) (CLA). This ensures that their contribution will be made available under an OSI-approved open-source license, currently licensed under Affero General Public License v3 (AGPLv3) or General Public License v3 (GPLv3) at your choice.
|
||||||
|
|
||||||
If you're contributing, or thinking about contributing, please come & chat to
|
|
||||||
us in our development room, [#element-dev](https://matrix.to/#/#element-dev:matrix.org).
|
|
||||||
This is the best place to ask questions about the code, how to work on the project
|
|
||||||
or whether a change is likely to be accepted.
|
|
||||||
|
|
||||||
## How to contribute
|
## How to contribute
|
||||||
|
|
||||||
The preferred and easiest way to contribute changes to the project is to fork
|
The preferred and easiest way to contribute changes to the project is to fork
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# syntax=docker.io/docker/dockerfile:1.19-labs@sha256:dce1c693ef318bca08c964ba3122ae6248e45a1b96d65c4563c8dc6fe80349a2
|
# syntax=docker.io/docker/dockerfile:1.17-labs@sha256:9187104f31e3a002a8a6a3209ea1f937fb7486c093cbbde1e14b0fa0d7e4f1b5
|
||||||
|
|
||||||
# Builder
|
# Builder
|
||||||
FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:c102f42d665c164b4e5e5549813b1547ac8a9f1d343c7d17ddac106905a1c30b AS builder
|
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:2d63e0f812d023c4c764e83d7e30dc94949304443ebc372d5c445e63a5ae49c1 AS builder
|
||||||
|
|
||||||
# Support custom branch of the js-sdk. This also helps us build images of element-web develop.
|
# Support custom branch of the js-sdk. This also helps us build images of element-web develop.
|
||||||
ARG USE_CUSTOM_SDKS=false
|
ARG USE_CUSTOM_SDKS=false
|
||||||
@@ -19,7 +19,7 @@ RUN /src/scripts/docker-package.sh
|
|||||||
RUN cp /src/config.sample.json /src/webapp/config.json
|
RUN cp /src/config.sample.json /src/webapp/config.json
|
||||||
|
|
||||||
# App
|
# App
|
||||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:65a7f97c299b919190e96e38e2ff8358132732000d3bc5c00c07cc8763fca53f
|
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:e61b77b27c8f3124fad6d19e894ca5b603bcaf6a34a2df035511299dfa6fad35
|
||||||
|
|
||||||
# Need root user to install packages & manipulate the usr directory
|
# Need root user to install packages & manipulate the usr directory
|
||||||
USER root
|
USER root
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ Element has several tiers of support for different environments:
|
|||||||
- Best effort
|
- Best effort
|
||||||
- Definition:
|
- Definition:
|
||||||
- Issues **accepted**, regressions **do not block** the release
|
- Issues **accepted**, regressions **do not block** the release
|
||||||
- The wider Element Products (including Element Call and the Enterprise Server Suite) do still not officially support these browsers.
|
- The wider Element Products(including Element Call and the Enterprise Server Suite) do still not officially support these browsers.
|
||||||
- The element web project and its contributors should keep the client functioning and gracefully degrade where other sibling features (E.g. Element Call) may not function.
|
- The element web project and its contributors should keep the client functioning and gracefully degrade where other sibling features (E.g. Element Call) may not function.
|
||||||
- Last major release of Firefox ESR and Chrome/Edge Extended Stable
|
- Last major release of Firefox ESR and Chrome/Edge Extended Stable
|
||||||
- Community Supported
|
- Community Supported
|
||||||
|
|||||||
@@ -14,9 +14,10 @@ entrypoint_log() {
|
|||||||
mkdir -p /tmp/element-web-config
|
mkdir -p /tmp/element-web-config
|
||||||
cp /app/config*.json /tmp/element-web-config/
|
cp /app/config*.json /tmp/element-web-config/
|
||||||
|
|
||||||
# If the module directory exists AND the module directory has modules in it
|
# If there are modules to be loaded
|
||||||
if [ -d "/modules" ] && [ "$( ls -A '/modules' )" ]; then
|
if [ -d "/modules" ]; then
|
||||||
cd /modules
|
cd /modules
|
||||||
|
|
||||||
for MODULE in *
|
for MODULE in *
|
||||||
do
|
do
|
||||||
# If the module has a package.json, use its main field as the entrypoint
|
# If the module has a package.json, use its main field as the entrypoint
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
# MVVM
|
|
||||||
|
|
||||||
_Deprecated_, see [MVVM.md](./MVVM.md) for the current version.
|
|
||||||
|
|
||||||
General description of the pattern can be found [here](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel). But the gist of it is that you divide your code into three sections:
|
|
||||||
|
|
||||||
1. Model: This is where the business logic and data resides.
|
|
||||||
2. View Model: This code exists to provide the logic necessary for the UI. It directly uses the Model code.
|
|
||||||
3. View: This is the UI code itself and depends on the view model.
|
|
||||||
|
|
||||||
If you do MVVM right, your view should be dumb i.e it gets data from the view model and merely displays it.
|
|
||||||
|
|
||||||
### Practical guidelines for MVVM in element-web
|
|
||||||
|
|
||||||
#### Model
|
|
||||||
|
|
||||||
This is anywhere your data or business logic comes from. If your view model is accessing something simple exposed from `matrix-js-sdk`, then the sdk is your model. If you're using something more high level in element-web to get your data/logic (eg: `MemberListStore`), then that becomes your model.
|
|
||||||
|
|
||||||
#### View Model
|
|
||||||
|
|
||||||
1. View model is always a custom react hook named like `useFooViewModel()`.
|
|
||||||
2. The return type of your view model (known as view state) must be defined as a typescript interface:
|
|
||||||
```ts
|
|
||||||
inteface FooViewState {
|
|
||||||
somethingUseful: string;
|
|
||||||
somethingElse: BarType;
|
|
||||||
update: () => Promise<void>
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
3. Any react state that your UI needs must be in the view model.
|
|
||||||
|
|
||||||
#### View
|
|
||||||
|
|
||||||
1. Views are simple react components (eg: `FooView`).
|
|
||||||
2. Views usually start by calling the view model hook, eg:
|
|
||||||
```tsx
|
|
||||||
const FooView: React.FC<IProps> = (props: IProps) => {
|
|
||||||
const vm = useFooViewModel();
|
|
||||||
....
|
|
||||||
return(
|
|
||||||
<div>
|
|
||||||
{vm.somethingUseful}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
3. Views are also allowed to accept the view model as a prop, eg:
|
|
||||||
```tsx
|
|
||||||
const FooView: React.FC<IProps> = ({ vm }: IProps) => {
|
|
||||||
....
|
|
||||||
return(
|
|
||||||
<div>
|
|
||||||
{vm.somethingUseful}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
4. Multiple views can share the same view model if necessary.
|
|
||||||
|
|
||||||
### Benefits
|
|
||||||
|
|
||||||
1. MVVM forces a separation of concern i.e we will no longer have large react components that have a lot of state and rendering code mixed together. This improves code readability and makes it easier to introduce changes.
|
|
||||||
2. Introduces the possibility of code reuse. You can reuse an old view model with a new view or vice versa.
|
|
||||||
3. Adding to the point above, in future you could import element-web view models to your project and supply your own views thus creating something similar to the [hydrogen sdk](https://github.com/element-hq/hydrogen-web/blob/master/doc/SDK.md).
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
We started experimenting with MVVM in the redesigned memberlist, you can see the code [here](https://github.com/vector-im/element-web/blob/develop/src/components/views/rooms/MemberList/MemberListView.tsx).
|
|
||||||
96
docs/MVVM.md
@@ -10,80 +10,58 @@ If you do MVVM right, your view should be dumb i.e it gets data from the view mo
|
|||||||
|
|
||||||
### Practical guidelines for MVVM in element-web
|
### Practical guidelines for MVVM in element-web
|
||||||
|
|
||||||
A first documentation and implementation of MVVM was done in [MVVM-v1.md](MVVM-v1.md). This v1 version is now deprecated and this document describes the current implementation.
|
|
||||||
|
|
||||||
#### Model
|
#### Model
|
||||||
|
|
||||||
This is anywhere your data or business logic comes from. If your view model is accessing something simple exposed from `matrix-js-sdk`, then the sdk is your model. If you're using something more high level in element-web to get your data/logic (eg: `MemberListStore`), then that becomes your model.
|
This is anywhere your data or business logic comes from. If your view model is accessing something simple exposed from `matrix-js-sdk`, then the sdk is your model. If you're using something more high level in element-web to get your data/logic (eg: `MemberListStore`), then that becomes your model.
|
||||||
|
|
||||||
|
#### View Model
|
||||||
|
|
||||||
|
1. View model is always a custom react hook named like `useFooViewModel()`.
|
||||||
|
2. The return type of your view model (known as view state) must be defined as a typescript interface:
|
||||||
|
```ts
|
||||||
|
inteface FooViewState {
|
||||||
|
somethingUseful: string;
|
||||||
|
somethingElse: BarType;
|
||||||
|
update: () => Promise<void>
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
3. Any react state that your UI needs must be in the view model.
|
||||||
|
|
||||||
#### View
|
#### View
|
||||||
|
|
||||||
1. Located in [`shared-components`](https://github.com/element-hq/element-web/tree/develop/packages/shared-components). Develop it in storybook!
|
1. Views are simple react components (eg: `FooView`).
|
||||||
2. Views are simple react components (eg: `FooView`).
|
2. Views usually start by calling the view model hook, eg:
|
||||||
3. Views use [useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore) internally where the view model is the external store.
|
|
||||||
4. Views should define the interface of the view model they expect:
|
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// Snapshot is the return type of your view model
|
const FooView: React.FC<IProps> = (props: IProps) => {
|
||||||
interface FooViewSnapshot {
|
const vm = useFooViewModel();
|
||||||
value: string;
|
....
|
||||||
}
|
return(
|
||||||
|
<div>
|
||||||
// To call function on the view model
|
{vm.somethingUseful}
|
||||||
interface FooViewActions {
|
</div>
|
||||||
doSomething: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ViewModel is a type defining the methods needed for `useSyncExternalStore`
|
|
||||||
// https://github.com/element-hq/element-web/blob/develop/packages/shared-components/src/ViewModel.ts
|
|
||||||
type FooViewModel = ViewModel<FooViewSnapshot> & FooViewActions;
|
|
||||||
|
|
||||||
interface FooViewProps {
|
|
||||||
vm: FooViewModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
function FooView({ vm }: FooViewProps) {
|
|
||||||
// useViewModel is a helper function that uses useSyncExternalStore under the hood
|
|
||||||
const { value } = useViewModel(vm);
|
|
||||||
return (
|
|
||||||
<button type="button" onClick={() => vm.doSomething()}>
|
|
||||||
{value}
|
|
||||||
</button>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
3. Views are also allowed to accept the view model as a prop, eg:
|
||||||
5. Multiple views can share the same view model if necessary.
|
```tsx
|
||||||
6. A full example is available [here](https://github.com/element-hq/element-web/blob/develop/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.tsx)
|
const FooView: React.FC<IProps> = ({ vm }: IProps) => {
|
||||||
|
....
|
||||||
#### View Model
|
return(
|
||||||
|
<div>
|
||||||
1. A View model is a class extending [`BaseViewModel`](https://github.com/element-hq/element-web/blob/develop/src/viewmodels/base/BaseViewModel.ts).
|
{vm.somethingUseful}
|
||||||
2. Implements the interface defined in the view (e.g `FooViewModel` in the example above).
|
</div>
|
||||||
3. View models define a snapshot type that defines the data the view will consume. The snapshot is immutable and can only be changed by calling `this.snapshot.set(...)` in the view model. This will trigger a re-render in the view.
|
);
|
||||||
|
|
||||||
```ts
|
|
||||||
interface Props {
|
|
||||||
propsValue: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class FooViewModel extends BaseViewModel<FooViewSnapshot, Props> implements FooViewModel {
|
|
||||||
constructor(props: Props) {
|
|
||||||
// Call super with initial snapshot
|
|
||||||
super(props, { value: "initial" });
|
|
||||||
}
|
|
||||||
|
|
||||||
public doSomething() {
|
|
||||||
// Call this.snapshot.set to update the snapshot
|
|
||||||
this.snapshot.set({ value: "changed" });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
4. Multiple views can share the same view model if necessary.
|
||||||
4. A full example is available [here](https://github.com/element-hq/element-web/blob/develop/src/viewmodels/audio/AudioPlayerViewModel.ts)
|
|
||||||
|
|
||||||
### Benefits
|
### Benefits
|
||||||
|
|
||||||
1. MVVM forces a separation of concern i.e we will no longer have large react components that have a lot of state and rendering code mixed together. This improves code readability and makes it easier to introduce changes.
|
1. MVVM forces a separation of concern i.e we will no longer have large react components that have a lot of state and rendering code mixed together. This improves code readability and makes it easier to introduce changes.
|
||||||
2. Introduces the possibility of code reuse. You can reuse an old view model with a new view or vice versa.
|
2. Introduces the possibility of code reuse. You can reuse an old view model with a new view or vice versa.
|
||||||
3. Adding to the point above, in future you could import element-web view models to your project and supply your own views thus creating something similar to the [hydrogen sdk](https://github.com/element-hq/hydrogen-web/blob/master/doc/SDK.md).
|
3. Adding to the point above, in future you could import element-web view models to your project and supply your own views thus creating something similar to the [hydrogen sdk](https://github.com/element-hq/hydrogen-web/blob/master/doc/SDK.md).
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
We started experimenting with MVVM in the redesigned memberlist, you can see the code [here](https://github.com/vector-im/element-web/blob/develop/src/components/views/rooms/MemberList/MemberListView.tsx).
|
||||||
|
|||||||
@@ -585,8 +585,6 @@ Currently, the following UI feature flags are supported:
|
|||||||
- `UIFeature.BulkUnverifiedSessionsReminder` - Display popup reminders to verify or remove unverified sessions. Defaults
|
- `UIFeature.BulkUnverifiedSessionsReminder` - Display popup reminders to verify or remove unverified sessions. Defaults
|
||||||
to true.
|
to true.
|
||||||
- `UIFeature.locationSharing` - Whether or not location sharing menus will be shown.
|
- `UIFeature.locationSharing` - Whether or not location sharing menus will be shown.
|
||||||
- `UIFeature.allowCreatingPublicRooms` - Whether or not public rooms can be created.
|
|
||||||
- `UIFeature.allowCreatingPublicSpaces` - Whether or not public spaces can be created.
|
|
||||||
|
|
||||||
## Undocumented / developer options
|
## Undocumented / developer options
|
||||||
|
|
||||||
|
|||||||
43
docs/e2ee.md
@@ -38,20 +38,45 @@ When `force_disable` is true:
|
|||||||
Note: If the server is configured to forcibly enable encryption for some or all rooms,
|
Note: If the server is configured to forcibly enable encryption for some or all rooms,
|
||||||
this behaviour will be overridden.
|
this behaviour will be overridden.
|
||||||
|
|
||||||
# Setting up recovery
|
# Secure backup
|
||||||
|
|
||||||
By default, Element strongly encourages (but does not require) users to set up
|
By default, Element strongly encourages (but does not require) users to set up
|
||||||
recovery so that you can access history on your new devices as well as retain access to your message history and cryptographic identity when you lose all of your devices.
|
Secure Backup so that cross-signing identity key and message keys can be
|
||||||
|
recovered in case of a disaster where you lose access to all active devices.
|
||||||
|
|
||||||
## Removal of old settings
|
## Requiring secure backup
|
||||||
|
|
||||||
Support for the configuration options `secure_backup_required` and `secure_backup_setup_methods`
|
To require Secure Backup to be configured before Element can be used, set the
|
||||||
in the `/.well-known/matrix/client` config has been removed.
|
following on your homeserver's `/.well-known/matrix/client` config:
|
||||||
|
|
||||||
Setting up recovery is now always recommended to all users by showing a one-off toast and a
|
```json
|
||||||
permanent red dot on the _Encryption_ tab in the _Settings_ dialog. When creating a new
|
{
|
||||||
recovery key, the UI only supports auto-generated keys. Using an existing (custom) passphrase
|
"io.element.e2ee": {
|
||||||
still works, but is not exposed in the UI when setting up recovery.
|
"secure_backup_required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Preferring setup methods
|
||||||
|
|
||||||
|
By default, Element offers users a choice of a random key or user-chosen
|
||||||
|
passphrase when setting up Secure Backup. If a homeserver admin would like to
|
||||||
|
only offer one of these, you can signal this via the
|
||||||
|
`/.well-known/matrix/client` config, for example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"io.element.e2ee": {
|
||||||
|
"secure_backup_setup_methods": ["passphrase"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The field `secure_backup_setup_methods` is an array listing the methods the
|
||||||
|
client should display. Supported values currently include `key` and
|
||||||
|
`passphrase`. If the `secure_backup_setup_methods` field is not present or
|
||||||
|
exists but does not contain any supported methods, Element will fallback to the
|
||||||
|
default value of: `["key", "passphrase"]`.
|
||||||
|
|
||||||
# Compatibility
|
# Compatibility
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ There are some exceptions like when using localhost, which is considered a [secu
|
|||||||
1. Download the latest version from <https://github.com/element-hq/element-web/releases>
|
1. Download the latest version from <https://github.com/element-hq/element-web/releases>
|
||||||
1. Untar the tarball on your web server
|
1. Untar the tarball on your web server
|
||||||
1. Move (or symlink) the `element-x.x.x` directory to an appropriate name
|
1. Move (or symlink) the `element-x.x.x` directory to an appropriate name
|
||||||
1. Configure the correct caching headers in your webserver (see [README.md](../README.md#caching-requirements))
|
1. Configure the correct caching headers in your webserver (see below)
|
||||||
1. Configure the app by copying `config.sample.json` to `config.json` and
|
1. Configure the app by copying `config.sample.json` to `config.json` and
|
||||||
modifying it. See the [configuration docs](config.md) for details.
|
modifying it. See the [configuration docs](config.md) for details.
|
||||||
1. Enter the URL into your browser and log into Element!
|
1. Enter the URL into your browser and log into Element!
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const config: Config = {
|
|||||||
// This is needed to be able to load dual CJS/ESM WASM packages e.g. rust crypto & matrix-wywiwyg
|
// This is needed to be able to load dual CJS/ESM WASM packages e.g. rust crypto & matrix-wywiwyg
|
||||||
customExportConditions: ["browser", "node"],
|
customExportConditions: ["browser", "node"],
|
||||||
},
|
},
|
||||||
testMatch: ["<rootDir>/test/**/*-test.[tj]s?(x)", "<rootDir>/packages/*/src/**/*.test.[t]s?(x)"],
|
testMatch: ["<rootDir>/test/**/*-test.[tj]s?(x)", "<rootDir>/src/shared-components/**/*.test.[t]s?(x)"],
|
||||||
globalSetup: "<rootDir>/test/globalSetup.ts",
|
globalSetup: "<rootDir>/test/globalSetup.ts",
|
||||||
setupFiles: ["jest-canvas-mock", "web-streams-polyfill/polyfill"],
|
setupFiles: ["jest-canvas-mock", "web-streams-polyfill/polyfill"],
|
||||||
setupFilesAfterEnv: ["<rootDir>/test/setupTests.ts"],
|
setupFilesAfterEnv: ["<rootDir>/test/setupTests.ts"],
|
||||||
@@ -41,12 +41,9 @@ const config: Config = {
|
|||||||
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
|
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
|
||||||
"^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
|
"^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
|
||||||
},
|
},
|
||||||
transformIgnorePatterns: [
|
transformIgnorePatterns: ["/node_modules/(?!(mime|matrix-js-sdk)).+$"],
|
||||||
"/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs)).+$",
|
|
||||||
],
|
|
||||||
collectCoverageFrom: [
|
collectCoverageFrom: [
|
||||||
"<rootDir>/src/**/*.{js,ts,tsx}",
|
"<rootDir>/src/**/*.{js,ts,tsx}",
|
||||||
"<rootDir>/packages/**/*.{js,ts,tsx}",
|
|
||||||
// getSessionLock is piped into a different JS context via stringification, and the coverage functionality is
|
// getSessionLock is piped into a different JS context via stringification, and the coverage functionality is
|
||||||
// not available in that contest. So, turn off coverage instrumentation for it.
|
// not available in that contest. So, turn off coverage instrumentation for it.
|
||||||
"!<rootDir>/src/utils/SessionLock.ts",
|
"!<rootDir>/src/utils/SessionLock.ts",
|
||||||
|
|||||||
13
knip.ts
@@ -2,6 +2,7 @@ import { KnipConfig } from "knip";
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
entry: [
|
entry: [
|
||||||
|
"src/vector/index.ts",
|
||||||
"src/serviceworker/index.ts",
|
"src/serviceworker/index.ts",
|
||||||
"src/workers/*.worker.ts",
|
"src/workers/*.worker.ts",
|
||||||
"src/utils/exportUtils/exportJS.js",
|
"src/utils/exportUtils/exportJS.js",
|
||||||
@@ -11,6 +12,8 @@ export default {
|
|||||||
"res/decoder-ring/**",
|
"res/decoder-ring/**",
|
||||||
"res/jitsi_external_api.min.js",
|
"res/jitsi_external_api.min.js",
|
||||||
"docs/**",
|
"docs/**",
|
||||||
|
// Used by jest
|
||||||
|
"__mocks__/maplibre-gl.js",
|
||||||
],
|
],
|
||||||
project: ["**/*.{js,ts,jsx,tsx}"],
|
project: ["**/*.{js,ts,jsx,tsx}"],
|
||||||
ignore: [
|
ignore: [
|
||||||
@@ -19,8 +22,6 @@ export default {
|
|||||||
"src/hooks/useTimeout.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",
|
||||||
|
|
||||||
"packages/**/*",
|
|
||||||
],
|
],
|
||||||
ignoreDependencies: [
|
ignoreDependencies: [
|
||||||
// Required for `action-validator`
|
// Required for `action-validator`
|
||||||
@@ -41,18 +42,10 @@ export default {
|
|||||||
"util",
|
"util",
|
||||||
// Embedded into webapp
|
// Embedded into webapp
|
||||||
"@element-hq/element-call-embedded",
|
"@element-hq/element-call-embedded",
|
||||||
|
|
||||||
// Used by matrix-js-sdk, which means we have to include them as a
|
|
||||||
// dependency so that // we can run `tsc` (since we import the typescript
|
|
||||||
// source of js-sdk, rather than the transpiled and annotated JS like you
|
|
||||||
// would with a normal library).
|
|
||||||
"@types/content-type",
|
|
||||||
"@types/sdp-transform",
|
|
||||||
],
|
],
|
||||||
ignoreBinaries: [
|
ignoreBinaries: [
|
||||||
// Used in scripts & workflows
|
// Used in scripts & workflows
|
||||||
"jq",
|
"jq",
|
||||||
"wait-on",
|
|
||||||
],
|
],
|
||||||
ignoreExportsUsedInFile: true,
|
ignoreExportsUsedInFile: true,
|
||||||
} satisfies KnipConfig;
|
} satisfies KnipConfig;
|
||||||
|
|||||||
71
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "element-web",
|
"name": "element-web",
|
||||||
"version": "1.12.2",
|
"version": "1.11.108",
|
||||||
"description": "Element: the future of secure communication",
|
"description": "Element: the future of secure communication",
|
||||||
"author": "New Vector Ltd.",
|
"author": "New Vector Ltd.",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"UserFriendlyError"
|
"UserFriendlyError"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"i18n": "matrix-gen-i18n src res packages/shared-components && yarn i18n:sort && yarn i18n:lint",
|
"i18n": "matrix-gen-i18n && yarn i18n:sort && yarn i18n:lint",
|
||||||
"i18n:sort": "jq --sort-keys '.' src/i18n/strings/en_EN.json > src/i18n/strings/en_EN.json.tmp && mv src/i18n/strings/en_EN.json.tmp src/i18n/strings/en_EN.json",
|
"i18n:sort": "jq --sort-keys '.' src/i18n/strings/en_EN.json > src/i18n/strings/en_EN.json.tmp && mv src/i18n/strings/en_EN.json.tmp src/i18n/strings/en_EN.json",
|
||||||
"i18n:lint": "matrix-i18n-lint && prettier --log-level=silent --write src/i18n/strings/ --ignore-path /dev/null",
|
"i18n:lint": "matrix-i18n-lint && prettier --log-level=silent --write src/i18n/strings/ --ignore-path /dev/null",
|
||||||
"i18n:diff": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && yarn i18n && matrix-compare-i18n-files src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json",
|
"i18n:diff": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && yarn i18n && matrix-compare-i18n-files src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json",
|
||||||
@@ -65,23 +65,28 @@
|
|||||||
"coverage": "yarn test --coverage",
|
"coverage": "yarn test --coverage",
|
||||||
"analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp",
|
"analyse:webpack-bundles": "webpack-bundle-analyzer webpack-stats.json webapp",
|
||||||
"update:jitsi": "curl -s https://meet.element.io/libs/external_api.min.js > ./res/jitsi_external_api.min.js",
|
"update:jitsi": "curl -s https://meet.element.io/libs/external_api.min.js > ./res/jitsi_external_api.min.js",
|
||||||
"postinstall": "patch-package"
|
"postinstall": "patch-package",
|
||||||
|
"storybook": "storybook dev -p 6007",
|
||||||
|
"build-storybook": "storybook build",
|
||||||
|
"test:storybook": "test-storybook --url http://localhost:6007/",
|
||||||
|
"test:storybook:ci": "concurrently -k -s first -n \"SB,TEST\" \"yarn storybook --no-open\" \"wait-on tcp:6007 && yarn test-storybook --url http://localhost:6007/ --ci --maxWorkers=2\"",
|
||||||
|
"test:storybook:update": "playwright-screenshots --entrypoint yarn --with-node-modules && playwright-screenshots --entrypoint /work/node_modules/.bin/test-storybook --with-node-modules --url http://host.docker.internal:6007/ --updateSnapshot"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"**/pretty-format/react-is": "19.2.0",
|
"**/pretty-format/react-is": "19.1.1",
|
||||||
"@playwright/test": "1.56.1",
|
"@playwright/test": "1.54.2",
|
||||||
"@types/react": "19.2.2",
|
"@types/react": "19.1.9",
|
||||||
"@types/react-dom": "19.2.2",
|
"@types/react-dom": "19.1.7",
|
||||||
"oidc-client-ts": "3.3.0",
|
"oidc-client-ts": "3.3.0",
|
||||||
"jwt-decode": "4.0.0",
|
"jwt-decode": "4.0.0",
|
||||||
"caniuse-lite": "1.0.30001751",
|
"caniuse-lite": "1.0.30001724",
|
||||||
"testcontainers": "^11.0.0",
|
"testcontainers": "^11.0.0",
|
||||||
"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"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"@element-hq/element-web-module-api": "1.5.0",
|
"@element-hq/element-web-module-api": "1.4.1",
|
||||||
"@fontsource/inconsolata": "^5",
|
"@fontsource/inconsolata": "^5",
|
||||||
"@fontsource/inter": "^5",
|
"@fontsource/inter": "^5",
|
||||||
"@formatjs/intl-segmenter": "^11.5.7",
|
"@formatjs/intl-segmenter": "^11.5.7",
|
||||||
@@ -91,9 +96,10 @@
|
|||||||
"@matrix-org/spec": "^1.7.0",
|
"@matrix-org/spec": "^1.7.0",
|
||||||
"@sentry/browser": "^10.0.0",
|
"@sentry/browser": "^10.0.0",
|
||||||
"@types/png-chunks-extract": "^1.0.2",
|
"@types/png-chunks-extract": "^1.0.2",
|
||||||
|
"@types/react-virtualized": "^9.21.30",
|
||||||
"@vector-im/compound-design-tokens": "^6.0.0",
|
"@vector-im/compound-design-tokens": "^6.0.0",
|
||||||
"@vector-im/compound-web": "^8.1.2",
|
"@vector-im/compound-web": "^8.1.2",
|
||||||
"@vector-im/matrix-wysiwyg": "2.40.0",
|
"@vector-im/matrix-wysiwyg": "2.39.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",
|
||||||
@@ -111,7 +117,7 @@
|
|||||||
"emojibase-regex": "15.3.2",
|
"emojibase-regex": "15.3.2",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"filesize": "11.0.13",
|
"filesize": "11.0.2",
|
||||||
"github-markdown-css": "^5.5.1",
|
"github-markdown-css": "^5.5.1",
|
||||||
"glob-to-regexp": "^0.4.1",
|
"glob-to-regexp": "^0.4.1",
|
||||||
"highlight.js": "^11.3.1",
|
"highlight.js": "^11.3.1",
|
||||||
@@ -122,7 +128,6 @@
|
|||||||
"jsrsasign": "^11.0.0",
|
"jsrsasign": "^11.0.0",
|
||||||
"jszip": "^3.7.0",
|
"jszip": "^3.7.0",
|
||||||
"katex": "^0.16.0",
|
"katex": "^0.16.0",
|
||||||
"linkify-html": "4.3.2",
|
|
||||||
"linkify-react": "4.3.2",
|
"linkify-react": "4.3.2",
|
||||||
"linkify-string": "4.3.2",
|
"linkify-string": "4.3.2",
|
||||||
"linkifyjs": "4.3.2",
|
"linkifyjs": "4.3.2",
|
||||||
@@ -138,7 +143,7 @@
|
|||||||
"opus-recorder": "^8.0.3",
|
"opus-recorder": "^8.0.3",
|
||||||
"pako": "^2.0.3",
|
"pako": "^2.0.3",
|
||||||
"png-chunks-extract": "^1.0.0",
|
"png-chunks-extract": "^1.0.0",
|
||||||
"posthog-js": "1.280.1",
|
"posthog-js": "1.257.0",
|
||||||
"qrcode": "1.5.4",
|
"qrcode": "1.5.4",
|
||||||
"re-resizable": "6.11.2",
|
"re-resizable": "6.11.2",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
@@ -146,17 +151,17 @@
|
|||||||
"react-blurhash": "^0.3.0",
|
"react-blurhash": "^0.3.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-focus-lock": "^2.5.1",
|
"react-focus-lock": "^2.5.1",
|
||||||
"react-merge-refs": "^3.0.2",
|
|
||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
"react-transition-group": "^4.4.1",
|
"react-transition-group": "^4.4.1",
|
||||||
"react-virtuoso": "^4.14.0",
|
"react-virtualized": "^9.22.5",
|
||||||
|
"react-virtuoso": "^4.12.6",
|
||||||
"rfc4648": "^1.4.0",
|
"rfc4648": "^1.4.0",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"sanitize-html": "2.17.0",
|
"sanitize-html": "2.17.0",
|
||||||
"tar-js": "^0.3.0",
|
"tar-js": "^0.3.0",
|
||||||
"temporal-polyfill": "^0.3.0",
|
"temporal-polyfill": "^0.3.0",
|
||||||
"ua-parser-js": "1.0.40",
|
"ua-parser-js": "^1.0.2",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^11.0.0",
|
||||||
"what-input": "^5.2.10"
|
"what-input": "^5.2.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -181,13 +186,18 @@
|
|||||||
"@babel/preset-typescript": "^7.12.7",
|
"@babel/preset-typescript": "^7.12.7",
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"@casualbot/jest-sonar-reporter": "2.2.7",
|
"@casualbot/jest-sonar-reporter": "2.2.7",
|
||||||
"@element-hq/element-call-embedded": "0.16.1",
|
"@element-hq/element-call-embedded": "0.14.1",
|
||||||
"@element-hq/element-web-playwright-common": "^2.0.0",
|
"@element-hq/element-web-playwright-common": "^1.4.4",
|
||||||
"@peculiar/webcrypto": "^1.4.3",
|
"@peculiar/webcrypto": "^1.4.3",
|
||||||
"@playwright/test": "^1.50.1",
|
"@playwright/test": "^1.50.1",
|
||||||
"@principalstudio/html-webpack-inject-preload": "^1.2.7",
|
"@principalstudio/html-webpack-inject-preload": "^1.2.7",
|
||||||
|
"@rrweb/types": "^2.0.0-alpha.18",
|
||||||
"@sentry/webpack-plugin": "^4.0.0",
|
"@sentry/webpack-plugin": "^4.0.0",
|
||||||
"@storybook/react-vite": "^9.1.10",
|
"@storybook/addon-designs": "^10.0.1",
|
||||||
|
"@storybook/addon-docs": "^9.0.12",
|
||||||
|
"@storybook/icons": "^1.4.0",
|
||||||
|
"@storybook/react-vite": "^9.0.15",
|
||||||
|
"@storybook/test-runner": "^0.23.0",
|
||||||
"@stylistic/eslint-plugin": "^5.0.0",
|
"@stylistic/eslint-plugin": "^5.0.0",
|
||||||
"@svgr/webpack": "^8.0.0",
|
"@svgr/webpack": "^8.0.0",
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@testing-library/dom": "^10.4.0",
|
||||||
@@ -195,7 +205,6 @@
|
|||||||
"@testing-library/react": "^16.0.0",
|
"@testing-library/react": "^16.0.0",
|
||||||
"@testing-library/user-event": "^14.5.2",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@types/commonmark": "^0.27.4",
|
"@types/commonmark": "^0.27.4",
|
||||||
"@types/content-type": "^1.1.9",
|
|
||||||
"@types/counterpart": "^0.18.1",
|
"@types/counterpart": "^0.18.1",
|
||||||
"@types/css-tree": "^2.3.8",
|
"@types/css-tree": "^2.3.8",
|
||||||
"@types/diff-match-patch": "^1.0.32",
|
"@types/diff-match-patch": "^1.0.32",
|
||||||
@@ -214,15 +223,15 @@
|
|||||||
"@types/node-fetch": "^2.6.2",
|
"@types/node-fetch": "^2.6.2",
|
||||||
"@types/pako": "^2.0.0",
|
"@types/pako": "^2.0.0",
|
||||||
"@types/qrcode": "^1.3.5",
|
"@types/qrcode": "^1.3.5",
|
||||||
"@types/react": "19.2.2",
|
"@types/react": "19.1.9",
|
||||||
"@types/react-beautiful-dnd": "^13.0.0",
|
"@types/react-beautiful-dnd": "^13.0.0",
|
||||||
"@types/react-dom": "19.2.2",
|
"@types/react-dom": "19.1.7",
|
||||||
"@types/react-transition-group": "^4.4.0",
|
"@types/react-transition-group": "^4.4.0",
|
||||||
"@types/sanitize-html": "2.16.0",
|
"@types/sanitize-html": "2.16.0",
|
||||||
"@types/sdp-transform": "^2.4.10",
|
|
||||||
"@types/semver": "^7.5.8",
|
"@types/semver": "^7.5.8",
|
||||||
"@types/tar-js": "^0.3.5",
|
"@types/tar-js": "^0.3.5",
|
||||||
"@types/ua-parser-js": "^0.7.36",
|
"@types/ua-parser-js": "^0.7.36",
|
||||||
|
"@types/uuid": "^10.0.0",
|
||||||
"@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",
|
||||||
@@ -240,14 +249,15 @@
|
|||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
"eslint-config-google": "^0.14.0",
|
"eslint-config-google": "^0.14.0",
|
||||||
"eslint-config-prettier": "^10.0.0",
|
"eslint-config-prettier": "^10.0.0",
|
||||||
"eslint-plugin-deprecate": "0.8.7",
|
"eslint-plugin-deprecate": "0.8.5",
|
||||||
"eslint-plugin-import": "^2.25.4",
|
"eslint-plugin-import": "^2.25.4",
|
||||||
"eslint-plugin-jest": "^29.0.0",
|
"eslint-plugin-jest": "^28.0.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||||
"eslint-plugin-matrix-org": "^3.0.0",
|
"eslint-plugin-matrix-org": "^2.0.2",
|
||||||
"eslint-plugin-react": "^7.28.0",
|
"eslint-plugin-react": "^7.28.0",
|
||||||
"eslint-plugin-react-compiler": "^19.0.0-beta-df7b47d-20241124",
|
"eslint-plugin-react-compiler": "^19.0.0-beta-df7b47d-20241124",
|
||||||
"eslint-plugin-react-hooks": "^7.0.0",
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
|
"eslint-plugin-storybook": "^9.0.12",
|
||||||
"eslint-plugin-unicorn": "^56.0.0",
|
"eslint-plugin-unicorn": "^56.0.0",
|
||||||
"express": "^5.0.0",
|
"express": "^5.0.0",
|
||||||
"fake-indexeddb": "^6.0.0",
|
"fake-indexeddb": "^6.0.0",
|
||||||
@@ -260,6 +270,7 @@
|
|||||||
"jest": "^29.6.2",
|
"jest": "^29.6.2",
|
||||||
"jest-canvas-mock": "^2.5.2",
|
"jest-canvas-mock": "^2.5.2",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
|
"jest-image-snapshot": "^6.5.1",
|
||||||
"jest-mock": "^29.6.2",
|
"jest-mock": "^29.6.2",
|
||||||
"jest-raw-loader": "^1.0.1",
|
"jest-raw-loader": "^1.0.1",
|
||||||
"jsqr": "^1.4.0",
|
"jsqr": "^1.4.0",
|
||||||
@@ -288,7 +299,7 @@
|
|||||||
"rimraf": "^6.0.0",
|
"rimraf": "^6.0.0",
|
||||||
"semver": "^7.5.2",
|
"semver": "^7.5.2",
|
||||||
"source-map-loader": "^5.0.0",
|
"source-map-loader": "^5.0.0",
|
||||||
"storybook": "^9.1.10",
|
"storybook": "^9.0.12",
|
||||||
"stylelint": "^16.23.0",
|
"stylelint": "^16.23.0",
|
||||||
"stylelint-config-standard": "^39.0.0",
|
"stylelint-config-standard": "^39.0.0",
|
||||||
"stylelint-scss": "^6.0.0",
|
"stylelint-scss": "^6.0.0",
|
||||||
@@ -298,6 +309,8 @@
|
|||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "5.8.3",
|
"typescript": "5.8.3",
|
||||||
"util": "^0.12.5",
|
"util": "^0.12.5",
|
||||||
|
"vite": "^7.0.1",
|
||||||
|
"vite-plugin-node-polyfills": "^0.24.0",
|
||||||
"web-streams-polyfill": "^4.0.0",
|
"web-streams-polyfill": "^4.0.0",
|
||||||
"webpack": "^5.89.0",
|
"webpack": "^5.89.0",
|
||||||
"webpack-bundle-analyzer": "^4.8.0",
|
"webpack-bundle-analyzer": "^4.8.0",
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
|
|
||||||
extends: [
|
|
||||||
"plugin:matrix-org/babel",
|
|
||||||
"plugin:matrix-org/react",
|
|
||||||
"plugin:matrix-org/a11y",
|
|
||||||
"plugin:storybook/recommended",
|
|
||||||
],
|
|
||||||
parserOptions: {
|
|
||||||
project: ["./tsconfig.json"],
|
|
||||||
},
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
// Bind or arrow functions in props causes performance issues (but we
|
|
||||||
// currently use them in some places).
|
|
||||||
// It's disabled here, but we should using it sparingly.
|
|
||||||
"react/jsx-no-bind": "off",
|
|
||||||
"react/jsx-key": ["error"],
|
|
||||||
"matrix-org/require-copyright-header": "error",
|
|
||||||
"react-compiler/react-compiler": "error",
|
|
||||||
},
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ["src/**/*.{ts,tsx}", "test/**/*.{ts,tsx}"],
|
|
||||||
extends: ["plugin:matrix-org/typescript", "plugin:matrix-org/react"],
|
|
||||||
rules: {
|
|
||||||
"@typescript-eslint/explicit-function-return-type": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
allowExpressions: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
// Remove Babel things manually due to override limitations
|
|
||||||
"@babel/no-invalid-this": ["off"],
|
|
||||||
|
|
||||||
// We're okay being explicit at the moment
|
|
||||||
"@typescript-eslint/no-empty-interface": "off",
|
|
||||||
// We disable this while we're transitioning
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
// We'd rather not do this but we do
|
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
|
||||||
// We're okay with assertion errors when we ask for them
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
|
||||||
"@typescript-eslint/no-empty-object-type": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
// We do this sometimes to brand interfaces
|
|
||||||
allowInterfaces: "with-single-extends",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: "detect",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
dist/
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@element-hq/web-shared-components",
|
|
||||||
"version": "0.0.0-test.6",
|
|
||||||
"description": "Shared components for Element",
|
|
||||||
"author": "New Vector Ltd.",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/element-hq/element-web"
|
|
||||||
},
|
|
||||||
"exports": {
|
|
||||||
".": {
|
|
||||||
"require": {
|
|
||||||
"style": "./dist/element-web-shared-components.css",
|
|
||||||
"types": "./dist/element-web-shared-components.d.ts",
|
|
||||||
"default": "./dist/element-web-shared-components.umd.js"
|
|
||||||
},
|
|
||||||
"import": {
|
|
||||||
"style": "./dist/element-web-shared-components.css",
|
|
||||||
"types": "./dist/element-web-shared-components.d.ts",
|
|
||||||
"default": "./dist/element-web-shared-components.mjs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"types": "dist/element-web-shared-components.d.ts",
|
|
||||||
"files": [
|
|
||||||
"dist",
|
|
||||||
"src",
|
|
||||||
"LICENSE",
|
|
||||||
"README.md",
|
|
||||||
"package.json"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"postinstall": "patch-package",
|
|
||||||
"prepare": "vite build",
|
|
||||||
"storybook": "storybook dev -p 6007",
|
|
||||||
"build-storybook": "storybook build",
|
|
||||||
"lint": "yarn lint:types && yarn lint:js",
|
|
||||||
"lint:js": "eslint --max-warnings 0 src && prettier --check .",
|
|
||||||
"lint:types": "tsc --noEmit --jsx react",
|
|
||||||
"test:storybook": "test-storybook --url http://localhost:6007/",
|
|
||||||
"test:storybook:ci": "concurrently -k -s first -n \"SB,TEST\" \"yarn storybook --no-open\" \"wait-on tcp:6007 && yarn test-storybook --url http://localhost:6007/ --ci --maxWorkers=2\"",
|
|
||||||
"test:storybook:update": "playwright-screenshots --entrypoint yarn --with-node-modules && playwright-screenshots --entrypoint /work/node_modules/.bin/test-storybook --with-node-modules --url http://host.docker.internal:6007/ --updateSnapshot"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"matrix-web-i18n": "^3.4.0",
|
|
||||||
"patch-package": "^8.0.1",
|
|
||||||
"counterpart": "^0.18.6"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@storybook/addon-a11y": "^9.1.10",
|
|
||||||
"@storybook/addon-designs": "^10.0.2",
|
|
||||||
"@storybook/addon-docs": "^9.1.10",
|
|
||||||
"@storybook/icons": "^1.6.0",
|
|
||||||
"@storybook/react-vite": "^9.1.10",
|
|
||||||
"@storybook/test-runner": "^0.23.0",
|
|
||||||
"concurrently": "^9.2.1",
|
|
||||||
"eslint": "8",
|
|
||||||
"eslint-plugin-storybook": "^10.0.0",
|
|
||||||
"jest-image-snapshot": "^6.5.1",
|
|
||||||
"patch-package": "^8.0.1",
|
|
||||||
"prettier": "^3.6.2",
|
|
||||||
"storybook": "^9.1.10",
|
|
||||||
"typescript": "^5.9.3",
|
|
||||||
"vite": "^7.1.9",
|
|
||||||
"vite-plugin-dts": "^4.5.4",
|
|
||||||
"vite-plugin-node-polyfills": "^0.24.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.0.0"
|
|
||||||
},
|
|
||||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
@@ -1,8 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare module "*.css";
|
|
||||||
@@ -1,31 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.avatarWithDetails {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
border-radius: 12px;
|
|
||||||
background-color: var(--cpd-color-gray-200);
|
|
||||||
padding: var(--cpd-space-2x);
|
|
||||||
gap: var(--cpd-space-2x);
|
|
||||||
|
|
||||||
.title {
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
font-weight: var(--cpd-font-weight-semibold);
|
|
||||||
font-size: var(--cpd-font-size-body-md);
|
|
||||||
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details {
|
|
||||||
font-size: var(--cpd-font-size-body-sm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +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 React from "react";
|
|
||||||
import { type Meta, type StoryObj } from "@storybook/react-vite/*";
|
|
||||||
|
|
||||||
import { AvatarWithDetails } from "./AvatarWithDetails";
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
title: "Avatar/AvatarWithDetails",
|
|
||||||
component: AvatarWithDetails,
|
|
||||||
tags: ["autodocs"],
|
|
||||||
args: {
|
|
||||||
avatar: <div style={{ width: 40, height: 40, backgroundColor: "#888", borderRadius: "50%" }} />,
|
|
||||||
details: "Details about the avatar go here",
|
|
||||||
title: "Room Name",
|
|
||||||
},
|
|
||||||
} satisfies Meta<typeof AvatarWithDetails>;
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof meta>;
|
|
||||||
export const Default: Story = {};
|
|
||||||
@@ -1,21 +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 { composeStories } from "@storybook/react-vite";
|
|
||||||
import { render } from "jest-matrix-react";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import * as stories from "./AvatarWithDetails.stories.tsx";
|
|
||||||
|
|
||||||
const { Default } = composeStories(stories);
|
|
||||||
|
|
||||||
describe("AvatarWithDetails", () => {
|
|
||||||
it("renders a textual event", () => {
|
|
||||||
const { container } = render(<Default />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,65 +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 ComponentProps, type ElementType, type JSX, type PropsWithChildren } from "react";
|
|
||||||
import React from "react";
|
|
||||||
import classNames from "classnames";
|
|
||||||
|
|
||||||
import styles from "./AvatarWithDetails.module.css";
|
|
||||||
import { Flex } from "../../utils/Flex";
|
|
||||||
|
|
||||||
export type AvatarWithDetailsProps<C extends ElementType> = {
|
|
||||||
/**
|
|
||||||
* The HTML tag.
|
|
||||||
* @default "div"
|
|
||||||
*/
|
|
||||||
as?: C;
|
|
||||||
/**
|
|
||||||
* The CSS class name.
|
|
||||||
*/
|
|
||||||
className?: string;
|
|
||||||
/**
|
|
||||||
* The title/label next to the avatar. Usually the user or room name.
|
|
||||||
*/
|
|
||||||
title: string;
|
|
||||||
/**
|
|
||||||
* A label with details to display under the avatar title.
|
|
||||||
* Commonly used to display the number of participants in a room.
|
|
||||||
*/
|
|
||||||
details: React.ReactNode;
|
|
||||||
/** The avatar to display. */
|
|
||||||
avatar: React.ReactNode;
|
|
||||||
} & ComponentProps<C>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A component to display an avatar with a title next to it in a grey box.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```tsx
|
|
||||||
* <AvatarWithDetails title="Room Name" details="10 participants" className="custom-class" />
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function AvatarWithDetails<C extends React.ElementType = "div">({
|
|
||||||
as,
|
|
||||||
className,
|
|
||||||
details,
|
|
||||||
avatar,
|
|
||||||
title,
|
|
||||||
...props
|
|
||||||
}: PropsWithChildren<AvatarWithDetailsProps<C>>): JSX.Element {
|
|
||||||
const Component = as || "div";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Component className={classNames(styles.avatarWithDetails, className)} {...props}>
|
|
||||||
{avatar}
|
|
||||||
<Flex direction="column">
|
|
||||||
<span className={styles.title}>{title}</span>
|
|
||||||
<span className={styles.details}>{details}</span>
|
|
||||||
</Flex>
|
|
||||||
</Component>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`AvatarWithDetails renders a textual event 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="avatarWithDetails"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style="width: 40px; height: 40px; background-color: rgb(136, 136, 136); border-radius: 50%;"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="flex"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="title"
|
|
||||||
>
|
|
||||||
Room Name
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="details"
|
|
||||||
>
|
|
||||||
Details about the avatar go here
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,8 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { AvatarWithDetails } from "./AvatarWithDetails";
|
|
||||||
@@ -1,155 +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 KeyboardEvent } from "react";
|
|
||||||
import { renderHook } from "jest-matrix-react";
|
|
||||||
|
|
||||||
import { useListKeyboardNavigation } from "./useListKeyboardNavigation";
|
|
||||||
|
|
||||||
describe("useListKeyDown", () => {
|
|
||||||
let mockList: HTMLUListElement;
|
|
||||||
let mockItems: HTMLElement[];
|
|
||||||
let mockEvent: Partial<KeyboardEvent<HTMLUListElement>>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// Create mock DOM elements
|
|
||||||
mockList = document.createElement("ul");
|
|
||||||
mockItems = [document.createElement("li"), document.createElement("li"), document.createElement("li")];
|
|
||||||
|
|
||||||
// Set up the DOM structure
|
|
||||||
mockItems.forEach((item, index) => {
|
|
||||||
item.setAttribute("tabindex", "0");
|
|
||||||
item.setAttribute("data-testid", `item-${index}`);
|
|
||||||
mockList.appendChild(item);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.body.appendChild(mockList);
|
|
||||||
|
|
||||||
// Mock event object
|
|
||||||
mockEvent = {
|
|
||||||
preventDefault: jest.fn(),
|
|
||||||
key: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mock focus methods
|
|
||||||
mockItems.forEach((item) => {
|
|
||||||
item.focus = jest.fn();
|
|
||||||
item.click = jest.fn();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
document.body.removeChild(mockList);
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
function render(): {
|
|
||||||
current: {
|
|
||||||
listRef: React.RefObject<HTMLUListElement | null>;
|
|
||||||
onKeyDown: React.KeyboardEventHandler<HTMLUListElement>;
|
|
||||||
onFocus: React.FocusEventHandler<HTMLUListElement>;
|
|
||||||
};
|
|
||||||
} {
|
|
||||||
const { result } = renderHook(() => useListKeyboardNavigation());
|
|
||||||
result.current.listRef.current = mockList;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
["Enter", "Enter"],
|
|
||||||
["Space", " "],
|
|
||||||
])("should handle %s key to click active element", (name, key) => {
|
|
||||||
const result = render();
|
|
||||||
|
|
||||||
// Mock document.activeElement
|
|
||||||
Object.defineProperty(document, "activeElement", {
|
|
||||||
value: mockItems[1],
|
|
||||||
configurable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Simulate key press
|
|
||||||
result.current.onKeyDown({
|
|
||||||
...mockEvent,
|
|
||||||
key,
|
|
||||||
} as KeyboardEvent<HTMLUListElement>);
|
|
||||||
|
|
||||||
expect(mockItems[1].click).toHaveBeenCalledTimes(1);
|
|
||||||
expect(mockEvent.preventDefault).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each(
|
|
||||||
// key, finalPosition, startPosition
|
|
||||||
[
|
|
||||||
["ArrowDown", 1, 0],
|
|
||||||
["ArrowUp", 1, 2],
|
|
||||||
["Home", 0, 1],
|
|
||||||
["End", 2, 1],
|
|
||||||
],
|
|
||||||
)("should handle %s to focus the %inth element", (key, finalPosition, startPosition) => {
|
|
||||||
const result = render();
|
|
||||||
mockList.contains = jest.fn().mockReturnValue(true);
|
|
||||||
|
|
||||||
Object.defineProperty(document, "activeElement", {
|
|
||||||
value: mockItems[startPosition],
|
|
||||||
configurable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
result.current.onKeyDown({
|
|
||||||
...mockEvent,
|
|
||||||
key,
|
|
||||||
} as KeyboardEvent<HTMLUListElement>);
|
|
||||||
|
|
||||||
expect(mockItems[finalPosition].focus).toHaveBeenCalledTimes(1);
|
|
||||||
expect(mockEvent.preventDefault).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each([["ArrowDown"], ["ArrowUp"]])("should not handle %s when active element is not in list", (key) => {
|
|
||||||
const result = render();
|
|
||||||
mockList.contains = jest.fn().mockReturnValue(false);
|
|
||||||
|
|
||||||
const outsideElement = document.createElement("button");
|
|
||||||
|
|
||||||
Object.defineProperty(document, "activeElement", {
|
|
||||||
value: outsideElement,
|
|
||||||
configurable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
result.current.onKeyDown({
|
|
||||||
...mockEvent,
|
|
||||||
key,
|
|
||||||
} as KeyboardEvent<HTMLUListElement>);
|
|
||||||
|
|
||||||
// No item should be focused
|
|
||||||
mockItems.forEach((item) => expect(item.focus).not.toHaveBeenCalled());
|
|
||||||
expect(mockEvent.preventDefault).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not prevent default for unhandled keys", () => {
|
|
||||||
const result = render();
|
|
||||||
|
|
||||||
result.current.onKeyDown({
|
|
||||||
...mockEvent,
|
|
||||||
key: "Tab",
|
|
||||||
} as KeyboardEvent<HTMLUListElement>);
|
|
||||||
|
|
||||||
expect(mockEvent.preventDefault).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should focus the first item if list itself is focused", () => {
|
|
||||||
const result = render();
|
|
||||||
result.current.onFocus({ target: mockList } as React.FocusEvent<HTMLUListElement>);
|
|
||||||
expect(mockItems[0].focus).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should focus the selected item if list itself is focused", () => {
|
|
||||||
mockItems[1].setAttribute("aria-selected", "true");
|
|
||||||
const result = render();
|
|
||||||
|
|
||||||
result.current.onFocus({ target: mockList } as React.FocusEvent<HTMLUListElement>);
|
|
||||||
expect(mockItems[1].focus).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,92 +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,
|
|
||||||
useRef,
|
|
||||||
type RefObject,
|
|
||||||
type KeyboardEvent,
|
|
||||||
type KeyboardEventHandler,
|
|
||||||
type FocusEventHandler,
|
|
||||||
type FocusEvent,
|
|
||||||
} from "react";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A hook that provides keyboard navigation for a list of options.
|
|
||||||
*/
|
|
||||||
export function useListKeyboardNavigation(): {
|
|
||||||
listRef: RefObject<HTMLUListElement | null>;
|
|
||||||
onKeyDown: KeyboardEventHandler<HTMLUListElement>;
|
|
||||||
onFocus: FocusEventHandler<HTMLUListElement>;
|
|
||||||
} {
|
|
||||||
const listRef = useRef<HTMLUListElement>(null);
|
|
||||||
|
|
||||||
const onFocus = useCallback((evt: FocusEvent<HTMLUListElement>) => {
|
|
||||||
if (!listRef.current) return;
|
|
||||||
|
|
||||||
if (evt.target === listRef.current) {
|
|
||||||
// By default, focus the selected item
|
|
||||||
let selectedChild = listRef.current?.firstElementChild;
|
|
||||||
|
|
||||||
// If there is a selected item, focus that instead
|
|
||||||
for (const child of listRef.current.children) {
|
|
||||||
if (child.getAttribute("aria-selected") === "true") {
|
|
||||||
selectedChild = child;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(selectedChild as HTMLElement)?.focus();
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onKeyDown = useCallback((evt: KeyboardEvent<HTMLUListElement>) => {
|
|
||||||
const { key } = evt;
|
|
||||||
|
|
||||||
let handled = false;
|
|
||||||
|
|
||||||
switch (key) {
|
|
||||||
case "Enter":
|
|
||||||
case " ": {
|
|
||||||
handled = true;
|
|
||||||
(document.activeElement as HTMLElement).click();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "ArrowDown": {
|
|
||||||
handled = true;
|
|
||||||
const currentFocus = document.activeElement;
|
|
||||||
if (listRef.current?.contains(currentFocus) && currentFocus) {
|
|
||||||
(currentFocus.nextElementSibling as HTMLElement)?.focus();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "ArrowUp": {
|
|
||||||
handled = true;
|
|
||||||
const currentFocus = document.activeElement;
|
|
||||||
if (listRef.current?.contains(currentFocus) && currentFocus) {
|
|
||||||
(currentFocus.previousElementSibling as HTMLElement)?.focus();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "Home": {
|
|
||||||
handled = true;
|
|
||||||
(listRef.current?.firstElementChild as HTMLElement)?.focus();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "End": {
|
|
||||||
handled = true;
|
|
||||||
(listRef.current?.lastElementChild as HTMLElement)?.focus();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handled) {
|
|
||||||
evt.preventDefault();
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
return { listRef, onKeyDown, onFocus };
|
|
||||||
}
|
|
||||||
@@ -1,32 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Components
|
|
||||||
export * from "./audio/AudioPlayerView";
|
|
||||||
export * from "./audio/Clock";
|
|
||||||
export * from "./audio/PlayPauseButton";
|
|
||||||
export * from "./audio/SeekBar";
|
|
||||||
export * from "./avatar/AvatarWithDetails";
|
|
||||||
export * from "./event-tiles/TextualEventView";
|
|
||||||
export * from "./message-body/MediaBody";
|
|
||||||
export * from "./pill-input/Pill";
|
|
||||||
export * from "./pill-input/PillInput";
|
|
||||||
export * from "./rich-list/RichItem";
|
|
||||||
export * from "./rich-list/RichList";
|
|
||||||
export * from "./utils/Box";
|
|
||||||
export * from "./utils/Flex";
|
|
||||||
|
|
||||||
// Utils
|
|
||||||
export { setLanguage } from "./utils/i18n";
|
|
||||||
export * from "./utils/humanize";
|
|
||||||
export * from "./utils/DateUtils";
|
|
||||||
export * from "./utils/numbers";
|
|
||||||
|
|
||||||
// MVVM
|
|
||||||
export * from "./viewmodel";
|
|
||||||
export * from "./useMockedViewModel";
|
|
||||||
export * from "./useViewModel";
|
|
||||||
@@ -1,17 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.pill {
|
|
||||||
background-color: var(--cpd-color-bg-action-primary-rest);
|
|
||||||
padding: var(--cpd-space-1x) var(--cpd-space-1-5x) var(--cpd-space-1x) var(--cpd-space-1x);
|
|
||||||
border-radius: 99px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
color: var(--cpd-color-text-on-solid-primary);
|
|
||||||
font: var(--cpd-font-body-sm-medium);
|
|
||||||
}
|
|
||||||
@@ -1,33 +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 React from "react";
|
|
||||||
import { fn } from "storybook/test";
|
|
||||||
|
|
||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
||||||
import { Pill } from "./Pill";
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
title: "PillInput/Pill",
|
|
||||||
component: Pill,
|
|
||||||
tags: ["autodocs"],
|
|
||||||
args: {
|
|
||||||
label: "Pill",
|
|
||||||
children: <div style={{ width: 20, height: 20, borderRadius: "100%", backgroundColor: "#ccc" }} />,
|
|
||||||
onClick: fn(),
|
|
||||||
},
|
|
||||||
} satisfies Meta<typeof Pill>;
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof meta>;
|
|
||||||
|
|
||||||
export const Default: Story = {};
|
|
||||||
export const WithoutCloseButton: Story = {
|
|
||||||
args: {
|
|
||||||
onClick: undefined,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,26 +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 { composeStories } from "@storybook/react-vite";
|
|
||||||
import { render } from "jest-matrix-react";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import * as stories from "./Pill.stories";
|
|
||||||
|
|
||||||
const { Default, WithoutCloseButton } = composeStories(stories);
|
|
||||||
|
|
||||||
describe("Pill", () => {
|
|
||||||
it("renders the pill", () => {
|
|
||||||
const { container } = render(<Default />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders the pill without close button", () => {
|
|
||||||
const { container } = render(<WithoutCloseButton />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,62 +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 React, { type MouseEventHandler, type JSX, type PropsWithChildren, type HTMLAttributes, useId } from "react";
|
|
||||||
import classNames from "classnames";
|
|
||||||
import { IconButton } from "@vector-im/compound-web";
|
|
||||||
import CloseIcon from "@vector-im/compound-design-tokens/assets/web/icons/close";
|
|
||||||
|
|
||||||
import { Flex } from "../../utils/Flex";
|
|
||||||
import styles from "./Pill.module.css";
|
|
||||||
import { _t } from "../../utils/i18n";
|
|
||||||
|
|
||||||
export interface PillProps extends Omit<HTMLAttributes<HTMLDivElement>, "onClick"> {
|
|
||||||
/**
|
|
||||||
* The text label to display inside the pill.
|
|
||||||
*/
|
|
||||||
label: string;
|
|
||||||
/**
|
|
||||||
* Optional click handler for a close button.
|
|
||||||
* If provided, a close button will be rendered.
|
|
||||||
*/
|
|
||||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A pill component that can display a label and an optional close button.
|
|
||||||
* The badge can also contain child elements, such as icons or avatars.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```tsx
|
|
||||||
* <Pill label="New" onClick={() => console.log("Closed")}>
|
|
||||||
* <SomeIcon />
|
|
||||||
* </Pill>
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function Pill({ className, children, label, onClick, ...props }: PropsWithChildren<PillProps>): JSX.Element {
|
|
||||||
const id = useId();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
display="inline-flex"
|
|
||||||
gap="var(--cpd-space-1-5x)"
|
|
||||||
align="center"
|
|
||||||
className={classNames(styles.pill, className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<span id={id} className={styles.label}>
|
|
||||||
{label}
|
|
||||||
</span>
|
|
||||||
{onClick && (
|
|
||||||
<IconButton aria-describedby={id} size="16px" onClick={onClick} aria-label={_t("action|delete")}>
|
|
||||||
<CloseIcon color="var(--cpd-color-icon-tertiary)" />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`Pill renders the pill 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="flex pill"
|
|
||||||
style="--mx-flex-display: inline-flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-1-5x); --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style="width: 20px; height: 20px; border-radius: 100%; background-color: rgb(204, 204, 204);"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="label"
|
|
||||||
id="_r_0_"
|
|
||||||
>
|
|
||||||
Pill
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
aria-describedby="_r_0_"
|
|
||||||
aria-label="Delete"
|
|
||||||
class="_icon-button_1pz9o_8"
|
|
||||||
data-kind="primary"
|
|
||||||
role="button"
|
|
||||||
style="--cpd-icon-button-size: 16px;"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="_indicator-icon_zr2a0_17"
|
|
||||||
style="--cpd-icon-button-size: 100%;"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
color="var(--cpd-color-icon-tertiary)"
|
|
||||||
fill="currentColor"
|
|
||||||
height="1em"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="1em"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Pill renders the pill without close button 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="flex pill"
|
|
||||||
style="--mx-flex-display: inline-flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-1-5x); --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style="width: 20px; height: 20px; border-radius: 100%; background-color: rgb(204, 204, 204);"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="label"
|
|
||||||
id="_r_1_"
|
|
||||||
>
|
|
||||||
Pill
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,8 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { Pill } from "./Pill";
|
|
||||||
@@ -1,34 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.pillInput {
|
|
||||||
background-color: var(--cpd-color-bg-subtle-secondary);
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: var(--cpd-space-2x) var(--cpd-space-3x) var(--cpd-space-2x) var(--cpd-space-3x);
|
|
||||||
/* To match pill height in order to avoid the PillInput to grow when a pill is inserted */
|
|
||||||
min-height: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pillInput:has(.input:focus) {
|
|
||||||
outline: var(--cpd-border-width-1) solid var(--cpd-color-gray-1400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
all: unset;
|
|
||||||
width: 100%;
|
|
||||||
flex: 1;
|
|
||||||
color: var(--cpd-color-text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input::placeholder {
|
|
||||||
color: var(--cpd-color-text-secondary);
|
|
||||||
font: var(--cpd-font-body-md-regular);
|
|
||||||
}
|
|
||||||
|
|
||||||
.largerInput {
|
|
||||||
padding: var(--cpd-space-2x) 0;
|
|
||||||
}
|
|
||||||
@@ -1,38 +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 React from "react";
|
|
||||||
import { fn } from "storybook/test";
|
|
||||||
|
|
||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
||||||
import { PillInput } from "./PillInput";
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
title: "PillInput/PillInput",
|
|
||||||
component: PillInput,
|
|
||||||
tags: ["autodocs"],
|
|
||||||
args: {
|
|
||||||
children: (
|
|
||||||
<>
|
|
||||||
<div style={{ minWidth: 162, height: 28, backgroundColor: "#ccc", borderRadius: "99px" }} />
|
|
||||||
<div style={{ minWidth: 162, height: 28, backgroundColor: "#ccc", borderRadius: "99px" }} />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
onChange: fn(),
|
|
||||||
onRemoveChildren: fn(),
|
|
||||||
inputProps: {
|
|
||||||
"placeholder": "Type something...",
|
|
||||||
"aria-label": "pill input",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} satisfies Meta<typeof PillInput>;
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof meta>;
|
|
||||||
|
|
||||||
export const Default: Story = {};
|
|
||||||
export const NoChild: Story = { args: { children: undefined } };
|
|
||||||
@@ -1,43 +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 { render, screen } from "jest-matrix-react";
|
|
||||||
import React from "react";
|
|
||||||
import { composeStories } from "@storybook/react-vite";
|
|
||||||
import userEvent from "@testing-library/user-event";
|
|
||||||
|
|
||||||
import * as stories from "./PillInput.stories";
|
|
||||||
import { PillInput } from "./PillInput";
|
|
||||||
|
|
||||||
const { Default, NoChild } = composeStories(stories);
|
|
||||||
|
|
||||||
describe("PillInput", () => {
|
|
||||||
it("renders the pill input", () => {
|
|
||||||
const { container } = render(<Default />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders only the input without children", () => {
|
|
||||||
const { container } = render(<NoChild />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calls onRemoveChildren when backspace is pressed and input is empty", async () => {
|
|
||||||
const user = userEvent.setup();
|
|
||||||
const mockOnRemoveChildren = jest.fn();
|
|
||||||
|
|
||||||
render(<PillInput onRemoveChildren={mockOnRemoveChildren} />);
|
|
||||||
|
|
||||||
const input = screen.getByRole("textbox");
|
|
||||||
|
|
||||||
// Focus the input and press backspace (input should be empty by default)
|
|
||||||
await user.click(input);
|
|
||||||
await user.keyboard("{Backspace}");
|
|
||||||
|
|
||||||
expect(mockOnRemoveChildren).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,96 +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 React, {
|
|
||||||
type PropsWithChildren,
|
|
||||||
type JSX,
|
|
||||||
useRef,
|
|
||||||
type KeyboardEventHandler,
|
|
||||||
type HTMLAttributes,
|
|
||||||
type HTMLProps,
|
|
||||||
Children,
|
|
||||||
} from "react";
|
|
||||||
import classNames from "classnames";
|
|
||||||
import { omit } from "lodash";
|
|
||||||
import { useMergeRefs } from "react-merge-refs";
|
|
||||||
|
|
||||||
import styles from "./PillInput.module.css";
|
|
||||||
import { Flex } from "../../utils/Flex";
|
|
||||||
|
|
||||||
export interface PillInputProps extends HTMLAttributes<HTMLDivElement> {
|
|
||||||
/**
|
|
||||||
* Callback for when the user presses backspace on an empty input.
|
|
||||||
*/
|
|
||||||
onRemoveChildren?: KeyboardEventHandler;
|
|
||||||
/**
|
|
||||||
* Props to pass to the input element.
|
|
||||||
*/
|
|
||||||
inputProps?: HTMLProps<HTMLInputElement> & { "data-testid"?: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An input component that can contain multiple child elements and an input field.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```tsx
|
|
||||||
* <PillInput>
|
|
||||||
* <div>Child 1</div>
|
|
||||||
* <div>Child 2</div>
|
|
||||||
* </PillInput>
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function PillInput({
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
onRemoveChildren,
|
|
||||||
inputProps,
|
|
||||||
...props
|
|
||||||
}: PropsWithChildren<PillInputProps>): JSX.Element {
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const inputAttributes = omit(inputProps, ["onKeyDown", "ref"]);
|
|
||||||
const ref = useMergeRefs([inputRef, inputProps?.ref]);
|
|
||||||
|
|
||||||
const hasChildren = Children.toArray(children).length > 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
{...props}
|
|
||||||
gap="var(--cpd-space-1x)"
|
|
||||||
direction="column"
|
|
||||||
className={classNames(styles.pillInput, className)}
|
|
||||||
onClick={(evt) => {
|
|
||||||
evt.preventDefault();
|
|
||||||
evt.stopPropagation();
|
|
||||||
inputRef.current?.focus();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{hasChildren && (
|
|
||||||
<Flex gap="var(--cpd-space-1x)" wrap="wrap" align="center">
|
|
||||||
{children}
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
<input
|
|
||||||
ref={ref}
|
|
||||||
autoComplete="off"
|
|
||||||
className={classNames(styles.input, { [styles.largerInput]: hasChildren })}
|
|
||||||
onKeyDown={(evt) => {
|
|
||||||
const value = evt.currentTarget.value.trim();
|
|
||||||
|
|
||||||
// If the input is empty and the user presses backspace, we call the onRemoveChildren handler
|
|
||||||
if (evt.key === "Backspace" && !value) {
|
|
||||||
evt.preventDefault();
|
|
||||||
onRemoveChildren?.(evt);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
inputProps?.onKeyDown?.(evt);
|
|
||||||
}}
|
|
||||||
{...inputAttributes}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`PillInput renders only the input without children 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="flex pillInput"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
aria-label="pill input"
|
|
||||||
autocomplete="off"
|
|
||||||
class="input"
|
|
||||||
placeholder="Type something..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`PillInput renders the pill input 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="flex pillInput"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: wrap;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style="min-width: 162px; height: 28px; background-color: rgb(204, 204, 204); border-radius: 99px;"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
style="min-width: 162px; height: 28px; background-color: rgb(204, 204, 204); border-radius: 99px;"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
aria-label="pill input"
|
|
||||||
autocomplete="off"
|
|
||||||
class="input largerInput"
|
|
||||||
placeholder="Type something..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,8 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { PillInput } from "./PillInput";
|
|
||||||
@@ -1,76 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.richItem {
|
|
||||||
/* Remove browser button style */
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
padding: var(--cpd-space-2x) var(--cpd-space-4x) var(--cpd-space-2x) var(--cpd-space-4x);
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: start;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
column-gap: var(--cpd-space-3x);
|
|
||||||
grid-template-columns: max-content 1fr max-content;
|
|
||||||
grid-template-areas:
|
|
||||||
"avatar title time"
|
|
||||||
"avatar description time";
|
|
||||||
}
|
|
||||||
|
|
||||||
.richItem:hover,
|
|
||||||
.richItem:focus {
|
|
||||||
background-color: var(--cpd-color-bg-subtle-secondary);
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.richItem:not(:last-child) {
|
|
||||||
border-bottom: var(--cpd-border-width-1) solid var(--cpd-color-gray-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
grid-area: avatar;
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
grid-area: title;
|
|
||||||
font: var(--cpd-font-body-sm-semibold);
|
|
||||||
color: var(--cpd-color-text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
grid-area: description;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timestamp {
|
|
||||||
grid-area: time;
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title,
|
|
||||||
.description {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description,
|
|
||||||
.timestamp {
|
|
||||||
font: var(--cpd-font-body-sm-regular);
|
|
||||||
color: var(--cpd-color-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkmark {
|
|
||||||
grid-area: avatar;
|
|
||||||
align-self: center;
|
|
||||||
background-color: var(--cpd-color-icon-accent-primary);
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
border-radius: 100%;
|
|
||||||
}
|
|
||||||
@@ -1,64 +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 React from "react";
|
|
||||||
import { fn } from "storybook/test";
|
|
||||||
|
|
||||||
import { RichItem } from "./RichItem";
|
|
||||||
import type { Meta, StoryFn } from "@storybook/react-vite";
|
|
||||||
|
|
||||||
const currentTimestamp = new Date("2025-03-09T12:00:00Z").getTime();
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: "RichList/RichItem",
|
|
||||||
component: RichItem,
|
|
||||||
tags: ["autodocs"],
|
|
||||||
args: {
|
|
||||||
avatar: <div style={{ width: 32, height: 32, backgroundColor: "#ccc", borderRadius: "50%" }} />,
|
|
||||||
title: "Rich Item Title",
|
|
||||||
description: "This is a description of the rich item.",
|
|
||||||
timestamp: currentTimestamp,
|
|
||||||
onClick: fn(),
|
|
||||||
},
|
|
||||||
beforeEach: () => {
|
|
||||||
Date.now = () => new Date("2025-08-01T12:00:00Z").getTime();
|
|
||||||
},
|
|
||||||
parameters: {
|
|
||||||
a11y: {
|
|
||||||
context: "button",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as Meta<typeof RichItem>;
|
|
||||||
|
|
||||||
const Template: StoryFn<typeof RichItem> = (args) => (
|
|
||||||
<ul role="listbox" style={{ all: "unset", listStyle: "none" }}>
|
|
||||||
<RichItem {...args} />
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Default = Template.bind({});
|
|
||||||
|
|
||||||
export const Selected = Template.bind({});
|
|
||||||
Selected.args = {
|
|
||||||
selected: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithoutTimestamp = Template.bind({});
|
|
||||||
WithoutTimestamp.args = {
|
|
||||||
timestamp: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Hover = Template.bind({});
|
|
||||||
Hover.parameters = { pseudo: { hover: true } };
|
|
||||||
|
|
||||||
const TemplateSeparator: StoryFn<typeof RichItem> = (args) => (
|
|
||||||
<ul role="listbox" style={{ all: "unset", listStyle: "none" }}>
|
|
||||||
<RichItem {...args} />
|
|
||||||
<RichItem {...args} />
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
export const Separator = TemplateSeparator.bind({});
|
|
||||||
@@ -1,35 +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 { composeStories } from "@storybook/react-vite";
|
|
||||||
import { render } from "jest-matrix-react";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import * as stories from "./RichItem.stories";
|
|
||||||
|
|
||||||
const { Default, Selected, WithoutTimestamp } = composeStories(stories);
|
|
||||||
|
|
||||||
describe("RichItem", () => {
|
|
||||||
beforeAll(() => {
|
|
||||||
jest.useFakeTimers().setSystemTime(new Date("2025-08-01T12:00:00Z"));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders the item in default state", () => {
|
|
||||||
const { container } = render(<Default />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders the item in selected state", () => {
|
|
||||||
const { container } = render(<Selected />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders the item without timestamp", () => {
|
|
||||||
const { container } = render(<WithoutTimestamp />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,96 +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 React, { type HTMLAttributes, type JSX, memo } from "react";
|
|
||||||
import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
|
|
||||||
|
|
||||||
import styles from "./RichItem.module.css";
|
|
||||||
import { humanizeTime } from "../../utils/humanize";
|
|
||||||
import { Flex } from "../../utils/Flex";
|
|
||||||
|
|
||||||
export interface RichItemProps extends HTMLAttributes<HTMLLIElement> {
|
|
||||||
/**
|
|
||||||
* Avatar to display at the start of the item
|
|
||||||
*/
|
|
||||||
avatar: React.ReactNode;
|
|
||||||
/**
|
|
||||||
* Title to display at the top of the item
|
|
||||||
*/
|
|
||||||
title: string;
|
|
||||||
/**
|
|
||||||
* Description to display below the title
|
|
||||||
*/
|
|
||||||
description: string;
|
|
||||||
/**
|
|
||||||
* Timestamp to display at the end of the item
|
|
||||||
* The value is humanized (e.g. "5 minutes ago")
|
|
||||||
*/
|
|
||||||
timestamp?: number;
|
|
||||||
/**
|
|
||||||
* Whether the item is selected
|
|
||||||
* This will replace the avatar with a checkmark
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
selected?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A rich item to display in a list, with an avatar, title, description and optional timestamp.
|
|
||||||
* If selected, the avatar is replaced with a checkmark.
|
|
||||||
* A separator is added between items in a list.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```tsx
|
|
||||||
* <RichItem
|
|
||||||
* avatar={<AvatarComponent />}
|
|
||||||
* title="Rich Item Title"
|
|
||||||
* description="This is a description of the rich item."
|
|
||||||
* timestamp={Date.now() - 5 * 60 * 1000} // 5 minutes ago
|
|
||||||
* selected={true}
|
|
||||||
* onClick={() => console.log("Item clicked")}
|
|
||||||
* />
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const RichItem = memo(function RichItem({
|
|
||||||
avatar,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
timestamp,
|
|
||||||
selected,
|
|
||||||
...props
|
|
||||||
}: RichItemProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
className={styles.richItem}
|
|
||||||
role="option"
|
|
||||||
tabIndex={-1}
|
|
||||||
aria-selected={selected}
|
|
||||||
aria-label={title}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{selected ? <Checkmark /> : <Flex className={styles.avatar}>{avatar}</Flex>}
|
|
||||||
<span className={styles.title}>{title}</span>
|
|
||||||
<span className={styles.description}>{description}</span>
|
|
||||||
{timestamp && (
|
|
||||||
<span role="timer" className={styles.timestamp}>
|
|
||||||
{humanizeTime(timestamp)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A checkmark icon inside a circle, used to indicate selection.
|
|
||||||
*/
|
|
||||||
function Checkmark(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Flex align="center" justify="center" aria-hidden="true" className={styles.checkmark}>
|
|
||||||
<CheckIcon width="24px" height="24px" color="var(--cpd-color-icon-on-solid-primary)" />
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`RichItem renders the item in default state 1`] = `
|
|
||||||
<div>
|
|
||||||
<ul
|
|
||||||
role="listbox"
|
|
||||||
style="all: unset; list-style: none;"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
aria-label="Rich Item Title"
|
|
||||||
class="richItem"
|
|
||||||
role="option"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex avatar"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style="width: 32px; height: 32px; background-color: rgb(204, 204, 204); border-radius: 50%;"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="title"
|
|
||||||
>
|
|
||||||
Rich Item Title
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="description"
|
|
||||||
>
|
|
||||||
This is a description of the rich item.
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="timestamp"
|
|
||||||
role="timer"
|
|
||||||
>
|
|
||||||
145 days ago
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`RichItem renders the item in selected state 1`] = `
|
|
||||||
<div>
|
|
||||||
<ul
|
|
||||||
role="listbox"
|
|
||||||
style="all: unset; list-style: none;"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
aria-label="Rich Item Title"
|
|
||||||
aria-selected="true"
|
|
||||||
class="richItem"
|
|
||||||
role="option"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-hidden="true"
|
|
||||||
class="flex checkmark"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
color="var(--cpd-color-icon-on-solid-primary)"
|
|
||||||
fill="currentColor"
|
|
||||||
height="24px"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="24px"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="title"
|
|
||||||
>
|
|
||||||
Rich Item Title
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="description"
|
|
||||||
>
|
|
||||||
This is a description of the rich item.
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="timestamp"
|
|
||||||
role="timer"
|
|
||||||
>
|
|
||||||
145 days ago
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`RichItem renders the item without timestamp 1`] = `
|
|
||||||
<div>
|
|
||||||
<ul
|
|
||||||
role="listbox"
|
|
||||||
style="all: unset; list-style: none;"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
aria-label="Rich Item Title"
|
|
||||||
class="richItem"
|
|
||||||
role="option"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex avatar"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style="width: 32px; height: 32px; background-color: rgb(204, 204, 204); border-radius: 50%;"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="title"
|
|
||||||
>
|
|
||||||
Rich Item Title
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="description"
|
|
||||||
>
|
|
||||||
This is a description of the rich item.
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,8 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { RichItem } from "./RichItem";
|
|
||||||
@@ -1,30 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.richList {
|
|
||||||
height: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font: var(--cpd-font-body-sm-semibold);
|
|
||||||
color: var(--cpd-color-text-secondary);
|
|
||||||
padding: var(--cpd-space-2x) var(--cpd-space-4x) var(--cpd-space-2x) var(--cpd-space-4x);
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
width: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
/* remove browser default ul padding/margin */
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty {
|
|
||||||
margin-left: var(--cpd-space-6x);
|
|
||||||
font: var(--cpd-font-body-sm-regular);
|
|
||||||
color: var(--cpd-color-text-secondary);
|
|
||||||
}
|
|
||||||
@@ -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 React from "react";
|
|
||||||
|
|
||||||
import { RichList } from "./RichList";
|
|
||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
||||||
import { RichItem } from "../RichItem";
|
|
||||||
|
|
||||||
const avatar = <div style={{ width: 32, height: 32, backgroundColor: "#ccc", borderRadius: "50%" }} />;
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
title: "RichList/RichList",
|
|
||||||
component: RichList,
|
|
||||||
tags: ["autodocs"],
|
|
||||||
decorators: [
|
|
||||||
(Story) => (
|
|
||||||
<div style={{ height: "220px", overflow: "hidden" }}>
|
|
||||||
<Story />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
],
|
|
||||||
args: {
|
|
||||||
title: "Rich List Title",
|
|
||||||
children: (
|
|
||||||
<>
|
|
||||||
<RichItem avatar={avatar} title="First Item" description="description" />
|
|
||||||
<RichItem selected={true} avatar={avatar} title="Second Item" description="description" />
|
|
||||||
<RichItem avatar={avatar} title="Third Item" description="description" />
|
|
||||||
<RichItem avatar={avatar} title="Fourth Item" description="description" />
|
|
||||||
<RichItem avatar={avatar} title="Fifth Item" description="description" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
} satisfies Meta<typeof RichList>;
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof meta>;
|
|
||||||
|
|
||||||
export const Default: Story = {};
|
|
||||||
export const Empty: Story = {
|
|
||||||
args: {
|
|
||||||
isEmpty: true,
|
|
||||||
children: "No items available",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,26 +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 { composeStories } from "@storybook/react-vite";
|
|
||||||
import { render } from "jest-matrix-react";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import * as stories from "./RichList.stories";
|
|
||||||
|
|
||||||
const { Default, Empty } = composeStories(stories);
|
|
||||||
|
|
||||||
describe("RichItem", () => {
|
|
||||||
it("renders the list", () => {
|
|
||||||
const { container } = render(<Default />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders the list with isEmpty=true", () => {
|
|
||||||
const { container } = render(<Empty />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,80 +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 React, { type HTMLProps, type JSX, type PropsWithChildren, useId } from "react";
|
|
||||||
import classNames from "classnames";
|
|
||||||
|
|
||||||
import styles from "./RichList.module.css";
|
|
||||||
import { Flex } from "../../utils/Flex";
|
|
||||||
import { useListKeyboardNavigation } from "../../hooks/useListKeyboardNavigation";
|
|
||||||
|
|
||||||
export interface RichListProps extends HTMLProps<HTMLDivElement> {
|
|
||||||
/**
|
|
||||||
* Title to display at the top of the list
|
|
||||||
*/
|
|
||||||
title: string;
|
|
||||||
/**
|
|
||||||
* Attributes to pass to the title element
|
|
||||||
* This can be used to set accessibility attributes like `aria-level` or `role`
|
|
||||||
* @example
|
|
||||||
* ```tsx
|
|
||||||
* <RichList title="My List" titleAttributes={{ role: "heading", "aria-level": 2 }}>
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
titleAttributes?: HTMLProps<HTMLSpanElement>;
|
|
||||||
/**
|
|
||||||
* Indicates if the list should show an empty state.
|
|
||||||
* The list renders its children in a span instead of an ul.
|
|
||||||
*/
|
|
||||||
isEmpty?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list component with a title and children.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```tsx
|
|
||||||
* <RichList title="My List">
|
|
||||||
* <RichItem ... />
|
|
||||||
* <RichItem ... />
|
|
||||||
* </RichList>
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function RichList({
|
|
||||||
children,
|
|
||||||
title,
|
|
||||||
className,
|
|
||||||
titleAttributes,
|
|
||||||
isEmpty = false,
|
|
||||||
...props
|
|
||||||
}: PropsWithChildren<RichListProps>): JSX.Element {
|
|
||||||
const id = useId();
|
|
||||||
const { listRef, onKeyDown, onFocus } = useListKeyboardNavigation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex className={classNames(styles.richList, className)} direction="column" {...props}>
|
|
||||||
<span id={id} className={styles.title} {...titleAttributes}>
|
|
||||||
{title}
|
|
||||||
</span>
|
|
||||||
{isEmpty ? (
|
|
||||||
<span className={styles.empty}>{children}</span>
|
|
||||||
) : (
|
|
||||||
<ul
|
|
||||||
ref={listRef}
|
|
||||||
role="listbox"
|
|
||||||
className={styles.content}
|
|
||||||
aria-labelledby={id}
|
|
||||||
tabIndex={0}
|
|
||||||
onKeyDown={onKeyDown}
|
|
||||||
onFocus={onFocus}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`RichItem renders the list 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
style="height: 220px; overflow: hidden;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex richList"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="title"
|
|
||||||
id="_r_0_"
|
|
||||||
>
|
|
||||||
Rich List Title
|
|
||||||
</span>
|
|
||||||
<ul
|
|
||||||
aria-labelledby="_r_0_"
|
|
||||||
class="content"
|
|
||||||
role="listbox"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
aria-label="First Item"
|
|
||||||
class="richItem"
|
|
||||||
role="option"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex avatar"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style="width: 32px; height: 32px; background-color: rgb(204, 204, 204); border-radius: 50%;"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="title"
|
|
||||||
>
|
|
||||||
First Item
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="description"
|
|
||||||
>
|
|
||||||
description
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
aria-label="Second Item"
|
|
||||||
aria-selected="true"
|
|
||||||
class="richItem"
|
|
||||||
role="option"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
aria-hidden="true"
|
|
||||||
class="flex checkmark"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
color="var(--cpd-color-icon-on-solid-primary)"
|
|
||||||
fill="currentColor"
|
|
||||||
height="24px"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width="24px"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="title"
|
|
||||||
>
|
|
||||||
Second Item
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="description"
|
|
||||||
>
|
|
||||||
description
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
aria-label="Third Item"
|
|
||||||
class="richItem"
|
|
||||||
role="option"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex avatar"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style="width: 32px; height: 32px; background-color: rgb(204, 204, 204); border-radius: 50%;"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="title"
|
|
||||||
>
|
|
||||||
Third Item
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="description"
|
|
||||||
>
|
|
||||||
description
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
aria-label="Fourth Item"
|
|
||||||
class="richItem"
|
|
||||||
role="option"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex avatar"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style="width: 32px; height: 32px; background-color: rgb(204, 204, 204); border-radius: 50%;"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="title"
|
|
||||||
>
|
|
||||||
Fourth Item
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="description"
|
|
||||||
>
|
|
||||||
description
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
aria-label="Fifth Item"
|
|
||||||
class="richItem"
|
|
||||||
role="option"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex avatar"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style="width: 32px; height: 32px; background-color: rgb(204, 204, 204); border-radius: 50%;"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="title"
|
|
||||||
>
|
|
||||||
Fifth Item
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="description"
|
|
||||||
>
|
|
||||||
description
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`RichItem renders the list with isEmpty=true 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
style="height: 220px; overflow: hidden;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex richList"
|
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: column; --mx-flex-align: start; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="title"
|
|
||||||
id="_r_1_"
|
|
||||||
>
|
|
||||||
Rich List Title
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="empty"
|
|
||||||
>
|
|
||||||
No items available
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||