diff --git a/.eslintrc.js b/.eslintrc.js index 26865d55ec..ed5961d652 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,10 @@ +/* +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 = { plugins: ["matrix-org", "eslint-plugin-react-compiler"], extends: ["plugin:matrix-org/babel", "plugin:matrix-org/react", "plugin:matrix-org/a11y"], diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ee6d9526ac..e1b7c24368 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,7 +44,7 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6 with: # Disable cache on Windows as it is slower than not caching # https://github.com/actions/setup-node/issues/975 @@ -66,7 +66,7 @@ jobs: run: VERSION=$(scripts/get-version-from-git.sh) yarn build - name: Upload Artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: webapp-${{ matrix.image }} path: webapp diff --git a/.github/workflows/build_debian.yaml b/.github/workflows/build_debian.yaml index 42c0dec22c..36417b0eab 100644 --- a/.github/workflows/build_debian.yaml +++ b/.github/workflows/build_debian.yaml @@ -62,7 +62,7 @@ jobs: dpkg-gencontrol -v"$VERSION" -ldebian/tmp/DEBIAN/changelog dpkg-deb -Zxz --root-owner-group --build debian/tmp element-web.deb - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: element-web.deb path: element-web.deb diff --git a/.github/workflows/build_develop.yml b/.github/workflows/build_develop.yml index 924d1bdd1d..2a778f0169 100644 --- a/.github/workflows/build_develop.yml +++ b/.github/workflows/build_develop.yml @@ -28,7 +28,7 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6 with: cache: "yarn" node-version: "lts/*" @@ -53,7 +53,7 @@ jobs: - run: mv dist/element-*.tar.gz dist/develop.tar.gz - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: webapp path: dist/develop.tar.gz diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 6840c48bc7..54764a0ecf 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -132,7 +132,7 @@ jobs: cosign sign --yes ${images} - name: Update repo description - uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4 + uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5 if: github.event_name != 'pull_request' continue-on-error: true with: @@ -141,7 +141,7 @@ jobs: repository: vectorim/element-web - name: Repository Dispatch - uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3 + uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4 if: github.event_name != 'pull_request' with: repository: element-hq/element-web-pro diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 62a582249f..43a13d8429 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -33,7 +33,7 @@ jobs: repository: matrix-org/matrix-js-sdk path: matrix-js-sdk - - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6 with: cache: "yarn" cache-dependency-path: element-web/yarn.lock diff --git a/.github/workflows/end-to-end-tests-netlify.yaml b/.github/workflows/end-to-end-tests-netlify.yaml index 1cd257b34d..049f4ea343 100644 --- a/.github/workflows/end-to-end-tests-netlify.yaml +++ b/.github/workflows/end-to-end-tests-netlify.yaml @@ -25,7 +25,7 @@ jobs: actions: read steps: - name: Download HTML report - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} diff --git a/.github/workflows/end-to-end-tests.yaml b/.github/workflows/end-to-end-tests.yaml index 5064c25fa3..0997c6306a 100644 --- a/.github/workflows/end-to-end-tests.yaml +++ b/.github/workflows/end-to-end-tests.yaml @@ -54,7 +54,7 @@ jobs: with: repository: element-hq/element-web - - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6 with: cache: "yarn" node-version: "lts/*" @@ -74,7 +74,7 @@ jobs: run: VERSION=$(scripts/get-version-from-git.sh) yarn build - name: Upload Artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: webapp path: webapp @@ -128,12 +128,12 @@ jobs: repository: element-hq/element-web - name: 📥 Download artifact - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: name: webapp path: webapp - - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6 with: cache: "yarn" cache-dependency-path: yarn.lock @@ -172,7 +172,7 @@ jobs: - name: Upload blob report to GitHub Actions Artifacts if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: all-blob-reports-${{ matrix.project }}-${{ matrix.runner }} path: blob-report @@ -200,7 +200,7 @@ jobs: persist-credentials: false repository: element-hq/element-web - - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6 if: inputs.skip != true with: cache: "yarn" @@ -212,7 +212,7 @@ jobs: - name: Download blob reports from GitHub Actions Artifacts if: inputs.skip != true - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: pattern: 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 - name: Upload HTML report if: always() && inputs.skip != true - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: html-report path: playwright-report diff --git a/.github/workflows/netlify.yaml b/.github/workflows/netlify.yaml index 05f7dbf3b1..ab2433c267 100644 --- a/.github/workflows/netlify.yaml +++ b/.github/workflows/netlify.yaml @@ -28,7 +28,7 @@ jobs: Exercise caution. Use test accounts. - name: 📥 Download artifact - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} diff --git a/.github/workflows/shared-component-publish.yaml b/.github/workflows/shared-component-publish.yaml new file mode 100644 index 0000000000..2df2d7a652 --- /dev/null +++ b/.github/workflows/shared-component-publish.yaml @@ -0,0 +1,40 @@ +name: Publish shared component npm package +on: + workflow_dispatch: {} + +concurrency: release +jobs: + publish: + name: "Publish" + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + + steps: + - name: 🧮 Checkout code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + + - name: 🔧 Set up node environment + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6 + with: + cache: "yarn" + node-version-file: ".node-version" + registry-url: "https://registry.npmjs.org" + + # 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 diff --git a/.github/workflows/shared-component-visual-tests-netlify.yaml b/.github/workflows/shared-component-visual-tests-netlify.yaml index 3313ecf03a..816d899836 100644 --- a/.github/workflows/shared-component-visual-tests-netlify.yaml +++ b/.github/workflows/shared-component-visual-tests-netlify.yaml @@ -27,7 +27,7 @@ jobs: run: "sudo apt-get install -y tree" - name: Download Diffs - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} diff --git a/.github/workflows/shared-component-visual-tests.yaml b/.github/workflows/shared-component-visual-tests.yaml index a1a1096467..aa9374f934 100644 --- a/.github/workflows/shared-component-visual-tests.yaml +++ b/.github/workflows/shared-component-visual-tests.yaml @@ -26,7 +26,7 @@ jobs: persist-credentials: false repository: element-hq/element-web - - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6 with: cache: "yarn" node-version: "lts/*" @@ -55,22 +55,12 @@ jobs: if: steps.playwright-cache.outputs.cache-hit != 'true' 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 --cwd packages/shared-components test:storybook:ci" - continue-on-error: true - - name: Run Visual tests run: "yarn --cwd packages/shared-components test:storybook:ci" - name: Upload received images & diffs if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: received-images path: packages/shared-components/playwright/shared-component-received diff --git a/.github/workflows/static_analysis.yaml b/.github/workflows/static_analysis.yaml index aea43181e8..60f03ead5c 100644 --- a/.github/workflows/static_analysis.yaml +++ b/.github/workflows/static_analysis.yaml @@ -24,7 +24,7 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6 with: cache: "yarn" node-version: "lts/*" @@ -75,7 +75,7 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6 with: cache: "yarn" node-version: "lts/*" @@ -99,7 +99,7 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6 with: cache: "yarn" node-version: "lts/*" @@ -117,7 +117,7 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6 with: cache: "yarn" node-version: "lts/*" @@ -135,7 +135,7 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6 with: cache: "yarn" node-version: "lts/*" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 47465f8dae..4fd5f4f633 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,8 +29,8 @@ env: permissions: {} jobs: - jest: - name: Jest + jest_ew: + name: Jest (Element Web) runs-on: ubuntu-24.04 strategy: fail-fast: false @@ -44,7 +44,7 @@ jobs: repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }} - name: Yarn cache - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6 with: node-version: "lts/*" cache: "yarn" @@ -84,7 +84,7 @@ jobs: - name: Upload Artifact if: env.ENABLE_COVERAGE == 'true' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: coverage-${{ matrix.runner }} path: | @@ -93,13 +93,13 @@ jobs: complete: name: jest-tests - needs: jest + needs: jest_ew if: always() runs-on: ubuntu-24.04 permissions: statuses: write steps: - - if: needs.jest.result != 'skipped' && needs.jest.result != 'success' + - if: needs.jest_ew.result != 'skipped' && needs.jest_ew.result != 'success' run: exit 1 - name: Skip SonarCloud in merge queue @@ -112,3 +112,56 @@ jobs: context: SonarCloud Code Analysis sha: ${{ github.sha }} 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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + with: + repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }} + + - name: Yarn cache + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # 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 diff --git a/.github/workflows/triage-stale.yml b/.github/workflows/triage-stale.yml index 768dad22b1..69d094810c 100644 --- a/.github/workflows/triage-stale.yml +++ b/.github/workflows/triage-stale.yml @@ -12,7 +12,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10 + - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10 with: operations-per-run: 100 diff --git a/.github/workflows/update-jitsi.yml b/.github/workflows/update-jitsi.yml index eda2137cdd..f98ecffecd 100644 --- a/.github/workflows/update-jitsi.yml +++ b/.github/workflows/update-jitsi.yml @@ -11,7 +11,7 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6 with: cache: "yarn" node-version: "lts/*" diff --git a/.gitignore b/.gitignore index 89247a57a3..3d6d723ac3 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ storybook-static /packages/shared-components/node_modules /packages/shared-components/dist +/packages/shared-components/src/i18nKeys.d.ts diff --git a/.node-version b/.node-version index 2bd5a0a98a..a45fd52cc5 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -22 +24 diff --git a/CHANGELOG.md b/CHANGELOG.md index 093930033a..7243ca664e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,50 @@ +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 `\ 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 diff --git a/Dockerfile b/Dockerfile index da11e0a74e..e37a88875b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -# syntax=docker.io/docker/dockerfile:1.18-labs@sha256:79cdc14e1c220efb546ad14a8ebc816e3277cd72d27195ced5bebdd226dd1025 +# syntax=docker.io/docker/dockerfile:1.19-labs@sha256:dce1c693ef318bca08c964ba3122ae6248e45a1b96d65c4563c8dc6fe80349a2 # Builder -FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:5e638ea282ab9f0224949e8cfc7bb4621710e5d21b19fc3cf6e8884fcb5839f0 AS builder +FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:c102f42d665c164b4e5e5549813b1547ac8a9f1d343c7d17ddac106905a1c30b AS builder # Support custom branch of the js-sdk. This also helps us build images of element-web develop. 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 # App -FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:13d1e0acc26b7ce8a40d155473759387e04d1433e73726d4bb49c67bdb197fe5 +FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:65a7f97c299b919190e96e38e2ff8358132732000d3bc5c00c07cc8763fca53f # Need root user to install packages & manipulate the usr directory USER root diff --git a/docs/MVVM.md b/docs/MVVM.md index 065b647c24..6175e21853 100644 --- a/docs/MVVM.md +++ b/docs/MVVM.md @@ -18,7 +18,7 @@ This is anywhere your data or business logic comes from. If your view model is a #### View -1. Located in [`shared-components`](https://github.com/element-hq/element-web/tree/develop/src/shared-components). Develop it in storybook! +1. Located in [`shared-components`](https://github.com/element-hq/element-web/tree/develop/packages/shared-components). Develop it in storybook! 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. 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` - // https://github.com/element-hq/element-web/blob/develop/src/shared-components/ViewModel.ts + // https://github.com/element-hq/element-web/blob/develop/packages/shared-components/src/ViewModel.ts type FooViewModel = ViewModel & FooViewActions; 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. -6. A full example is available [here](https://github.com/element-hq/element-web/blob/develop/src/shared-components/audio/AudioPlayerView/AudioPlayerView.tsx) +6. A full example is available [here](https://github.com/element-hq/element-web/blob/develop/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.tsx) #### View Model diff --git a/docs/config.md b/docs/config.md index 0c5a2b2dca..6f5315d257 100644 --- a/docs/config.md +++ b/docs/config.md @@ -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: 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://element.io/bugreports/submit` to submit + not present in the config, the app will disable all rageshake functionality. Set to `https://rageshakes.element.io/api/submit` to submit 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 alongside the rageshake so the rageshake server can filter them by app name. By default, this will be `element-auto-uisi` diff --git a/docs/install.md b/docs/install.md index 8d06b6fc64..185cf51f99 100644 --- a/docs/install.md +++ b/docs/install.md @@ -14,48 +14,48 @@ The release tarball contains a pre-built, production-ready version of Element We 1. **Download the latest release** - Download from + Download from - Releases are signed using GPG and the OpenPGP standard. You can verify the signature against the public key at + Releases are signed using GPG and the OpenPGP standard. You can verify the signature against the public key at 2. **Extract the tarball** - ```bash - tar -xzf element-v*.tar.gz - ``` + ```bash + tar -xzf element-v*.tar.gz + ``` - This creates a directory named `element-x.x.x` containing all the static files. + This creates a directory named `element-x.x.x` containing all the static files. 3. **Deploy to your web server** - Move or symlink the directory to your web server's document root: + Move or symlink the directory to your web server's document root: - ```bash - # Example: Move to /var/www/element - sudo mv element-x.x.x /var/www/element + ```bash + # Example: Move to /var/www/element + sudo mv element-x.x.x /var/www/element - # Or create a symlink for easier version management - sudo ln -s /var/www/element-x.x.x /var/www/element - ``` + # Or create a symlink for easier version management + sudo ln -s /var/www/element-x.x.x /var/www/element + ``` 4. **Configure Element Web** - Copy the sample configuration and customize it: + Copy the sample configuration and customize it: - ```bash - cd /var/www/element - cp config.sample.json config.json - ``` + ```bash + cd /var/www/element + cp config.sample.json config.json + ``` - Edit `config.json` to configure your homeserver and other settings. See the [configuration docs](config.md) for details. + Edit `config.json` to configure your homeserver and other settings. See the [configuration docs](config.md) for details. 5. **Configure your web server** - Set up proper caching headers and security settings. See the [web server configuration examples](#web-server-configuration) below. + Set up proper caching headers and security settings. See the [web server configuration examples](#web-server-configuration) below. 6. **Access Element Web** - Navigate to your server's URL (e.g., `https://element.example.com`) and log in! + Navigate to your server's URL (e.g., `https://element.example.com`) and log in! ### Web Server Configuration diff --git a/docs/kubernetes.md b/docs/kubernetes.md index 23c3fde611..39c10d330b 100644 --- a/docs/kubernetes.md +++ b/docs/kubernetes.md @@ -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/api" ], - "bug_report_endpoint_url": "https://element.io/bugreports/submit", + "bug_report_endpoint_url": "https://rageshakes.element.io/api/submit", "defaultCountryCode": "GB", "show_labs_settings": false, "features": { }, diff --git a/element.io/app/config.json b/element.io/app/config.json index 4324ffc7fb..4f7bde39fb 100644 --- a/element.io/app/config.json +++ b/element.io/app/config.json @@ -17,7 +17,7 @@ "https://scalar-staging.vector.im/_matrix/integrations/v1", "https://scalar-staging.vector.im/api" ], - "bug_report_endpoint_url": "https://element.io/bugreports/submit", + "bug_report_endpoint_url": "https://rageshakes.element.io/api/submit", "uisi_autorageshake_app": "element-auto-uisi", "show_labs_settings": false, "room_directory": { diff --git a/element.io/develop/config.json b/element.io/develop/config.json index ce4a8a0407..7cfe544478 100644 --- a/element.io/develop/config.json +++ b/element.io/develop/config.json @@ -17,7 +17,7 @@ "https://scalar-staging.vector.im/_matrix/integrations/v1", "https://scalar-staging.vector.im/api" ], - "bug_report_endpoint_url": "https://element.io/bugreports/submit", + "bug_report_endpoint_url": "https://rageshakes.element.io/api/submit", "uisi_autorageshake_app": "element-auto-uisi", "show_labs_settings": true, "room_directory": { diff --git a/jest.config.ts b/jest.config.ts index d4e79561da..46a40d4a2e 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -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 customExportConditions: ["browser", "node"], }, - testMatch: ["/test/**/*-test.[tj]s?(x)", "/src/shared-components/**/*.test.[t]s?(x)"], + testMatch: ["/test/**/*-test.[tj]s?(x)"], globalSetup: "/test/globalSetup.ts", setupFiles: ["jest-canvas-mock", "web-streams-polyfill/polyfill"], setupFilesAfterEnv: ["/test/setupTests.ts"], @@ -40,12 +40,14 @@ const config: Config = { "^!!raw-loader!.*": "jest-raw-loader", "recorderWorkletFactory": "/__mocks__/empty.js", "^fetch-mock$": "/node_modules/fetch-mock", + "counterpart": "/node_modules/counterpart", }, transformIgnorePatterns: [ "/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs)).+$", ], collectCoverageFrom: [ "/src/**/*.{js,ts,tsx}", + "/packages/**/*.{js,ts,tsx}", // 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. "!/src/utils/SessionLock.ts", diff --git a/package.json b/package.json index a06f826985..38bf708029 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "element-web", - "version": "1.12.1", + "version": "1.12.3", "description": "Element: the future of secure communication", "author": "New Vector Ltd.", "repository": { @@ -29,7 +29,7 @@ "UserFriendlyError" ], "scripts": { - "i18n": "matrix-gen-i18n src res packages/shared-components && yarn i18n:sort && yarn i18n:lint", + "i18n": "matrix-gen-i18n src res packages/shared-components/src && 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: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", @@ -68,20 +68,22 @@ "postinstall": "patch-package" }, "resolutions": { - "**/pretty-format/react-is": "19.1.1", - "@playwright/test": "1.54.2", - "@types/react": "19.1.14", - "@types/react-dom": "19.1.9", + "**/pretty-format/react-is": "19.2.0", + "@playwright/test": "1.56.1", + "@types/react": "19.2.2", + "@types/react-dom": "19.2.2", + "@types/serve-static": "1.15.10", "oidc-client-ts": "3.3.0", "jwt-decode": "4.0.0", - "caniuse-lite": "1.0.30001745", + "caniuse-lite": "1.0.30001751", "testcontainers": "^11.0.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0", "wrap-ansi": "npm:wrap-ansi@^7.0.0" }, "dependencies": { "@babel/runtime": "^7.12.5", - "@element-hq/element-web-module-api": "1.4.1", + "@element-hq/element-web-module-api": "1.5.0", + "@element-hq/web-shared-components": "file:packages/shared-components", "@fontsource/inconsolata": "^5", "@fontsource/inter": "^5", "@formatjs/intl-segmenter": "^11.5.7", @@ -103,7 +105,6 @@ "browserslist": "^4.23.2", "classnames": "^2.2.6", "commonmark": "^0.31.0", - "counterpart": "^0.18.6", "css-tree": "^3.0.0", "diff-dom": "^5.0.0", "diff-match-patch": "^1.0.5", @@ -122,6 +123,7 @@ "jsrsasign": "^11.0.0", "jszip": "^3.7.0", "katex": "^0.16.0", + "linkify-html": "4.3.2", "linkify-react": "4.3.2", "linkify-string": "4.3.2", "linkifyjs": "4.3.2", @@ -137,7 +139,7 @@ "opus-recorder": "^8.0.3", "pako": "^2.0.3", "png-chunks-extract": "^1.0.0", - "posthog-js": "1.268.6", + "posthog-js": "1.280.1", "qrcode": "1.5.4", "re-resizable": "6.11.2", "react": "^19.0.0", @@ -180,13 +182,13 @@ "@babel/preset-typescript": "^7.12.7", "@babel/runtime": "^7.12.5", "@casualbot/jest-sonar-reporter": "2.2.7", - "@element-hq/element-call-embedded": "0.16.0", + "@element-hq/element-call-embedded": "0.16.1", "@element-hq/element-web-playwright-common": "^2.0.0", "@peculiar/webcrypto": "^1.4.3", "@playwright/test": "^1.50.1", "@principalstudio/html-webpack-inject-preload": "^1.2.7", - "@rrweb/types": "^2.0.0-alpha.18", "@sentry/webpack-plugin": "^4.0.0", + "@storybook/react-vite": "^9.1.10", "@stylistic/eslint-plugin": "^5.0.0", "@svgr/webpack": "^8.0.0", "@testing-library/dom": "^10.4.0", @@ -213,9 +215,9 @@ "@types/node-fetch": "^2.6.2", "@types/pako": "^2.0.0", "@types/qrcode": "^1.3.5", - "@types/react": "19.1.14", + "@types/react": "19.2.2", "@types/react-beautiful-dnd": "^13.0.0", - "@types/react-dom": "19.1.9", + "@types/react-dom": "19.2.2", "@types/react-transition-group": "^4.4.0", "@types/sanitize-html": "2.16.0", "@types/sdp-transform": "^2.4.10", @@ -239,14 +241,14 @@ "eslint": "8.57.1", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^10.0.0", - "eslint-plugin-deprecate": "0.8.5", + "eslint-plugin-deprecate": "0.8.7", "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jest": "^28.0.0", + "eslint-plugin-jest": "^29.0.0", "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-matrix-org": "^2.0.2", + "eslint-plugin-matrix-org": "^3.0.0", "eslint-plugin-react": "^7.28.0", "eslint-plugin-react-compiler": "^19.0.0-beta-df7b47d-20241124", - "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-hooks": "^7.0.0", "eslint-plugin-unicorn": "^56.0.0", "express": "^5.0.0", "fake-indexeddb": "^6.0.0", @@ -287,6 +289,7 @@ "rimraf": "^6.0.0", "semver": "^7.5.2", "source-map-loader": "^5.0.0", + "storybook": "^9.1.10", "stylelint": "^16.23.0", "stylelint-config-standard": "^39.0.0", "stylelint-scss": "^6.0.0", diff --git a/packages/shared-components/.eslintrc.js b/packages/shared-components/.eslintrc.js index 0408f840e1..15871e1bac 100644 --- a/packages/shared-components/.eslintrc.js +++ b/packages/shared-components/.eslintrc.js @@ -1,4 +1,12 @@ +/* +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", diff --git a/packages/shared-components/.prettierignore b/packages/shared-components/.prettierignore index 849ddff3b7..bbb6b1ef7f 100644 --- a/packages/shared-components/.prettierignore +++ b/packages/shared-components/.prettierignore @@ -1 +1,2 @@ dist/ +i18n/i18nKeys.d.ts diff --git a/packages/shared-components/babel.config.js b/packages/shared-components/babel.config.js new file mode 100644 index 0000000000..02ff2e43fe --- /dev/null +++ b/packages/shared-components/babel.config.js @@ -0,0 +1,21 @@ +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", + ], +}; diff --git a/packages/shared-components/jest.config.ts b/packages/shared-components/jest.config.ts new file mode 100644 index 0000000000..993fcc680b --- /dev/null +++ b/packages/shared-components/jest.config.ts @@ -0,0 +1,58 @@ +/* +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: ["/src/**/*.test.[tj]s?(x)"], + setupFilesAfterEnv: ["/src/test/setupTests.ts"], + moduleNameMapper: { + // Support CSS module + "\\.(module.css)$": "identity-obj-proxy", + "\\.(css|scss|pcss)$": "/__mocks__/cssMock.js", + "\\.(gif|png|ttf|woff2)$": "/__mocks__/imageMock.js", + "\\.svg$": "/__mocks__/svg.js", + "\\$webapp/i18n/languages.json": "/../../__mocks__/languages.json", + "^react$": "/node_modules/react", + "^react-dom$": "/node_modules/react-dom", + "waveWorker\\.min\\.js": "/__mocks__/empty.js", + "context-filter-polyfill": "/__mocks__/empty.js", + "workers/(.+)Factory": "/__mocks__/workerFactoryMock.js", + }, + transformIgnorePatterns: [ + "/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs)).+$", + ], + collectCoverageFrom: [ + "/src/**/*.{js,ts,tsx}", + "/packages/**/*.{js,ts,tsx}", + // Coverage chokes on type definition files + "!/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("/../../test/slowReporter.cjs"); + } + config.reporters = reporters; +} + +export default config; diff --git a/packages/shared-components/package.json b/packages/shared-components/package.json index a6cb432243..ad8e86187a 100644 --- a/packages/shared-components/package.json +++ b/packages/shared-components/package.json @@ -1,22 +1,41 @@ { - "name": "element-web-shared-components", - "version": "1.12.1", + "name": "@element-hq/web-shared-components", + "version": "0.0.0-test.7", "description": "Shared components for Element", "author": "New Vector Ltd.", "repository": { "type": "git", "url": "https://github.com/element-hq/element-web" }, - "license": "SEE LICENSE IN README.md", + "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": [ - "lib", + "dist", "src", "LICENSE", "README.md", "package.json" ], "scripts": { - "postinstall": "patch-package", + "test": "jest", + "prepare": "patch-package && yarn --cwd ../.. build:res && ts-node scripts/gatherTranslationKeys.ts && vite build", "storybook": "storybook dev -p 6007", "build-storybook": "storybook build", "lint": "yarn lint:types && yarn lint:js", @@ -24,29 +43,52 @@ "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" + "test:storybook:update": "playwright-screenshots --entrypoint /work/node_modules/.bin/test-storybook --with-node-modules --url http://host.docker.internal:6007/ --updateSnapshot" + }, + "dependencies": { + "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" }, - "dependencies": {}, "devDependencies": { + "@element-hq/element-web-playwright-common": "^2.0.0", + "@playwright/test": "^1.50.1", "@storybook/addon-a11y": "^9.1.10", "@storybook/addon-designs": "^10.0.2", "@storybook/addon-docs": "^9.1.10", "@storybook/icons": "^1.6.0", "@storybook/react-vite": "^9.1.10", "@storybook/test-runner": "^0.23.0", + "@testing-library/dom": "^10.4.1", + "@testing-library/react": "^16.3.0", + "@types/counterpart": "^0.18.4", + "@types/lodash": "^4.17.20", + "@types/react": "^19.2.2", "concurrently": "^9.2.1", "eslint": "8", - "eslint-plugin-storybook": "^9.1.10", + "eslint-plugin-matrix-org": "^3.0.0", + "eslint-plugin-storybook": "^10.0.0", + "jest": "^30.2.0", "jest-image-snapshot": "^6.5.1", "patch-package": "^8.0.1", "prettier": "^3.6.2", "storybook": "^9.1.10", + "ts-node": "^10.9.2", "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" + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e", + "peerDependencies": { + "@vector-im/compound-design-tokens": "^6.0.0", + "@vector-im/compound-web": "^8.2.5" + } } diff --git a/packages/shared-components/scripts/gatherTranslationKeys.ts b/packages/shared-components/scripts/gatherTranslationKeys.ts new file mode 100644 index 0000000000..b92d767179 --- /dev/null +++ b/packages/shared-components/scripts/gatherTranslationKeys.ts @@ -0,0 +1,61 @@ +/* +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"; + +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 (require.main === module) { + main(); +} diff --git a/packages/shared-components/src/ViewWrapper.tsx b/packages/shared-components/src/ViewWrapper.tsx deleted file mode 100644 index 57b81bd5b9..0000000000 --- a/packages/shared-components/src/ViewWrapper.tsx +++ /dev/null @@ -1,52 +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 JSX, useMemo, type ComponentType } from "react"; -import { omitBy, pickBy } from "lodash"; - -import { MockViewModel } from "./MockViewModel"; -import { type ViewModel } from "./ViewModel"; - -interface ViewWrapperProps { - /** - * The component to render, which should accept a `vm` prop of type `V`. - */ - Component: ComponentType<{ vm: V }>; - /** - * The props to pass to the component, which can include both snapshot data and actions. - */ - props: Record; -} - -/** - * A wrapper component that creates a view model instance and passes it to the specified component. - * This is useful for testing components in isolation with a mocked view model and allows to use primitive types in stories. - * - * Props is parsed and split into snapshot and actions. Where values that are functions (`typeof Function`) are considered actions and the rest is considered the snapshot. - * - * @example - * ```tsx - * props={Snapshot&Actions} Component={MyComponent} /> - * ``` - */ -export function ViewWrapper>({ - props, - Component, -}: Readonly>): JSX.Element { - const vm = useMemo(() => { - const isFunction = (value: any): value is typeof Function => typeof value === typeof Function; - const snapshot = omitBy(props, isFunction) as T; - const actions = pickBy(props, isFunction); - - const vm = new MockViewModel(snapshot); - Object.assign(vm, actions); - - return vm as unknown as V; - }, [props]); - - return ; -} diff --git a/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.module.css b/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.module.css index 905719eedf..fc8c26aef7 100644 --- a/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.module.css +++ b/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.module.css @@ -6,7 +6,7 @@ */ .audioPlayer { - padding: var(--cpd-space-4x) var(--cpd-space-3x) var(--cpd-space-3x) var(--cpd-space-3x); + padding: var(--cpd-space-4x) var(--cpd-space-3x) var(--cpd-space-3x) var(--cpd-space-3x) !important; } .mediaInfo { diff --git a/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.stories.tsx b/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.stories.tsx index 869e922dd4..18782f25cb 100644 --- a/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.stories.tsx +++ b/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.stories.tsx @@ -9,18 +9,18 @@ import React, { type JSX } from "react"; import { fn } from "storybook/test"; import type { Meta, StoryFn } from "@storybook/react-vite"; -import { - AudioPlayerView, - type AudioPlayerViewActions, - type AudioPlayerViewSnapshot, - type AudioPlayerViewModel, -} from "./AudioPlayerView"; -import { ViewWrapper } from "../../ViewWrapper"; +import { AudioPlayerView, type AudioPlayerViewActions, type AudioPlayerViewSnapshot } from "./AudioPlayerView"; +import { useMockedViewModel } from "../../useMockedViewModel"; type AudioPlayerProps = AudioPlayerViewSnapshot & AudioPlayerViewActions; -const AudioPlayerViewWrapper = (props: AudioPlayerProps): JSX.Element => ( - Component={AudioPlayerView} props={props} /> -); +const AudioPlayerViewWrapper = ({ togglePlay, onKeyDown, onSeekbarChange, ...rest }: AudioPlayerProps): JSX.Element => { + const vm = useMockedViewModel(rest, { + togglePlay, + onKeyDown, + onSeekbarChange, + }); + return ; +}; export default { title: "Audio/AudioPlayerView", diff --git a/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.test.tsx b/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.test.tsx index 3f08eec28c..018b388f6b 100644 --- a/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.test.tsx +++ b/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.test.tsx @@ -13,7 +13,7 @@ import { fireEvent } from "@testing-library/dom"; import * as stories from "./AudioPlayerView.stories.tsx"; import { AudioPlayerView, type AudioPlayerViewActions, type AudioPlayerViewSnapshot } from "./AudioPlayerView"; -import { MockViewModel } from "../../MockViewModel"; +import { MockViewModel } from "../../viewmodel/MockViewModel.ts"; const { Default, NoMediaName, NoSize, HasError } = composeStories(stories); diff --git a/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.tsx b/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.tsx index f963c8b2cf..29fb02ba34 100644 --- a/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.tsx +++ b/packages/shared-components/src/audio/AudioPlayerView/AudioPlayerView.tsx @@ -7,7 +7,7 @@ import React, { type ChangeEventHandler, type JSX, type KeyboardEventHandler, type MouseEventHandler } from "react"; -import { type ViewModel } from "../../ViewModel"; +import { type ViewModel } from "../../viewmodel/ViewModel"; import { useViewModel } from "../../useViewModel"; import { MediaBody } from "../../message-body/MediaBody"; import { Flex } from "../../utils/Flex"; diff --git a/packages/shared-components/src/audio/AudioPlayerView/__snapshots__/AudioPlayerView.test.tsx.snap b/packages/shared-components/src/audio/AudioPlayerView/__snapshots__/AudioPlayerView.test.tsx.snap index 790ab16870..89b47dfecb 100644 --- a/packages/shared-components/src/audio/AudioPlayerView/__snapshots__/AudioPlayerView.test.tsx.snap +++ b/packages/shared-components/src/audio/AudioPlayerView/__snapshots__/AudioPlayerView.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`AudioPlayerView renders the audio player in default state 1`] = `
@@ -15,7 +15,7 @@ exports[`AudioPlayerView renders the audio player in default state 1`] = `
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index a4df7a5fe9..0365b4b583 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -140,6 +140,7 @@ import { ShareFormat, type SharePayload } from "../../dispatcher/payloads/ShareP import Markdown from "../../Markdown"; import { sanitizeHtmlParams } from "../../Linkify"; import { isOnlyAdmin } from "../../utils/membership"; +import { ModuleApi } from "../../modules/Api.ts"; // legacy export export { default as Views } from "../../Views"; @@ -175,9 +176,11 @@ interface IProps { interface IState { // the master view we are showing. view: Views; - // What the LoggedInView would be showing if visible + // What the LoggedInView would be showing if visible. + // A member of the enum for standard pages or a string for those provided by + // a module. // eslint-disable-next-line camelcase - page_type?: PageType; + page_type?: PageType | string; // The ID of the room we're viewing. This is either populated directly // in the case where we view a room by ID or by RoomView when it resolves // what ID an alias points at. @@ -1921,8 +1924,8 @@ export default class MatrixChat extends React.PureComponent { userId: userId, subAction: params?.action, }); - } else { - logger.info(`Ignoring showScreen for '${screen}'`); + } else if (ModuleApi.instance.navigation.locationRenderers.get(screen)) { + this.setState({ page_type: screen }); } } diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index e006b1d92a..24c1655455 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -34,7 +34,6 @@ import { Action } from "../../dispatcher/actions"; import { type XOR } from "../../@types/common"; import ExtensionsCard from "../views/right_panel/ExtensionsCard"; import MemberListView from "../views/rooms/MemberList/MemberListView"; -import { _t } from "../../languageHandler"; interface BaseProps { overwriteCard?: IRightPanelCard; // used to display a custom card and ignoring the RightPanelStore (used for UserView) @@ -65,7 +64,6 @@ interface IState { export default class RightPanel extends React.Component { public static contextType = MatrixClientContext; declare public context: React.ContextType; - private ref = React.createRef(); public constructor(props: Props) { super(props); @@ -84,7 +82,6 @@ export default class RightPanel extends React.Component { public componentDidMount(): void { this.context.on(RoomStateEvent.Members, this.onRoomStateMember); RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate); - this.ref.current?.focus(); } public componentWillUnmount(): void { @@ -122,13 +119,7 @@ export default class RightPanel extends React.Component { }; private onRightPanelStoreUpdate = (): void => { - const oldPhase = this.state.phase; - const newState = RightPanel.getDerivedStateFromProps(this.props) as IState; - this.setState({ ...newState }); - - if (oldPhase !== newState.phase) { - this.ref.current?.focus(); - } + this.setState({ ...(RightPanel.getDerivedStateFromProps(this.props) as IState) }); }; private onClose = (): void => { @@ -289,14 +280,7 @@ export default class RightPanel extends React.Component { } return ( -