Compare commits

..

2 Commits

Author SHA1 Message Date
Andy Balaam
95974b53cb Update strings for the verification dialog
This completes the Element Web part of
https://github.com/element-hq/element-meta/issues/2898
2025-09-26 10:40:17 +01:00
Andy Balaam
093a708b44 Change 'Verify Session' to 'Start Verification' 2025-09-26 10:40:17 +01:00
1381 changed files with 22179 additions and 47532 deletions

View File

@@ -1,16 +1,13 @@
/*
Copyright 2025 Element Creations 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.
*/
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"],
tsconfigRootDir: __dirname,
}, },
env: { env: {
browser: true, browser: true,
@@ -168,10 +165,6 @@ module.exports = {
group: ["@vector-im/compound-design-tokens/icons/*"], group: ["@vector-im/compound-design-tokens/icons/*"],
message: "Please use @vector-im/compound-design-tokens/assets/web/icons/* instead", message: "Please use @vector-im/compound-design-tokens/assets/web/icons/* instead",
}, },
{
group: ["**/packages/shared-components/**", "../packages/shared-components/**"],
message: "Please use @element-hq/web-shared-components",
},
], ],
}, },
], ],

3
.github/labels.yml vendored
View File

@@ -279,6 +279,3 @@
- name: "Z-Flaky-Test-Disabled" - name: "Z-Flaky-Test-Disabled"
description: "The flaking test has been disabled" description: "The flaking test has been disabled"
color: "ededed" color: "ededed"
- name: "Z-Skip-Coverage"
description: "Skip SonarQube coverage for this PR"
color: "ededed"

View File

@@ -42,9 +42,9 @@ jobs:
run: run:
shell: bash shell: bash
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
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
@@ -66,7 +66,7 @@ jobs:
run: VERSION=$(scripts/get-version-from-git.sh) yarn build run: VERSION=$(scripts/get-version-from-git.sh) 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

View File

@@ -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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- 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

View File

@@ -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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
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

View File

@@ -34,7 +34,7 @@ jobs:
env: env:
SITE: ${{ inputs.site || 'staging.element.io' }} SITE: ${{ inputs.site || 'staging.element.io' }}
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- name: Load GPG key - name: Load GPG key
run: | run: |

View File

@@ -20,7 +20,7 @@ jobs:
env: env:
TEST_TAG: vectorim/element-web:test TEST_TAG: vectorim/element-web:test
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
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
@@ -29,7 +29,7 @@ jobs:
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3 uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
@@ -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
@@ -96,7 +96,7 @@ jobs:
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
with: with:
images: | images: |
@@ -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@28959ce8df70de7be546dd1250a005dd32156697 # 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

View File

@@ -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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with: with:
path: element-web path: element-web
- name: Fetch matrix-js-sdk - name: Fetch matrix-js-sdk
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
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@395ad3262231945c25e8478fd5baf05154b1d79f # v6 - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with: with:
cache: "yarn" cache: "yarn"
cache-dependency-path: element-web/yarn.lock cache-dependency-path: element-web/yarn.lock
@@ -43,13 +43,13 @@ jobs:
working-directory: element-web working-directory: element-web
run: | run: |
yarn install --frozen-lockfile yarn install --frozen-lockfile
yarn node ./scripts/gen-workflow-mermaid.ts ../element-desktop ../element-web ../matrix-js-sdk > docs/automations.md yarn ts-node ./scripts/gen-workflow-mermaid.ts ../element-desktop ../element-web ../matrix-js-sdk > docs/automations.md
echo "- [Automations](automations.md)" >> docs/SUMMARY.md echo "- [Automations](automations.md)" >> docs/SUMMARY.md
- name: Setup mdBook - name: Setup mdBook
uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2 uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2
with: with:
mdbook-version: "0.5.1" mdbook-version: "0.4.10"
- name: Install mdbook extensions - name: Install mdbook extensions
run: cargo install mdbook-combiner mdbook-mermaid run: cargo install mdbook-combiner mdbook-mermaid

View File

@@ -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@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
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 }}

View File

@@ -50,11 +50,11 @@ 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with: with:
repository: element-hq/element-web repository: element-hq/element-web
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with: with:
cache: "yarn" cache: "yarn"
node-version: "lts/*" node-version: "lts/*"
@@ -74,7 +74,7 @@ jobs:
run: VERSION=$(scripts/get-version-from-git.sh) yarn build run: VERSION=$(scripts/get-version-from-git.sh) 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
@@ -122,18 +122,18 @@ jobs:
- runAllTests: false - runAllTests: false
project: Pinecone project: Pinecone
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
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@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
with: with:
name: webapp name: webapp
path: webapp path: webapp
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with: with:
cache: "yarn" cache: "yarn"
cache-dependency-path: yarn.lock cache-dependency-path: yarn.lock
@@ -147,7 +147,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@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
id: playwright-cache id: playwright-cache
with: with:
path: ~/.cache/ms-playwright path: ~/.cache/ms-playwright
@@ -172,7 +172,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 +194,13 @@ jobs:
if: always() if: always()
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
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@395ad3262231945c25e8478fd5baf05154b1d79f # v6 - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
if: inputs.skip != true if: inputs.skip != true
with: with:
cache: "yarn" cache: "yarn"
@@ -212,7 +212,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@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
with: with:
pattern: all-blob-reports-* pattern: all-blob-reports-*
path: all-blob-reports path: all-blob-reports
@@ -228,7 +228,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

View File

@@ -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@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
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 }}

View File

@@ -10,7 +10,7 @@ jobs:
permissions: permissions:
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- name: Update synapse image - name: Update synapse image
run: | run: |
@@ -32,7 +32,7 @@ jobs:
- name: Create Pull Request - name: Create Pull Request
id: cpr id: cpr
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7 uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7
with: with:
token: ${{ secrets.ELEMENT_BOT_TOKEN }} token: ${{ secrets.ELEMENT_BOT_TOKEN }}
branch: actions/playwright-image-updates branch: actions/playwright-image-updates

View File

@@ -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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
if: inputs.matrix-js-sdk if: inputs.matrix-js-sdk
with: with:
repository: matrix-org/matrix-js-sdk repository: matrix-org/matrix-js-sdk

View File

@@ -1,40 +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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: 🔧 Set up node environment
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
cache: "yarn"
node-version-file: ".node-version"
registry-url: "https://registry.npmjs.org"
# Ensure npm 11.5.1 or later is installed
- name: Update npm
run: npm install -g npm@latest
# Need to setup element web too as it needs the translations
- name: 🛠️ Setup EW
run: yarn install --pure-lockfile
- 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 --tag test --provenance

View File

@@ -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@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
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 }}

View File

@@ -21,46 +21,50 @@ jobs:
issues: read issues: read
pull-requests: read pull-requests: read
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with: with:
persist-credentials: false persist-credentials: false
repository: element-hq/element-web repository: element-hq/element-web
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with: with:
cache: "yarn" cache: "yarn"
node-version: "lts/*" node-version: "lts/*"
- name: Install element web dependencies
run: yarn install --frozen-lockfile
- 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@0400d5f644dc74513175e3cd8d07132dd4860809 # 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
# 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.
run: "yarn test:storybook:ci"
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

View File

@@ -22,9 +22,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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with: with:
cache: "yarn" cache: "yarn"
node-version: "lts/*" node-version: "lts/*"
@@ -35,12 +35,6 @@ jobs:
- name: Typecheck - name: Typecheck
run: "yarn run lint:types" run: "yarn run lint:types"
- 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
@@ -63,7 +57,7 @@ jobs:
name: "Rethemendex Check" name: "Rethemendex Check"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- run: ./res/css/rethemendex.sh - run: ./res/css/rethemendex.sh
@@ -73,9 +67,9 @@ jobs:
name: "ESLint" name: "ESLint"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with: with:
cache: "yarn" cache: "yarn"
node-version: "lts/*" node-version: "lts/*"
@@ -87,19 +81,13 @@ jobs:
- name: Run Linter - name: Run Linter
run: "yarn run lint:js" run: "yarn run lint:js"
- 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with: with:
cache: "yarn" cache: "yarn"
node-version: "lts/*" node-version: "lts/*"
@@ -115,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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with: with:
cache: "yarn" cache: "yarn"
node-version: "lts/*" node-version: "lts/*"
@@ -133,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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with: with:
cache: "yarn" cache: "yarn"
node-version: "lts/*" node-version: "lts/*"

View File

@@ -29,8 +29,8 @@ env:
permissions: {} permissions: {}
jobs: jobs:
jest_ew: jest:
name: Jest (Element Web) name: Jest
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
strategy: strategy:
fail-fast: false fail-fast: false
@@ -39,12 +39,12 @@ jobs:
runner: [1, 2] runner: [1, 2]
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
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@395ad3262231945c25e8478fd5baf05154b1d79f # v6 uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
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@0400d5f644dc74513175e3cd8d07132dd4860809 # 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: |
@@ -93,18 +93,18 @@ jobs:
complete: complete:
name: jest-tests name: jest-tests
needs: jest_ew needs: jest
if: always() if: always()
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
permissions: permissions:
statuses: write statuses: write
steps: steps:
- if: needs.jest_ew.result != 'skipped' && needs.jest_ew.result != 'success' - if: needs.jest.result != 'skipped' && needs.jest.result != 'success'
run: exit 1 run: exit 1
- 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
@@ -112,56 +112,3 @@ jobs:
context: SonarCloud Code Analysis context: SonarCloud Code Analysis
sha: ${{ github.sha }} sha: ${{ github.sha }}
target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
jest_sc:
name: Jest (Shared Components)
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }}
- name: Yarn cache
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
node-version: "lts/*"
cache: "yarn"
- name: Install EW Deps
run: "yarn install"
- name: Install Shared Component Deps
working-directory: "packages/shared-components"
run: "yarn install"
- name: Jest Cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: /tmp/jest_cache
key: ${{ hashFiles('**/yarn.lock') }}
- name: Get number of CPU cores
id: cpu-cores
uses: SimenB/github-actions-cpu-cores@97ba232459a8e02ff6121db9362b09661c875ab8 # v2
- name: Run tests
working-directory: "packages/shared-components"
run: |
yarn test \
--coverage=${{ env.ENABLE_COVERAGE }} \
--ci \
--max-workers ${{ steps.cpu-cores.outputs.count }} \
--cacheDirectory /tmp/jest_cache
env:
# tell jest to use coloured output
FORCE_COLOR: true
- name: Upload Artifact
if: env.ENABLE_COVERAGE == 'true'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
with:
name: coverage-sharedcomponents
path: |
packages/shared-components/coverage
!packages/shared-components/coverage/lcov-report

View File

@@ -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@abaeca7ba4f0325d63b8de7ef943c2418d161b93 # v3.0.0 - uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2
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@abaeca7ba4f0325d63b8de7ef943c2418d161b93 # v3.0.0 - uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2
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@abaeca7ba4f0325d63b8de7ef943c2418d161b93 # v3.0.0 - uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2
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@abaeca7ba4f0325d63b8de7ef943c2418d161b93 # v3.0.0 - uses: octokit/graphql-action@8ad880e4d437783ea2ab17010324de1075228110 # v2.3.2
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:

View File

@@ -12,7 +12,7 @@ jobs:
issues: write issues: write
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10 - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10
with: with:
operations-per-run: 100 operations-per-run: 100

View File

@@ -9,9 +9,9 @@ jobs:
update: update:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with: with:
cache: "yarn" cache: "yarn"
node-version: "lts/*" node-version: "lts/*"
@@ -23,7 +23,7 @@ jobs:
run: "yarn update:jitsi" run: "yarn update:jitsi"
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7 uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7
with: with:
token: ${{ secrets.ELEMENT_BOT_TOKEN }} token: ${{ secrets.ELEMENT_BOT_TOKEN }}
branch: actions/jitsi-update branch: actions/jitsi-update

View File

@@ -26,13 +26,13 @@ jobs:
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 }}
PUBLIC_DISCUSSION_ROOM_ID: "!xUW4PpAe1CmThA3r2wI8IrgwwsK006-zqWdJCljpd10" PUBLIC_ROOM_ID: "!IemiTbwVankHTFiEoh:matrix.org"
ANNOUNCEMENT_ROOM_ID: "!ars5ndgI6IIYZXECiJ-u8YljHNzShJn3nHdB-3rYI2M" ANNOUNCEMENT_ROOM_ID: "!bijaLdadorKgNGtHdA:matrix.org"
TOKEN: ${{ secrets.BETABOT_ACCESS_TOKEN }} TOKEN: ${{ secrets.BETABOT_ACCESS_TOKEN }}
RELEASE_STATUS: "Release status: ${{ inputs.expected_status }} expected ${{ inputs.expected_date }}" RELEASE_STATUS: "Release status: ${{ inputs.expected_status }} expected ${{ inputs.expected_date }}"
with: with:
script: | script: |
const { HS_URL, TOKEN, RELEASE_STATUS, LOBBY_ROOM_ID, PUBLIC_DISCUSSION_ROOM_ID, ANNOUNCEMENT_ROOM_ID } = process.env; const { HS_URL, TOKEN, RELEASE_STATUS, LOBBY_ROOM_ID, PUBLIC_ROOM_ID, ANNOUNCEMENT_ROOM_ID } = process.env;
const repo = context.repo; const repo = context.repo;
const { data } = await github.rest.repos.getLatestRelease({ const { data } = await github.rest.repos.getLatestRelease({
@@ -71,23 +71,18 @@ jobs:
const data = await res.json(); const data = await res.json();
console.log(roomId, "got event", data); console.log(roomId, "got event", data);
if (!regex.test(data.topic)) {
core.setFailed("Topic format is incorrect for room " + roomId);
return;
}
const topic = data.topic.replace(regex, releaseTopic); const topic = data.topic.replace(regex, releaseTopic);
if (topic === data.topic) { if (topic === data.topic) {
console.log(roomId, "nothing to do"); console.log(roomId, "nothing to do");
return; return;
} }
if (data["org.matrix.msc3765.topic"]) { if (data["org.matrix.msc3765.topic"]) {
data["org.matrix.msc3765.topic"]?.["m.text"].forEach(d => { data["org.matrix.msc3765.topic"].forEach(d => {
d.body = d.body.replace(regex, releaseTopic); d.body = d.body.replace(regex, releaseTopic);
}); });
} }
if (data["m.topic"]) { if (data["m.topic"]) {
data["m.topic"]?.["m.text"].forEach(d => { data["m.topic"].forEach(d => {
d.body = d.body.replace(regex, releaseTopic); d.body = d.body.replace(regex, releaseTopic);
}); });
} }
@@ -102,18 +97,12 @@ jobs:
}); });
if (res.ok) { if (res.ok) {
const resJson = res.json(); console.log(roomId, "topic updated:", topic);
if (resJson.errcode) {
core.setFailed(`Error updating ${roomId}: ${resJson.error}`);
} else {
console.log(roomId, "topic updated:", topic);
}
} else { } else {
const errText = await res.text(); console.log(roomId, await res.text());
core.setFailed(`Error updating ${roomId}: ${errText}`);
} }
} }
await updateReleaseInTopic(LOBBY_ROOM_ID); await updateReleaseInTopic(LOBBY_ROOM_ID);
await updateReleaseInTopic(PUBLIC_DISCUSSION_ROOM_ID); await updateReleaseInTopic(PUBLIC_ROOM_ID);
await updateReleaseInTopic(ANNOUNCEMENT_ROOM_ID); await updateReleaseInTopic(ANNOUNCEMENT_ROOM_ID);

5
.gitignore vendored
View File

@@ -4,6 +4,7 @@
/key.pem /key.pem
/lib /lib
/node_modules /node_modules
/packages/
/webapp /webapp
/.npmrc /.npmrc
/*.log /*.log
@@ -33,7 +34,3 @@ electron/pub
*storybook.log *storybook.log
storybook-static storybook-static
/packages/shared-components/node_modules
/packages/shared-components/dist
/packages/shared-components/src/i18nKeys.d.ts

View File

@@ -1 +1 @@
24 22

View File

@@ -2,6 +2,7 @@
/dist /dist
/lib /lib
/node_modules /node_modules
/packages/
/webapp /webapp
/*.log /*.log
yarn.lock yarn.lock

View File

@@ -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");
/** /**

View File

@@ -11,8 +11,8 @@ 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", "@storybook/addon-a11y"],
framework: "@storybook/react-vite", framework: "@storybook/react-vite",
core: { core: {
@@ -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;

View File

@@ -1,12 +1,11 @@
import type { ArgTypes, Preview, Decorator, ReactRenderer, StrictArgs } from "@storybook/react-vite"; import type { ArgTypes, Preview, Decorator, ReactRenderer, StrictArgs } from "@storybook/react-vite";
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 { 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"; import { StoryContext } from "storybook/internal/csf";
import { I18nApi, I18nContext } from "../src";
export const globalTypes = { export const globalTypes = {
theme: { theme: {
@@ -71,17 +70,9 @@ const withTooltipProvider: Decorator = (Story) => {
); );
}; };
const withI18nProvider: Decorator = (Story) => {
return (
<I18nContext.Provider value={new I18nApi()}>
<Story />
</I18nContext.Provider>
);
};
const preview: Preview = { const preview: Preview = {
tags: ["autodocs"], tags: ["autodocs"],
decorators: [withThemeProvider, withTooltipProvider, withI18nProvider], decorators: [withThemeProvider, withTooltipProvider],
parameters: { parameters: {
options: { options: {
storySort: { storySort: {

View File

@@ -5,14 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import { waitForPageReady, TestRunnerConfig } 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/`;
const config: TestRunnerConfig = { /**
setup() { * @type {import('@storybook/test-runner').TestRunnerConfig}
*/
const config = {
setup(page) {
expect.extend({ toMatchImageSnapshot }); expect.extend({ toMatchImageSnapshot });
}, },
async postVisit(page, context) { async postVisit(page, context) {

View File

@@ -1,145 +1,3 @@
Changes in [1.12.6](https://github.com/element-hq/element-web/releases/tag/v1.12.6) (2025-12-03)
================================================================================================
This release fixes a bug where 1:1 calling was incorrectly not available if no Element Call focus was set.
## 🐛 Bug Fixes
* Add option to pick call options for voice calls. ([#31413](https://github.com/element-hq/element-web/pull/31413)).
Changes in [1.12.5](https://github.com/element-hq/element-web/releases/tag/v1.12.5) (2025-12-02)
================================================================================================
## ✨ Features
* Update Emojibase to v17 ([#31307](https://github.com/element-hq/element-web/pull/31307)). Contributed by @t3chguy.
* Adds tooltip for compose menu ([#31122](https://github.com/element-hq/element-web/pull/31122)). Contributed by @byteplow.
* Add option to hide pinned message banner in room view ([#31296](https://github.com/element-hq/element-web/pull/31296)). Contributed by @florianduros.
* update twemoji to not monochromise emoji with BLACK in their name ([#31281](https://github.com/element-hq/element-web/pull/31281)). Contributed by @ara4n.
* upgrade to twemoji 17.0.2 and fix #14695 ([#31267](https://github.com/element-hq/element-web/pull/31267)). Contributed by @ara4n.
* Add options to hide right panel in room view ([#31252](https://github.com/element-hq/element-web/pull/31252)). Contributed by @florianduros.
* Delayed event management: split endpoints, no auth ([#31183](https://github.com/element-hq/element-web/pull/31183)). Contributed by @AndrewFerr.
* Support using Element Call for voice calls in DMs ([#30817](https://github.com/element-hq/element-web/pull/30817)). Contributed by @Half-Shot.
* Improve screen reader accessibility of auth pages ([#31236](https://github.com/element-hq/element-web/pull/31236)). Contributed by @t3chguy.
* Add posthog tracking for key backup toasts ([#31195](https://github.com/element-hq/element-web/pull/31195)). Contributed by @Half-Shot.
## 🐛 Bug Fixes
* Return to using Fira Code as the default monospace font ([#31302](https://github.com/element-hq/element-web/pull/31302)). Contributed by @ara4n.
* Fix case of home screen being displayed erroneously ([#31301](https://github.com/element-hq/element-web/pull/31301)). Contributed by @langleyd.
* Fix message edition and reply when multiple rooms at displayed the same moment ([#31280](https://github.com/element-hq/element-web/pull/31280)). Contributed by @florianduros.
* Key storage out of sync: reset key backup when needed ([#31279](https://github.com/element-hq/element-web/pull/31279)). Contributed by @uhoreg.
* Fix invalid events crashing entire room rather than just their tile ([#31256](https://github.com/element-hq/element-web/pull/31256)). Contributed by @t3chguy.
* Fix expand button of space panel getting cut off at the edges ([#31259](https://github.com/element-hq/element-web/pull/31259)). Contributed by @MidhunSureshR.
* Fix pill buttons in dialogs ([#31246](https://github.com/element-hq/element-web/pull/31246)). Contributed by @dbkr.
* Fix blank sections at the top and bottom of the member list when scrolling ([#31198](https://github.com/element-hq/element-web/pull/31198)). Contributed by @langleyd.
* Fix emoji category selection with keyboard ([#31162](https://github.com/element-hq/element-web/pull/31162)). Contributed by @langleyd.
Changes in [1.12.4](https://github.com/element-hq/element-web/releases/tag/v1.12.4) (2025-11-18)
================================================================================================
## ✨ Features
* Apply aria-hidden to emoji in SAS verification ([#31204](https://github.com/element-hq/element-web/pull/31204)). Contributed by @t3chguy.
* Add options to hide header and composer of room view for the module api ([#31095](https://github.com/element-hq/element-web/pull/31095)). Contributed by @florianduros.
* Experimental Module API Additions ([#30863](https://github.com/element-hq/element-web/pull/30863)). Contributed by @dbkr.
* Change polls to use fieldset/legend markup ([#31160](https://github.com/element-hq/element-web/pull/31160)). Contributed by @langleyd.
* Use compound Button styles for Jitsi button ([#31159](https://github.com/element-hq/element-web/pull/31159)). Contributed by @Half-Shot.
* Add FocusLock to emoji picker ([#31146](https://github.com/element-hq/element-web/pull/31146)). Contributed by @langleyd.
* Move room name, avatar, and topic to IOpts. ([#30981](https://github.com/element-hq/element-web/pull/30981)). Contributed by @kaylendog.
* Add a devtool for looking at users and their devices ([#30983](https://github.com/element-hq/element-web/pull/30983)). Contributed by @uhoreg.
## 🐛 Bug Fixes
* Fix room list handling of membership changes ([#31197](https://github.com/element-hq/element-web/pull/31197)). Contributed by @t3chguy.
* Fix room list unable to be resized when displayed after a module ([#31186](https://github.com/element-hq/element-web/pull/31186)). Contributed by @florianduros.
* Inhibit keyboard highlights in dialogs when effector is not in focus ([#31181](https://github.com/element-hq/element-web/pull/31181)). Contributed by @t3chguy.
* Strip mentions from forwarded messages ([#30884](https://github.com/element-hq/element-web/pull/30884)). Contributed by @twassman.
* Don't allow pin or edit of messages with a send status ([#31158](https://github.com/element-hq/element-web/pull/31158)). Contributed by @langleyd.
* Hide room header buttons if the room hasn't been created yet. ([#31092](https://github.com/element-hq/element-web/pull/31092)). Contributed by @Half-Shot.
* Fix screen readers not indicating the emoji picker search field is focused. ([#31128](https://github.com/element-hq/element-web/pull/31128)). Contributed by @langleyd.
* Fix emoji picker highlight missing when not active element ([#31148](https://github.com/element-hq/element-web/pull/31148)). Contributed by @t3chguy.
* Add relevant aria attribute for selected emoji in the emoji picker ([#31125](https://github.com/element-hq/element-web/pull/31125)). Contributed by @t3chguy.
* Fix tooltips within context menu portals being unreliable ([#31129](https://github.com/element-hq/element-web/pull/31129)). Contributed by @t3chguy.
* Avoid excessive re-render of room list and member list ([#31131](https://github.com/element-hq/element-web/pull/31131)). Contributed by @florianduros.
* Make emoji picker height responsive. ([#31130](https://github.com/element-hq/element-web/pull/31130)). Contributed by @langleyd.
* Emoji Picker: Focused emoji does not move with the arrow keys ([#30893](https://github.com/element-hq/element-web/pull/30893)). Contributed by @langleyd.
* Fix audio player seek bar position ([#31127](https://github.com/element-hq/element-web/pull/31127)). Contributed by @florianduros.
* Add aria label to emoji picker search ([#31126](https://github.com/element-hq/element-web/pull/31126)). Contributed by @langleyd.
Changes in [1.12.3](https://github.com/element-hq/element-web/releases/tag/v1.12.3) (2025-11-04)
================================================================================================
## 🦖 Deprecations
* Remove allowVoipWithNoMedia feature flag ([#31087](https://github.com/element-hq/element-web/pull/31087)). Contributed by @Half-Shot.
## ✨ Features
* Change module API to be an instance getter ([#31025](https://github.com/element-hq/element-web/pull/31025)). Contributed by @dbkr.
## 🐛 Bug Fixes
* Show hover elements when keyboard focus is within an event tile ([#31078](https://github.com/element-hq/element-web/pull/31078)). Contributed by @t3chguy.
* Ensure toolbar navigation pattern works in MessageActionBar ([#31080](https://github.com/element-hq/element-web/pull/31080)). Contributed by @t3chguy.
* Ensure sent markers are hidden when showing thread summary. ([#31076](https://github.com/element-hq/element-web/pull/31076)). Contributed by @Half-Shot.
* Fix translation in dev mode ([#31045](https://github.com/element-hq/element-web/pull/31045)). Contributed by @florianduros.
* Fix sort order in space hierarchy ([#30975](https://github.com/element-hq/element-web/pull/30975)). Contributed by @t3chguy.
* New Room list: don't display message preview of thread ([#31043](https://github.com/element-hq/element-web/pull/31043)). Contributed by @florianduros.
* Revert "A11y: move focus to right panel when opened" ([#30999](https://github.com/element-hq/element-web/pull/30999)). Contributed by @florianduros.
* Fix highlights in messages (or search results) breaking links ([#30264](https://github.com/element-hq/element-web/pull/30264)). Contributed by @bojidar-bg.
* Add prepare script ([#31030](https://github.com/element-hq/element-web/pull/31030)). Contributed by @dbkr.
* Fix html exports by adding SDKContext ([#30987](https://github.com/element-hq/element-web/pull/30987)). Contributed by @t3chguy.
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) Changes in [1.12.0](https://github.com/element-hq/element-web/releases/tag/v1.12.0) (2025-09-23)
================================================================================================ ================================================================================================
## 🦖 Deprecations ## 🦖 Deprecations

View File

@@ -1,7 +1,7 @@
# syntax=docker.io/docker/dockerfile:1.20-labs@sha256:dbcde2ebc4abc8bb5c3c499b9c9a6876842bf5da243951cd2697f921a7aeb6a9 # syntax=docker.io/docker/dockerfile:1.18-labs@sha256:79cdc14e1c220efb546ad14a8ebc816e3277cd72d27195ced5bebdd226dd1025
# Builder # Builder
FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:5583cbe5d3347db372d9a9100eba272b548ca1f53246b9b769334bcd0eef2c26 AS builder FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:f8c398a3ad2612293e8827915c056ed0f5cc708b0f676274bb6c732e3c10f93d 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:a6bec37058b9047ece799c01d98dc6d5aa0542b6583cc69f187652f91331a752 FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:14b127ed799301a21a1798516443c675237120c76b9a738d43c5e4747de4b1c9
# Need root user to install packages & manipulate the usr directory # Need root user to install packages & manipulate the usr directory
USER root USER root

View File

@@ -4,6 +4,7 @@
title = "Element Web & Desktop" title = "Element Web & Desktop"
authors = ["New Vector Ltd.", "The Matrix.org Foundation C.I.C."] authors = ["New Vector Ltd.", "The Matrix.org Foundation C.I.C."]
language = "en" language = "en"
multilingual = false
# The directory that documentation files are stored in # The directory that documentation files are stored in
src = "docs" src = "docs"

View File

@@ -79,7 +79,7 @@ Unless otherwise specified, the following applies to all code:
11. If a variable is not receiving a value on declaration, its type must be defined. 11. If a variable is not receiving a value on declaration, its type must be defined.
```typescript ```typescript
let errorMessage: string; let errorMessage: Optional<string>;
``` ```
12. Objects can use shorthand declarations, including mixing of types. 12. Objects can use shorthand declarations, including mixing of types.

View File

@@ -18,7 +18,7 @@ This is anywhere your data or business logic comes from. If your view model is a
#### View #### View
1. Located in [`shared-components`](https://github.com/element-hq/element-web/tree/develop/packages/shared-components). Develop it in storybook! 1. Located in [`shared-components`](https://github.com/element-hq/element-web/tree/develop/src/shared-components). Develop it in storybook!
2. Views are simple react components (eg: `FooView`). 2. Views are simple react components (eg: `FooView`).
3. Views use [useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore) internally where the view model is the external store. 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: 4. Views should define the interface of the view model they expect:
@@ -35,7 +35,7 @@ This is anywhere your data or business logic comes from. If your view model is a
} }
// ViewModel is a type defining the methods needed for `useSyncExternalStore` // 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 // https://github.com/element-hq/element-web/blob/develop/src/shared-components/ViewModel.ts
type FooViewModel = ViewModel<FooViewSnapshot> & FooViewActions; type FooViewModel = ViewModel<FooViewSnapshot> & FooViewActions;
interface FooViewProps { interface FooViewProps {
@@ -54,7 +54,7 @@ This is anywhere your data or business logic comes from. If your view model is a
``` ```
5. Multiple views can share the same view model if necessary. 5. Multiple views can share the same view model if necessary.
6. A full example is available [here](https://github.com/element-hq/element-web/blob/develop/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.tsx) 6. A full example is available [here](https://github.com/element-hq/element-web/blob/develop/src/shared-components/audio/AudioPlayerView/AudioPlayerView.tsx)
#### View Model #### View Model

View File

@@ -407,7 +407,7 @@ The VoIP and Jitsi options are:
If you run your own rageshake server to collect bug reports, the following options may be of interest: If you run your own rageshake server to collect bug reports, the following options may be of interest:
1. `bug_report_endpoint_url`: URL for where to submit rageshake logs to. Rageshakes include feedback submissions and bug reports. When 1. `bug_report_endpoint_url`: URL for where to submit rageshake logs to. Rageshakes include feedback submissions and bug reports. When
not present in the config, the app will disable all rageshake functionality. Set to `https://rageshakes.element.io/api/submit` to submit not present in the config, the app will disable all rageshake functionality. Set to `https://element.io/bugreports/submit` to submit
rageshakes to us, or use your own rageshake server. rageshakes to us, or use your own rageshake server.
2. `uisi_autorageshake_app`: If a user has enabled the "automatically send debug logs on decryption errors" flag, this option will be sent 2. `uisi_autorageshake_app`: If a user has enabled the "automatically send debug logs on decryption errors" flag, this option will be sent
alongside the rageshake so the rageshake server can filter them by app name. By default, this will be `element-auto-uisi` alongside the rageshake so the rageshake server can filter them by app name. By default, this will be `element-auto-uisi`

View File

@@ -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!

View File

@@ -57,7 +57,7 @@ Then you can deploy it to your cluster with something like `kubectl apply -f my-
"https://scalar-staging.vector.im/_matrix/integrations/v1", "https://scalar-staging.vector.im/_matrix/integrations/v1",
"https://scalar-staging.vector.im/api" "https://scalar-staging.vector.im/api"
], ],
"bug_report_endpoint_url": "https://rageshakes.element.io/api/submit", "bug_report_endpoint_url": "https://element.io/bugreports/submit",
"defaultCountryCode": "GB", "defaultCountryCode": "GB",
"show_labs_settings": false, "show_labs_settings": false,
"features": { }, "features": { },

View File

@@ -17,7 +17,7 @@
"https://scalar-staging.vector.im/_matrix/integrations/v1", "https://scalar-staging.vector.im/_matrix/integrations/v1",
"https://scalar-staging.vector.im/api" "https://scalar-staging.vector.im/api"
], ],
"bug_report_endpoint_url": "https://rageshakes.element.io/api/submit", "bug_report_endpoint_url": "https://element.io/bugreports/submit",
"uisi_autorageshake_app": "element-auto-uisi", "uisi_autorageshake_app": "element-auto-uisi",
"show_labs_settings": false, "show_labs_settings": false,
"room_directory": { "room_directory": {

View File

@@ -17,7 +17,7 @@
"https://scalar-staging.vector.im/_matrix/integrations/v1", "https://scalar-staging.vector.im/_matrix/integrations/v1",
"https://scalar-staging.vector.im/api" "https://scalar-staging.vector.im/api"
], ],
"bug_report_endpoint_url": "https://rageshakes.element.io/api/submit", "bug_report_endpoint_url": "https://element.io/bugreports/submit",
"uisi_autorageshake_app": "element-auto-uisi", "uisi_autorageshake_app": "element-auto-uisi",
"show_labs_settings": true, "show_labs_settings": true,
"room_directory": { "room_directory": {

View File

@@ -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)"], 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"],
@@ -40,14 +40,10 @@ const config: Config = {
"^!!raw-loader!.*": "jest-raw-loader", "^!!raw-loader!.*": "jest-raw-loader",
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js", "recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
"^fetch-mock$": "<rootDir>/node_modules/fetch-mock", "^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
"counterpart": "<rootDir>/node_modules/counterpart",
}, },
transformIgnorePatterns: [ transformIgnorePatterns: ["/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error)).+$"],
"/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs|is-ip|ip-regex|super-regex|function-timeout|time-span|convert-hrtime|clone-regexp|is-regexp)).+$",
],
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",

View File

@@ -5,7 +5,6 @@ export default {
"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",
"src/vector/localstorage-fix.ts",
"scripts/**", "scripts/**",
"playwright/**", "playwright/**",
"test/**", "test/**",
@@ -20,8 +19,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`
@@ -42,8 +39,6 @@ export default {
"util", "util",
// Embedded into webapp // Embedded into webapp
"@element-hq/element-call-embedded", "@element-hq/element-call-embedded",
// Transitive dep of jest
"jsdom",
// Used by matrix-js-sdk, which means we have to include them as a // 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 // dependency so that // we can run `tsc` (since we import the typescript
@@ -51,13 +46,11 @@ export default {
// would with a normal library). // would with a normal library).
"@types/content-type", "@types/content-type",
"@types/sdp-transform", "@types/sdp-transform",
// Used in EW but failed because of "link:"
"@element-hq/web-shared-components",
], ],
ignoreBinaries: [ ignoreBinaries: [
// Used in scripts & workflows // Used in scripts & workflows
"jq", "jq",
"wait-on",
], ],
ignoreExportsUsedInFile: true, ignoreExportsUsedInFile: true,
} satisfies KnipConfig; } satisfies KnipConfig;

View File

@@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details.
*/ */
import * as YAML from "yaml"; import * as YAML from "yaml";
import * as fs from "node:fs"; import * as fs from "fs";
export type BuildConfig = { export type BuildConfig = {
// Dev note: make everything here optional for user safety. Invalid // Dev note: make everything here optional for user safety. Invalid

View File

@@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import * as fs from "node:fs"; import * as fs from "fs";
import * as childProcess from "node:child_process"; import * as childProcess from "child_process";
import * as semver from "semver"; import * as semver from "semver";
import { type BuildConfig } from "./BuildConfig.ts"; import { type BuildConfig } from "./BuildConfig";
// This expects to be run from ./scripts/install.ts // This expects to be run from ./scripts/install.ts

View File

@@ -5,8 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import { readBuildConfig } from "../BuildConfig.ts"; import { readBuildConfig } from "../BuildConfig";
import { installer } from "../installer.ts"; import { installer } from "../installer";
const buildConf = readBuildConfig(); const buildConf = readBuildConfig();
installer(buildConf); installer(buildConf);

View File

@@ -1,6 +1,6 @@
{ {
"name": "element-web", "name": "element-web",
"version": "1.12.6", "version": "1.12.0",
"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/src && 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",
@@ -38,16 +38,16 @@
"clean": "rimraf lib webapp", "clean": "rimraf lib webapp",
"build": "yarn clean && yarn build:genfiles && yarn build:bundle", "build": "yarn clean && yarn build:genfiles && yarn build:bundle",
"build-stats": "yarn clean && yarn build:genfiles && yarn build:bundle-stats", "build-stats": "yarn clean && yarn build:genfiles && yarn build:bundle-stats",
"build:res": "node scripts/copy-res.ts", "build:res": "ts-node scripts/copy-res.ts",
"build:genfiles": "yarn build:res && yarn build:module_system", "build:genfiles": "yarn build:res && yarn build:module_system",
"build:modernizr": "modernizr -c .modernizr.json -d src/vector/modernizr.js", "build:modernizr": "modernizr -c .modernizr.json -d src/vector/modernizr.js",
"build:bundle": "webpack --progress --mode production", "build:bundle": "webpack --progress --mode production",
"build:bundle-stats": "webpack --progress --mode production --json > webpack-stats.json", "build:bundle-stats": "webpack --progress --mode production --json > webpack-stats.json",
"build:module_system": "node module_system/scripts/install.ts", "build:module_system": "ts-node --project ./tsconfig.module_system.json module_system/scripts/install.ts",
"dist": "./scripts/package.sh", "dist": "./scripts/package.sh",
"start": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n modules,res \"yarn build:module_system\" \"yarn build:res\" && concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n res,element-js \"yarn start:res\" \"yarn start:js\"", "start": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n modules,res \"yarn build:module_system\" \"yarn build:res\" && concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n res,element-js \"yarn start:res\" \"yarn start:js\"",
"start:https": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n res,element-js \"yarn start:res\" \"yarn start:js --server-type https\"", "start:https": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n res,element-js \"yarn start:res\" \"yarn start:js --server-type https\"",
"start:res": "node scripts/copy-res.ts -w", "start:res": "ts-node scripts/copy-res.ts -w",
"start:js": "webpack serve --output-path webapp --output-filename=bundles/_dev_/[name].js --output-chunk-filename=bundles/_dev_/[name].js --mode development", "start:js": "webpack serve --output-path webapp --output-filename=bundles/_dev_/[name].js --output-chunk-filename=bundles/_dev_/[name].js --mode development",
"lint": "yarn lint:types && yarn lint:js && yarn lint:style && yarn lint:workflows", "lint": "yarn lint:types && yarn lint:js && yarn lint:style && yarn lint:workflows",
"lint:js": "eslint --max-warnings 0 src test playwright module_system && prettier --check .", "lint:js": "eslint --max-warnings 0 src test playwright module_system && prettier --check .",
@@ -65,35 +65,39 @@
"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",
"install": "yarn --cwd packages/shared-components install --frozen-lockfile", "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.1", "**/pretty-format/react-is": "19.1.1",
"@types/react": "19.2.7", "@playwright/test": "1.54.2",
"@types/react-dom": "19.2.3", "@types/react": "19.1.13",
"oidc-client-ts": "3.4.1", "@types/react-dom": "19.1.9",
"oidc-client-ts": "3.3.0",
"jwt-decode": "4.0.0", "jwt-decode": "4.0.0",
"caniuse-lite": "1.0.30001759", "caniuse-lite": "1.0.30001741",
"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.9.0", "@element-hq/element-web-module-api": "1.4.1",
"@element-hq/web-shared-components": "link:packages/shared-components", "@fontsource/inconsolata": "^5",
"@fontsource/fira-code": "^5",
"@fontsource/inter": "^5", "@fontsource/inter": "^5",
"@formatjs/intl-segmenter": "^11.5.7", "@formatjs/intl-segmenter": "^11.5.7",
"@matrix-org/analytics-events": "^0.30.0", "@matrix-org/analytics-events": "^0.29.2",
"@matrix-org/emojibase-bindings": "^1.5.0", "@matrix-org/emojibase-bindings": "^1.3.4",
"@matrix-org/react-sdk-module-api": "^2.4.0", "@matrix-org/react-sdk-module-api": "^2.4.0",
"@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",
"@vector-im/compound-design-tokens": "6.4.1", "@vector-im/compound-design-tokens": "^6.0.0",
"@vector-im/compound-web": "^8.3.1", "@vector-im/compound-web": "^8.1.2",
"@vector-im/matrix-wysiwyg": "2.40.0", "@vector-im/matrix-wysiwyg": "2.40.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",
@@ -104,40 +108,41 @@
"browserslist": "^4.23.2", "browserslist": "^4.23.2",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"commonmark": "^0.31.0", "commonmark": "^0.31.0",
"counterpart": "^0.18.6",
"css-tree": "^3.0.0", "css-tree": "^3.0.0",
"diff-dom": "^5.0.0", "diff-dom": "^5.0.0",
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",
"domutils": "^3.2.2", "domutils": "^3.2.2",
"emojibase-regex": "^17.0.0", "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",
"html-entities": "^2.0.0", "html-entities": "^2.0.0",
"html-react-parser": "^5.2.2", "html-react-parser": "^5.2.2",
"is-ip": "^5.0.0", "is-ip": "^3.1.0",
"js-xxhash": "^5.0.0", "js-xxhash": "^4.0.0",
"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",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"maplibre-gl": "^5.0.0", "maplibre-gl": "^5.0.0",
"matrix-encrypt-attachment": "^1.0.3", "matrix-encrypt-attachment": "^1.0.3",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#hs/safety-error-code", "matrix-events-sdk": "0.0.1",
"matrix-widget-api": "^1.14.0", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
"matrix-widget-api": "^1.10.0",
"memoize-one": "^6.0.0", "memoize-one": "^6.0.0",
"mime": "^4.0.4", "mime": "^4.0.4",
"oidc-client-ts": "^3.0.1", "oidc-client-ts": "^3.0.1",
"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.297.2", "posthog-js": "1.265.1",
"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",
@@ -145,7 +150,6 @@
"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-virtuoso": "^4.14.0",
@@ -179,14 +183,20 @@
"@babel/preset-react": "^7.12.10", "@babel/preset-react": "^7.12.10",
"@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.5.0", "@casualbot/jest-sonar-reporter": "2.2.7",
"@element-hq/element-call-embedded": "0.16.3", "@element-hq/element-call-embedded": "0.15.0",
"@element-hq/element-web-playwright-common": "^2.0.0", "@element-hq/element-web-playwright-common": "^1.4.6",
"@peculiar/webcrypto": "^1.4.3", "@peculiar/webcrypto": "^1.4.3",
"@playwright/test": "1.57.0", "@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": "^10.0.7", "@storybook/addon-a11y": "^9.0.18",
"@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",
@@ -202,7 +212,7 @@
"@types/express": "^5.0.0", "@types/express": "^5.0.0",
"@types/file-saver": "^2.0.3", "@types/file-saver": "^2.0.3",
"@types/glob-to-regexp": "^0.4.1", "@types/glob-to-regexp": "^0.4.1",
"@types/jest": "30.0.0", "@types/jest": "29.5.12",
"@types/jitsi-meet": "^2.0.2", "@types/jitsi-meet": "^2.0.2",
"@types/jsrsasign": "^10.5.4", "@types/jsrsasign": "^10.5.4",
"@types/katex": "^0.16.0", "@types/katex": "^0.16.0",
@@ -213,9 +223,9 @@
"@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.7", "@types/react": "19.1.13",
"@types/react-beautiful-dnd": "^13.0.0", "@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "19.2.3", "@types/react-dom": "19.1.9",
"@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/sdp-transform": "^2.4.10",
@@ -224,11 +234,11 @@
"@types/ua-parser-js": "^0.7.36", "@types/ua-parser-js": "^0.7.36",
"@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": "^30.0.0", "babel-jest": "^29.0.0",
"babel-loader": "^10.0.0", "babel-loader": "^10.0.0",
"babel-plugin-jsx-remove-data-test-id": "^3.0.0", "babel-plugin-jsx-remove-data-test-id": "^3.0.0",
"blob-polyfill": "^9.0.0", "blob-polyfill": "^9.0.0",
"chokidar": "^5.0.0", "chokidar": "^4.0.0",
"concurrently": "^9.0.0", "concurrently": "^9.0.0",
"copy-webpack-plugin": "^13.0.0", "copy-webpack-plugin": "^13.0.0",
"core-js": "^3.38.1", "core-js": "^3.38.1",
@@ -239,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",
@@ -256,10 +267,11 @@
"html-webpack-plugin": "^5.5.3", "html-webpack-plugin": "^5.5.3",
"husky": "^9.0.0", "husky": "^9.0.0",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "^30.0.0", "jest": "^29.6.2",
"jest-canvas-mock": "^2.5.2", "jest-canvas-mock": "^2.5.2",
"jest-environment-jsdom": "^30.0.0", "jest-environment-jsdom": "^29.7.0",
"jest-mock": "^30.0.0", "jest-image-snapshot": "^6.5.1",
"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",
"knip": "^5.36.2", "knip": "^5.36.2",
@@ -287,18 +299,21 @@
"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": "^10.0.7", "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",
"stylelint-value-no-unknown-custom-properties": "^6.0.1", "stylelint-value-no-unknown-custom-properties": "^6.0.1",
"terser-webpack-plugin": "^5.3.9", "terser-webpack-plugin": "^5.3.9",
"testcontainers": "^11.0.0", "testcontainers": "^11.0.0",
"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": "^5.0.0", "webpack-bundle-analyzer": "^4.8.0",
"webpack-cli": "^6.0.0", "webpack-cli": "^6.0.0",
"webpack-dev-server": "^5.0.0", "webpack-dev-server": "^5.0.0",
"webpack-retry-chunk-load-plugin": "^3.1.1", "webpack-retry-chunk-load-plugin": "^3.1.1",
@@ -311,7 +326,7 @@
"relativePaths": true "relativePaths": true
}, },
"engines": { "engines": {
"node": ">=22.18" "node": ">=20.0.0"
}, },
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
} }

View File

@@ -1,72 +0,0 @@
/*
Copyright 2025 Element Creations 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.
*/
module.exports = {
root: true,
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"],
tsconfigRootDir: __dirname,
},
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",
},
},
};

View File

@@ -1,2 +0,0 @@
dist/
i18n/i18nKeys.d.ts

View File

@@ -1,21 +0,0 @@
module.exports = {
sourceMaps: true,
presets: [
[
"@babel/preset-env",
{
include: ["@babel/plugin-transform-class-properties"],
},
],
["@babel/preset-typescript", { allowDeclareFields: true }],
"@babel/preset-react",
],
plugins: [
"@babel/plugin-proposal-export-default-from",
"@babel/plugin-transform-numeric-separator",
"@babel/plugin-transform-object-rest-spread",
"@babel/plugin-transform-optional-chaining",
"@babel/plugin-transform-nullish-coalescing-operator",
"@babel/plugin-transform-runtime",
],
};

View File

@@ -1,58 +0,0 @@
/*
Copyright 2025 Element Creations 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 { env } from "process";
import type { Config } from "jest";
const config: Config = {
testEnvironment: "jsdom",
testEnvironmentOptions: {
url: "http://localhost/",
},
testMatch: ["<rootDir>/src/**/*.test.[tj]s?(x)"],
setupFilesAfterEnv: ["<rootDir>/src/test/setupTests.ts"],
moduleNameMapper: {
// Support CSS module
"\\.(module.css)$": "identity-obj-proxy",
"\\.(css|scss|pcss)$": "<rootDir>/__mocks__/cssMock.js",
"\\.(gif|png|ttf|woff2)$": "<rootDir>/__mocks__/imageMock.js",
"\\.svg$": "<rootDir>/__mocks__/svg.js",
"\\$webapp/i18n/languages.json": "<rootDir>/../../__mocks__/languages.json",
"^react$": "<rootDir>/node_modules/react",
"^react-dom$": "<rootDir>/node_modules/react-dom",
"waveWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
"context-filter-polyfill": "<rootDir>/__mocks__/empty.js",
"workers/(.+)Factory": "<rootDir>/__mocks__/workerFactoryMock.js",
},
transformIgnorePatterns: [
"/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs|@storybook|storybook)).+$",
],
collectCoverageFrom: [
"<rootDir>/src/**/*.{js,ts,tsx}",
"<rootDir>/packages/**/*.{js,ts,tsx}",
// Coverage chokes on type definition files
"!<rootDir>/src/**/*.d.ts",
],
coverageReporters: ["text-summary", "lcov"],
testResultsProcessor: "@casualbot/jest-sonar-reporter",
prettierPath: null,
moduleDirectories: ["node_modules", "./src/test/utils"],
};
// if we're running under GHA, enable the GHA reporter
if (env["GITHUB_ACTIONS"] !== undefined) {
const reporters: Config["reporters"] = [["github-actions", { silent: false }], "summary"];
// if we're running against the develop branch, also enable the slow test reporter
if (env["GITHUB_REF"] == "refs/heads/develop") {
reporters.push("<rootDir>/../../test/slowReporter.cjs");
}
config.reporters = reporters;
}
export default config;

View File

@@ -1,98 +0,0 @@
{
"name": "@element-hq/web-shared-components",
"version": "0.0.0-test.12",
"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"
}
},
"./dist/element-web-shared-components.css": {
"require": "./dist/element-web-shared-components.css",
"import": "./dist/element-web-shared-components.css"
}
},
"types": "dist/element-web-shared-components.d.ts",
"files": [
"dist",
"src",
"LICENSE",
"README.md",
"package.json"
],
"scripts": {
"test": "jest",
"prepare": "patch-package && yarn --cwd ../.. build:res && node scripts/gatherTranslationKeys.ts && 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"
},
"resolutions": {
"playwright": "1.57.0"
},
"dependencies": {
"@element-hq/element-web-module-api": "^1.8.0",
"@vector-im/compound-design-tokens": "^6.3.0",
"classnames": "^2.5.1",
"counterpart": "^0.18.6",
"lodash": "^4.17.21",
"matrix-web-i18n": "^3.4.0",
"patch-package": "^8.0.1",
"react-merge-refs": "^3.0.2",
"temporal-polyfill": "^0.3.0"
},
"devDependencies": {
"@element-hq/element-web-playwright-common": "^2.0.0",
"@playwright/test": "1.57.0",
"@storybook/addon-a11y": "^10.0.7",
"@storybook/addon-designs": "^11.0.1",
"@storybook/addon-docs": "^10.0.7",
"@storybook/icons": "^2.0.0",
"@storybook/react-vite": "^10.0.7",
"@storybook/test-runner": "^0.24.1",
"@testing-library/dom": "^10.4.1",
"@testing-library/react": "^16.3.0",
"@types/counterpart": "^0.18.4",
"@types/jest-image-snapshot": "^6.4.0",
"@types/lodash": "^4.17.20",
"@types/react": "^19.2.2",
"concurrently": "^9.2.1",
"eslint": "8",
"eslint-plugin-matrix-org": "^3.0.0",
"eslint-plugin-storybook": "^10.0.7",
"jest": "^30.2.0",
"jest-image-snapshot": "^6.5.1",
"patch-package": "^8.0.1",
"prettier": "^3.6.2",
"storybook": "^10.0.7",
"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",
"peerDependencies": {
"@vector-im/compound-web": "^8.2.5"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -1,67 +0,0 @@
/*
Copyright 2025 Element Creations 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.
*/
// Gathers all the translation keys from element-web's en_EN.json into a TypeScript type definition file
// that exports a type `TranslationKey` which is a union of all supported translation keys.
// This prevents having to import the json file and make typescript do the work as this results in vite-dts
// generating an import to the json file in the .d.ts which doesn't work at runtime: this way, the type
// gets put into the bundle.
// XXX: It should *not* be in the 'src' directory, being a generated file, but if it isn't then the type
// bundler won't bundle the types and will leave the file as a relative import, which will break.
import * as fs from "fs";
import * as path from "path";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const i18nStringsPath = path.resolve(__dirname, "../../../src/i18n/strings/en_EN.json");
const outPath = path.resolve(__dirname, "../src/i18nKeys.d.ts");
function gatherKeys(obj: any, prefix: string[] = []): string[] {
if (typeof obj !== "object" || obj === null) return [];
let keys: string[] = [];
for (const key of Object.keys(obj)) {
const value = obj[key];
// add the path (for both leaves and intermediates as then we include plurals)
keys.push([...prefix, key].join("|"));
if (typeof value === "object" && value !== null) {
// If the value is an object, recurse
keys = keys.concat(gatherKeys(value, [...prefix, key]));
}
}
return keys;
}
function main() {
const json = JSON.parse(fs.readFileSync(i18nStringsPath, "utf8"));
const keys = gatherKeys(json);
const typeDef =
"/*\n" +
" * Copyright 2025 Element Creations Ltd.\n" +
" *\n" +
" * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial\n" +
" * Please see LICENSE files in the repository root for full details.\n" +
" */\n" +
"\n" +
"// This file is auto-generated by gatherTranslationKeys.ts\n" +
"// Do not edit manually.\n\n" +
"export type TranslationKey =\n" +
keys.map((k) => ` | \"${k}\"`).join("\n") +
";\n";
fs.mkdirSync(path.dirname(outPath), { recursive: true });
fs.writeFileSync(outPath, typeDef, "utf8");
console.log(`Wrote ${keys.length} keys to ${outPath}`);
}
if (import.meta.url.startsWith("file:")) {
const modulePath = fileURLToPath(import.meta.url);
if (process.argv[1] === modulePath) {
main();
}
}

View File

@@ -1,11 +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.
*/
.button {
border-radius: 32px !important;
background-color: var(--cpd-color-bg-subtle-primary) !important;
}

View File

@@ -1,93 +0,0 @@
/*
* Copyright (c) 2025 Element Creations 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.
*/
:root {
--cpd-color-gradient-critical-linear: linear-gradient(
180deg,
var(--cpd-color-alpha-red-500) 0%,
var(--cpd-color-alpha-red-400) 20%,
var(--cpd-color-alpha-red-300) 40%,
var(--cpd-color-alpha-red-200) 60%,
var(--cpd-color-alpha-red-100) 80%,
var(--cpd-color-transparent) 100%
);
}
.banner {
container-type: inline-size;
container-name: banner;
display: flex;
align-items: center;
justify-content: start;
gap: var(--cpd-space-3x);
padding: var(--cpd-space-4x);
border-top: 1px solid var(--cpd-color-gray-400);
white-space: nowrap;
}
.banner[data-type="success"] {
background: var(--cpd-color-gradient-subtle-linear);
border-color: var(--cpd-color-green-900);
}
.banner[data-type="critical"] {
background: var(--cpd-color-gradient-critical-linear);
border-color: var(--cpd-color-border-critical-primary);
}
.banner[data-type="info"] {
background: var(--cpd-color-gradient-info-linear);
border-color: var(--cpd-color-blue-900);
}
.banner[data-type="info"] :is(svg) {
color: var(--cpd-color-blue-900);
}
.banner[data-type="success"] :is(.content, svg) {
color: var(--cpd-color-green-900);
}
.banner[data-type="critical"] :is(.content, svg) {
color: var(--cpd-color-red-900);
}
.banner p {
margin: 0;
}
.icon {
/* lock icon dimensions */
min-width: 32px;
min-height: 32px;
max-width: 32px;
max-height: 32px;
margin: 4px;
/* centre svg icons, as they are not full width */
flex: 0;
display: flex;
align-items: center;
justify-content: center;
}
.icon img {
border-radius: 50%;
}
.actions {
margin-left: auto;
flex: 0;
display: flex;
flex-direction: row;
gap: var(--cpd-space-1x);
align-self: center;
}

View File

@@ -1,73 +0,0 @@
/*
* Copyright (c) 2025 Element Creations 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, type StoryObj } from "@storybook/react-vite";
import { Button } from "@vector-im/compound-web";
import { Banner } from "./Banner";
import { _t } from "../../utils/i18n";
const meta = {
title: "room/Banner",
component: Banner,
tags: ["autodocs"],
args: {
children: <p>Hello! This is a status banner.</p>,
onClose: fn(),
},
} satisfies Meta<typeof Banner>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};
export const Info: Story = {
args: {
type: "info",
},
};
export const Success: Story = {
args: {
type: "success",
},
};
export const Critical: Story = {
args: {
type: "critical",
},
};
export const WithAction: Story = {
args: {
children: (
<p>
{_t(
"encryption|pinned_identity_changed",
{ displayName: "Alice", userId: "@alice:example.org" },
{
a: (sub) => <a href="https://example.org">{sub}</a>,
b: (sub) => <b>{sub}</b>,
},
)}
</p>
),
actions: <Button kind="primary">{_t("encryption|withdraw_verification_action")}</Button>,
},
};
export const WithAvatarImage: Story = {
args: {
avatar: <img alt="Example" src="https://picsum.photos/32/32" />,
},
};
export const WithoutClose: Story = {
args: {
onClose: undefined,
},
};

View File

@@ -1,41 +0,0 @@
/*
* Copyright (c) 2025 Element Creations 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 { render } from "jest-matrix-react";
import { composeStories } from "@storybook/react-vite";
import * as stories from "./Banner.stories.tsx";
const { Default, Info, Success, WithAction, WithAvatarImage, Critical } = composeStories(stories);
describe("AvatarWithDetails", () => {
it("renders a default banner", () => {
const { container } = render(<Default />);
expect(container).toMatchSnapshot();
});
it("renders a info banner", () => {
const { container } = render(<Info />);
expect(container).toMatchSnapshot();
});
it("renders a success banner", () => {
const { container } = render(<Success />);
expect(container).toMatchSnapshot();
});
it("renders a critical banner", () => {
const { container } = render(<Critical />);
expect(container).toMatchSnapshot();
});
it("renders a banner with an action", () => {
const { container } = render(<WithAction />);
expect(container).toMatchSnapshot();
});
it("renders a banner with an avatar iamge", () => {
const { container } = render(<WithAvatarImage />);
expect(container).toMatchSnapshot();
});
});

View File

@@ -1,93 +0,0 @@
/*
* Copyright (c) 2025 Element Creations 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 classNames from "classnames";
import React, {
type MouseEventHandler,
type ReactElement,
type ReactNode,
type PropsWithChildren,
useMemo,
} from "react";
import { Button } from "@vector-im/compound-web";
import CheckCircleIcon from "@vector-im/compound-design-tokens/assets/web/icons/check-circle";
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
import InfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info";
import styles from "./Banner.module.css";
import { _t } from "../../utils/i18n";
interface BannerProps {
/**
* The type of the status banner.
*/
type?: "success" | "info" | "critical";
/**
* The banner avatar.
*/
avatar?: React.ReactNode;
className?: string;
/**
* Actions presented to the user in the right-hand side of the banner alongside the dismiss button.
*/
actions?: ReactNode;
/**
* Called when the user presses the "dismiss" button.
*/
onClose?: MouseEventHandler<HTMLButtonElement>;
}
/**
* A banner component used for displaying user-facing information above the message composer.
*
* @example
* ```tsx
* <Banner onClose={onCloseHandler} />
* ```
*/
export function Banner({
type,
children,
avatar,
className,
actions,
onClose,
...props
}: PropsWithChildren<BannerProps>): ReactElement {
const classes = classNames(styles.banner, className);
const icon = useMemo(() => {
switch (type) {
case "critical":
return <ErrorIcon fontSize={24} {...props} />;
case "info":
return <InfoIcon fontSize={24} {...props} />;
case "success":
return <CheckCircleIcon fontSize={24} {...props} />;
default:
return <InfoIcon fontSize={24} {...props} />;
}
}, [type, props]);
return (
<div {...props} className={classes} data-type={type}>
<div className={styles.icon}>{avatar ?? icon}</div>
<span className={styles.content}>{children}</span>
<div className={styles.actions}>
{actions}
{onClose && (
<Button kind="secondary" size="sm" onClick={onClose}>
{_t("action|dismiss")}
</Button>
)}
</div>
</div>
);
}

View File

@@ -1,290 +0,0 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`AvatarWithDetails renders a banner with an action 1`] = `
<div>
<div
class="banner"
>
<div
class="icon"
>
<svg
fill="currentColor"
font-size="24"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
/>
<path
clip-rule="evenodd"
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
fill-rule="evenodd"
/>
</svg>
</div>
<span
class="content"
>
<p>
encryption|pinned_identity_changed
</p>
</span>
<div
class="actions"
>
<button
class="_button_187yx_8"
data-kind="primary"
data-size="lg"
role="button"
tabindex="0"
>
encryption|withdraw_verification_action
</button>
<button
class="_button_187yx_8"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
Dismiss
</button>
</div>
</div>
</div>
`;
exports[`AvatarWithDetails renders a banner with an avatar iamge 1`] = `
<div>
<div
class="banner"
>
<div
class="icon"
>
<img
alt="Example"
src="https://picsum.photos/32/32"
/>
</div>
<span
class="content"
>
<p>
Hello! This is a status banner.
</p>
</span>
<div
class="actions"
>
<button
class="_button_187yx_8"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
Dismiss
</button>
</div>
</div>
</div>
`;
exports[`AvatarWithDetails renders a critical banner 1`] = `
<div>
<div
class="banner"
data-type="critical"
>
<div
class="icon"
>
<svg
fill="currentColor"
font-size="24"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
/>
</svg>
</div>
<span
class="content"
>
<p>
Hello! This is a status banner.
</p>
</span>
<div
class="actions"
>
<button
class="_button_187yx_8"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
Dismiss
</button>
</div>
</div>
</div>
`;
exports[`AvatarWithDetails renders a default banner 1`] = `
<div>
<div
class="banner"
>
<div
class="icon"
>
<svg
fill="currentColor"
font-size="24"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
/>
<path
clip-rule="evenodd"
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
fill-rule="evenodd"
/>
</svg>
</div>
<span
class="content"
>
<p>
Hello! This is a status banner.
</p>
</span>
<div
class="actions"
>
<button
class="_button_187yx_8"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
Dismiss
</button>
</div>
</div>
</div>
`;
exports[`AvatarWithDetails renders a info banner 1`] = `
<div>
<div
class="banner"
data-type="info"
>
<div
class="icon"
>
<svg
fill="currentColor"
font-size="24"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
/>
<path
clip-rule="evenodd"
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
fill-rule="evenodd"
/>
</svg>
</div>
<span
class="content"
>
<p>
Hello! This is a status banner.
</p>
</span>
<div
class="actions"
>
<button
class="_button_187yx_8"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
Dismiss
</button>
</div>
</div>
</div>
`;
exports[`AvatarWithDetails renders a success banner 1`] = `
<div>
<div
class="banner"
data-type="success"
>
<div
class="icon"
>
<svg
fill="currentColor"
font-size="24"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m10.6 13.8-2.15-2.15a.95.95 0 0 0-.7-.275.95.95 0 0 0-.7.275.95.95 0 0 0-.275.7q0 .425.275.7L9.9 15.9q.3.3.7.3t.7-.3l5.65-5.65a.95.95 0 0 0 .275-.7.95.95 0 0 0-.275-.7.95.95 0 0 0-.7-.275.95.95 0 0 0-.7.275zM12 22a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20"
/>
</svg>
</div>
<span
class="content"
>
<p>
Hello! This is a status banner.
</p>
</span>
<div
class="actions"
>
<button
class="_button_187yx_8"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
Dismiss
</button>
</div>
</div>
</div>
`;

View File

@@ -1,8 +0,0 @@
/*
* Copyright (c) 2025 Element Creations 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 * from "./Banner";

View File

@@ -1,42 +0,0 @@
/*
* Copyright (c) 2025 Element Creations 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 Meta, type StoryFn } from "@storybook/react-vite";
import React, { type JSX } from "react";
import { fn } from "storybook/test";
import { useMockedViewModel } from "../../useMockedViewModel";
import {
HistoryVisibleBannerView,
type HistoryVisibleBannerViewActions,
type HistoryVisibleBannerViewSnapshot,
} from "./HistoryVisibleBannerView";
type HistoryVisibleBannerProps = HistoryVisibleBannerViewSnapshot & HistoryVisibleBannerViewActions;
const HistoryVisibleBannerViewWrapper = ({ onClose, ...rest }: HistoryVisibleBannerProps): JSX.Element => {
const vm = useMockedViewModel(rest, {
onClose,
});
return <HistoryVisibleBannerView vm={vm} />;
};
export default {
title: "composer/HistoryVisibleBannerView",
component: HistoryVisibleBannerViewWrapper,
tags: ["autodocs"],
argTypes: {},
args: {
visible: true,
onClose: fn(),
},
} as Meta<typeof HistoryVisibleBannerViewWrapper>;
const Template: StoryFn<typeof HistoryVisibleBannerViewWrapper> = (args) => (
<HistoryVisibleBannerViewWrapper {...args} />
);
export const Default = Template.bind({});

View File

@@ -1,28 +0,0 @@
/*
* Copyright (c) 2025 Element Creations 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 { render } from "jest-matrix-react";
import { composeStories } from "@storybook/react-vite";
import * as stories from "./HistoryVisibleBannerView.stories.tsx";
const { Default } = composeStories(stories);
describe("HistoryVisibleBannerView", () => {
it("renders a history visible banner", () => {
const dismissFn = jest.fn();
const { container } = render(<Default onClose={dismissFn} />);
expect(container).toMatchSnapshot();
const button = container.querySelector("button");
expect(button).not.toBeNull();
button?.click();
expect(dismissFn).toHaveBeenCalled();
});
});

View File

@@ -1,79 +0,0 @@
/*
* Copyright (c) 2025 Element Creations 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 { Link } from "@vector-im/compound-web";
import React, { type JSX } from "react";
import { useViewModel } from "../../useViewModel";
import { _t } from "../../utils/i18n";
import { type ViewModel } from "../../viewmodel";
import { Banner } from "../Banner";
export interface HistoryVisibleBannerViewActions {
/**
* Called when the user dismisses the banner.
*/
onClose: () => void;
}
export interface HistoryVisibleBannerViewSnapshot {
/**
* Whether the banner is currently visible.
*/
visible: boolean;
}
/**
* The view model for the banner.
*/
export type HistoryVisibleBannerViewModel = ViewModel<HistoryVisibleBannerViewSnapshot> &
HistoryVisibleBannerViewActions;
interface HistoryVisibleBannerViewProps {
/**
* The view model for the banner.
*/
vm: HistoryVisibleBannerViewModel;
}
/**
* A component to alert that history is shared to new members of the room.
*
* @example
* ```tsx
* <HistoryVisibleBannerView vm={historyVisibleBannerViewModel} />
* ```
*/
export function HistoryVisibleBannerView({ vm }: Readonly<HistoryVisibleBannerViewProps>): JSX.Element {
const { visible } = useViewModel(vm);
const contents = _t(
"room|status_bar|history_visible",
{},
{
a: substituteATag,
},
);
return (
<>
{visible && (
<Banner type="info" onClose={() => vm.onClose()}>
{contents}
</Banner>
)}
</>
);
}
function substituteATag(sub: string): JSX.Element {
return (
<Link href="https://element.io/en/help#e2ee-history-sharing" target="_blank">
{sub}
</Link>
);
}

View File

@@ -1,62 +0,0 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`HistoryVisibleBannerView renders a history visible banner 1`] = `
<div>
<div
class="banner"
data-type="info"
>
<div
class="icon"
>
<svg
fill="currentColor"
font-size="24"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.288 7.288A.97.97 0 0 1 12 7q.424 0 .713.287Q13 7.576 13 8t-.287.713A.97.97 0 0 1 12 9a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 8q0-.424.287-.713m.001 4.001A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713v4q0 .424-.287.712A.97.97 0 0 1 12 17a.97.97 0 0 1-.713-.288A.97.97 0 0 1 11 16v-4q0-.424.287-.713"
/>
<path
clip-rule="evenodd"
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10m-2 0a8 8 0 1 1-16 0 8 8 0 0 1 16 0"
fill-rule="evenodd"
/>
</svg>
</div>
<span
class="content"
>
<span>
Messages you send will be shared with new members invited to this room.
<a
class="_link_1v5rz_8"
data-kind="primary"
data-size="medium"
href="https://element.io/en/help#e2ee-history-sharing"
rel="noreferrer noopener"
target="_blank"
>
Learn more
</a>
</span>
</span>
<div
class="actions"
>
<button
class="_button_187yx_8"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
Dismiss
</button>
</div>
</div>
</div>
`;

View File

@@ -1,8 +0,0 @@
/*
* Copyright (c) 2025 Element Creations 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 * from "./HistoryVisibleBannerView";

View File

@@ -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 { TextualEventView, type TextualEventViewSnapshot } from "./TextualEventView";

View File

@@ -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);
});
});

View File

@@ -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 };
}

View File

@@ -1,41 +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 "./composer/Banner";
export * from "./composer/HistoryVisibleBannerView";
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 * from "./utils/i18n";
export * from "./utils/i18nContext";
export * from "./utils/humanize";
export * from "./utils/DateUtils";
export * from "./utils/numbers";
export * from "./utils/FormattingUtils";
export * from "./utils/I18nApi";
// MVVM
export * from "./viewmodel";
export * from "./useMockedViewModel";
export * from "./useViewModel";
// i18n (we must export this directly in order to not confuse the type bundler, it seems,
// otherwise it will leave it as a relative import rather than bundling it)
export type * from "./i18nKeys.d.ts";

View File

@@ -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);
}

View File

@@ -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,
},
};

View File

@@ -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();
});
});

View File

@@ -1,69 +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 { useI18n } from "../../utils/i18nContext";
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();
const { translate: _t } = useI18n();
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")}
className="mx_Dialog_nonDialogButton"
>
<CloseIcon color="var(--cpd-color-icon-tertiary)" />
</IconButton>
)}
</Flex>
);
}

View File

@@ -1,66 +0,0 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
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 mx_Dialog_nonDialogButton"
data-kind="primary"
role="button"
style="--cpd-icon-button-size: 16px;"
tabindex="0"
>
<div
class="_indicator-icon_147l5_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>
`;

View File

@@ -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;
}

View File

@@ -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 } };

View File

@@ -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);
});
});

View File

@@ -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>
);
}

View File

@@ -1,44 +0,0 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
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>
`;

View File

@@ -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";

View File

@@ -1,22 +0,0 @@
/*
Copyright 2025 Element Creations 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 fetchMock from "fetch-mock-jest";
import { setLanguage } from "../../src/utils/i18n";
import en from "../../../../src/i18n/strings/en_EN.json";
export function setupLanguageMock(): void {
fetchMock
.get("/i18n/languages.json", {
en: "en_EN.json",
})
.get("end:en_EN.json", en);
}
setupLanguageMock();
setLanguage("en");

View File

@@ -1,55 +0,0 @@
/*
Copyright 2025 Element Creations Ltd.
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
// Copied from element-web/test/test-utils because, seemingly, if we
// set that as the modules directory to use it directly, it fails to
// actually put the right thing in the context somehow.
import React, { type ReactElement } from "react";
// eslint-disable-next-line no-restricted-imports
import { render, type RenderOptions } from "@testing-library/react";
import { TooltipProvider } from "@vector-im/compound-web";
import { I18nApi, I18nContext } from "../..";
const wrapWithTooltipProvider = (Wrapper: RenderOptions["wrapper"]) => {
return ({ children }: { children: React.ReactNode }) => {
if (Wrapper) {
return (
<I18nContext.Provider value={new I18nApi()}>
<Wrapper>
<TooltipProvider>{children}</TooltipProvider>
</Wrapper>
</I18nContext.Provider>
);
} else {
return (
<I18nContext.Provider value={new I18nApi()}>
<TooltipProvider>{children}</TooltipProvider>
</I18nContext.Provider>
);
}
};
};
const customRender = (ui: ReactElement, options: RenderOptions = {}): ReturnType<typeof render> => {
return render(ui, {
...options,
wrapper: wrapWithTooltipProvider(options?.wrapper) as RenderOptions["wrapper"],
}) as ReturnType<typeof render>;
};
// eslint-disable-next-line no-restricted-imports
export * from "@testing-library/react";
/**
* This custom render function wraps your component with a TooltipProvider.
* See https://testing-library.com/docs/react-testing-library/setup/#custom-render
*/
export { customRender as render };

View File

@@ -1,25 +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 { useMemo } from "react";
import { MockViewModel, type ViewModel } from "./viewmodel";
/**
* Hook helper to return a mocked view model created with the given snapshot and actions.
* This is useful for testing components in isolation with a mocked view model and allows to use primitive types in stories.
*
* @param snapshot
* @param actions
*/
export function useMockedViewModel<S, A>(snapshot: S, actions: A): ViewModel<S> & A {
return useMemo(() => {
const vm = new MockViewModel<S>(snapshot);
Object.assign(vm, actions);
return vm as unknown as ViewModel<S> & A;
}, [snapshot, actions]);
}

View File

@@ -1,22 +0,0 @@
/*
* Copyright 2025 Element Creations 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 TranslationKey } from "../i18nKeys";
import { I18nApi } from "./I18nApi";
describe("I18nApi", () => {
it("can register a translation and use it", () => {
const i18n = new I18nApi();
i18n.register({
"hello.world": {
en: "Hello, World!",
},
});
expect(i18n.translate("hello.world" as TranslationKey)).toBe("Hello, World!");
});
});

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2025 Element Creations 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 counterpart from "counterpart";
import { registerTranslations, setMissingEntryGenerator, getLocale, setLocale } from "./i18n";
describe("i18n utils", () => {
beforeEach(() => {
jest.clearAllMocks();
});
it("should wrap registerTranslations", () => {
jest.spyOn(counterpart, "registerTranslations");
registerTranslations("en", { test: "This is a test" });
expect(counterpart.registerTranslations).toHaveBeenCalledWith("en", { test: "This is a test" });
});
it("should wrap setMissingEntryGenerator", () => {
jest.spyOn(counterpart, "setMissingEntryGenerator");
const dummyFn = jest.fn();
setMissingEntryGenerator(dummyFn);
expect(counterpart.setMissingEntryGenerator).toHaveBeenCalledWith(dummyFn);
});
it("should wrap getLocale", () => {
jest.spyOn(counterpart, "getLocale");
getLocale();
expect(counterpart.getLocale).toHaveBeenCalled();
});
it("should wrap setLocale", () => {
jest.spyOn(counterpart, "setLocale");
setLocale("en");
expect(counterpart.setLocale).toHaveBeenCalledWith("en");
});
});

View File

@@ -1,27 +0,0 @@
/*
Copyright 2025 Element Creations 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 { createContext, useContext } from "react";
import { type I18nApi } from "@element-hq/element-web-module-api";
export const I18nContext = createContext<I18nApi | null>(null);
I18nContext.displayName = "I18nContext";
/**
* A hook to get the i18n API from the context. Will throw if no i18n context is found.
* @throws If no i18n context is found
* @returns The i18n API from the context
*/
export function useI18n(): I18nApi {
const i18n = useContext(I18nContext);
if (!i18n) {
throw new Error("useI18n must be used within an I18nContext.Provider");
}
return i18n;
}

View File

@@ -1,14 +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 * from "./BaseViewModel";
export * from "./Disposables";
export * from "./Snapshot";
export * from "./ViewModelSubscriptions";
export type * from "./ViewModel";
export * from "./MockViewModel";
export * from "./useCreateAutoDisposedViewModel";

Some files were not shown because too many files have changed in this diff Show More