Compare commits
2 Commits
hs/safety-
...
andybalaam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95974b53cb | ||
|
|
093a708b44 |
19
.eslintrc.js
@@ -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
@@ -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"
|
|
||||||
|
|||||||
6
.github/workflows/build.yml
vendored
@@ -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
|
||||||
|
|||||||
4
.github/workflows/build_debian.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
R2_URL: ${{ vars.CF_R2_S3_API }}
|
R2_URL: ${{ vars.CF_R2_S3_API }}
|
||||||
VERSION: ${{ github.ref_name }}
|
VERSION: ${{ github.ref_name }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@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
|
||||||
|
|||||||
6
.github/workflows/build_develop.yml
vendored
@@ -26,9 +26,9 @@ jobs:
|
|||||||
R2_URL: ${{ vars.CF_R2_S3_API }}
|
R2_URL: ${{ vars.CF_R2_S3_API }}
|
||||||
R2_PUBLIC_URL: "https://element-web-develop.element.io"
|
R2_PUBLIC_URL: "https://element-web-develop.element.io"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@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
|
||||||
|
|||||||
2
.github/workflows/deploy.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
SITE: ${{ inputs.site || 'staging.element.io' }}
|
SITE: ${{ inputs.site || 'staging.element.io' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
|
||||||
- name: Load GPG key
|
- name: Load GPG key
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
14
.github/workflows/docker.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
TEST_TAG: vectorim/element-web:test
|
TEST_TAG: vectorim/element-web:test
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@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
|
||||||
|
|||||||
12
.github/workflows/docs.yml
vendored
@@ -17,23 +17,23 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Fetch element-desktop
|
- name: Fetch element-desktop
|
||||||
uses: actions/checkout@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
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|||||||
24
.github/workflows/end-to-end-tests.yaml
vendored
@@ -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
|
||||||
|
|||||||
2
.github/workflows/netlify.yaml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
Exercise caution. Use test accounts.
|
Exercise caution. Use test accounts.
|
||||||
|
|
||||||
- name: 📥 Download artifact
|
- name: 📥 Download artifact
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
uses: actions/download-artifact@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 }}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
6
.github/workflows/release_prepare.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
|||||||
REPOS: matrix-js-sdk element-web element-desktop
|
REPOS: matrix-js-sdk element-web element-desktop
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Element Desktop
|
- name: Checkout Element Desktop
|
||||||
uses: actions/checkout@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
|
||||||
|
|||||||
40
.github/workflows/shared-component-publish.yaml
vendored
@@ -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
|
|
||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
34
.github/workflows/static_analysis.yaml
vendored
@@ -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/*"
|
||||||
|
|||||||
71
.github/workflows/tests.yml
vendored
@@ -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
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
2
.github/workflows/triage-stale.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10
|
- uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10
|
||||||
with:
|
with:
|
||||||
operations-per-run: 100
|
operations-per-run: 100
|
||||||
|
|
||||||
|
|||||||
6
.github/workflows/update-jitsi.yml
vendored
@@ -9,9 +9,9 @@ jobs:
|
|||||||
update:
|
update:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@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
|
||||||
|
|||||||
27
.github/workflows/update-topics.yaml
vendored
@@ -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
@@ -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
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
24
|
22
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
/dist
|
/dist
|
||||||
/lib
|
/lib
|
||||||
/node_modules
|
/node_modules
|
||||||
|
/packages/
|
||||||
/webapp
|
/webapp
|
||||||
/*.log
|
/*.log
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { GlobeIcon } from "@storybook/icons";
|
|||||||
|
|
||||||
// We can't import `shared/i18n.tsx` directly here.
|
// We can't import `shared/i18n.tsx` directly here.
|
||||||
// The storybook addon doesn't seem to benefit the vite config of storybook and we can't resolve the alias in i18n.tsx.
|
// The storybook addon doesn't seem to benefit the vite config of storybook and we can't resolve the alias in i18n.tsx.
|
||||||
import json from "../../../webapp/i18n/languages.json";
|
import json from "../webapp/i18n/languages.json";
|
||||||
const languages = Object.keys(json).filter((lang) => lang !== "default");
|
const languages = Object.keys(json).filter((lang) => lang !== "default");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,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;
|
||||||
@@ -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: {
|
||||||
@@ -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) {
|
||||||
142
CHANGELOG.md
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
@@ -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!
|
||||||
|
|||||||
@@ -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": { },
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
9
knip.ts
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
111
package.json
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
dist/
|
|
||||||
i18n/i18nKeys.d.ts
|
|
||||||
@@ -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",
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
`;
|
|
||||||
@@ -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";
|
|
||||||
@@ -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({});
|
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
`;
|
|
||||||
@@ -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";
|
|
||||||
@@ -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";
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { type KeyboardEvent } from "react";
|
|
||||||
import { renderHook } from "jest-matrix-react";
|
|
||||||
|
|
||||||
import { useListKeyboardNavigation } from "./useListKeyboardNavigation";
|
|
||||||
|
|
||||||
describe("useListKeyDown", () => {
|
|
||||||
let mockList: HTMLUListElement;
|
|
||||||
let mockItems: HTMLElement[];
|
|
||||||
let mockEvent: Partial<KeyboardEvent<HTMLUListElement>>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// Create mock DOM elements
|
|
||||||
mockList = document.createElement("ul");
|
|
||||||
mockItems = [document.createElement("li"), document.createElement("li"), document.createElement("li")];
|
|
||||||
|
|
||||||
// Set up the DOM structure
|
|
||||||
mockItems.forEach((item, index) => {
|
|
||||||
item.setAttribute("tabindex", "0");
|
|
||||||
item.setAttribute("data-testid", `item-${index}`);
|
|
||||||
mockList.appendChild(item);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.body.appendChild(mockList);
|
|
||||||
|
|
||||||
// Mock event object
|
|
||||||
mockEvent = {
|
|
||||||
preventDefault: jest.fn(),
|
|
||||||
key: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mock focus methods
|
|
||||||
mockItems.forEach((item) => {
|
|
||||||
item.focus = jest.fn();
|
|
||||||
item.click = jest.fn();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
document.body.removeChild(mockList);
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
function render(): {
|
|
||||||
current: {
|
|
||||||
listRef: React.RefObject<HTMLUListElement | null>;
|
|
||||||
onKeyDown: React.KeyboardEventHandler<HTMLUListElement>;
|
|
||||||
onFocus: React.FocusEventHandler<HTMLUListElement>;
|
|
||||||
};
|
|
||||||
} {
|
|
||||||
const { result } = renderHook(() => useListKeyboardNavigation());
|
|
||||||
result.current.listRef.current = mockList;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
["Enter", "Enter"],
|
|
||||||
["Space", " "],
|
|
||||||
])("should handle %s key to click active element", (name, key) => {
|
|
||||||
const result = render();
|
|
||||||
|
|
||||||
// Mock document.activeElement
|
|
||||||
Object.defineProperty(document, "activeElement", {
|
|
||||||
value: mockItems[1],
|
|
||||||
configurable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Simulate key press
|
|
||||||
result.current.onKeyDown({
|
|
||||||
...mockEvent,
|
|
||||||
key,
|
|
||||||
} as KeyboardEvent<HTMLUListElement>);
|
|
||||||
|
|
||||||
expect(mockItems[1].click).toHaveBeenCalledTimes(1);
|
|
||||||
expect(mockEvent.preventDefault).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each(
|
|
||||||
// key, finalPosition, startPosition
|
|
||||||
[
|
|
||||||
["ArrowDown", 1, 0],
|
|
||||||
["ArrowUp", 1, 2],
|
|
||||||
["Home", 0, 1],
|
|
||||||
["End", 2, 1],
|
|
||||||
],
|
|
||||||
)("should handle %s to focus the %inth element", (key, finalPosition, startPosition) => {
|
|
||||||
const result = render();
|
|
||||||
mockList.contains = jest.fn().mockReturnValue(true);
|
|
||||||
|
|
||||||
Object.defineProperty(document, "activeElement", {
|
|
||||||
value: mockItems[startPosition],
|
|
||||||
configurable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
result.current.onKeyDown({
|
|
||||||
...mockEvent,
|
|
||||||
key,
|
|
||||||
} as KeyboardEvent<HTMLUListElement>);
|
|
||||||
|
|
||||||
expect(mockItems[finalPosition].focus).toHaveBeenCalledTimes(1);
|
|
||||||
expect(mockEvent.preventDefault).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each([["ArrowDown"], ["ArrowUp"]])("should not handle %s when active element is not in list", (key) => {
|
|
||||||
const result = render();
|
|
||||||
mockList.contains = jest.fn().mockReturnValue(false);
|
|
||||||
|
|
||||||
const outsideElement = document.createElement("button");
|
|
||||||
|
|
||||||
Object.defineProperty(document, "activeElement", {
|
|
||||||
value: outsideElement,
|
|
||||||
configurable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
result.current.onKeyDown({
|
|
||||||
...mockEvent,
|
|
||||||
key,
|
|
||||||
} as KeyboardEvent<HTMLUListElement>);
|
|
||||||
|
|
||||||
// No item should be focused
|
|
||||||
mockItems.forEach((item) => expect(item.focus).not.toHaveBeenCalled());
|
|
||||||
expect(mockEvent.preventDefault).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not prevent default for unhandled keys", () => {
|
|
||||||
const result = render();
|
|
||||||
|
|
||||||
result.current.onKeyDown({
|
|
||||||
...mockEvent,
|
|
||||||
key: "Tab",
|
|
||||||
} as KeyboardEvent<HTMLUListElement>);
|
|
||||||
|
|
||||||
expect(mockEvent.preventDefault).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should focus the first item if list itself is focused", () => {
|
|
||||||
const result = render();
|
|
||||||
result.current.onFocus({ target: mockList } as React.FocusEvent<HTMLUListElement>);
|
|
||||||
expect(mockItems[0].focus).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should focus the selected item if list itself is focused", () => {
|
|
||||||
mockItems[1].setAttribute("aria-selected", "true");
|
|
||||||
const result = render();
|
|
||||||
|
|
||||||
result.current.onFocus({ target: mockList } as React.FocusEvent<HTMLUListElement>);
|
|
||||||
expect(mockItems[1].focus).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
useCallback,
|
|
||||||
useRef,
|
|
||||||
type RefObject,
|
|
||||||
type KeyboardEvent,
|
|
||||||
type KeyboardEventHandler,
|
|
||||||
type FocusEventHandler,
|
|
||||||
type FocusEvent,
|
|
||||||
} from "react";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A hook that provides keyboard navigation for a list of options.
|
|
||||||
*/
|
|
||||||
export function useListKeyboardNavigation(): {
|
|
||||||
listRef: RefObject<HTMLUListElement | null>;
|
|
||||||
onKeyDown: KeyboardEventHandler<HTMLUListElement>;
|
|
||||||
onFocus: FocusEventHandler<HTMLUListElement>;
|
|
||||||
} {
|
|
||||||
const listRef = useRef<HTMLUListElement>(null);
|
|
||||||
|
|
||||||
const onFocus = useCallback((evt: FocusEvent<HTMLUListElement>) => {
|
|
||||||
if (!listRef.current) return;
|
|
||||||
|
|
||||||
if (evt.target === listRef.current) {
|
|
||||||
// By default, focus the selected item
|
|
||||||
let selectedChild = listRef.current?.firstElementChild;
|
|
||||||
|
|
||||||
// If there is a selected item, focus that instead
|
|
||||||
for (const child of listRef.current.children) {
|
|
||||||
if (child.getAttribute("aria-selected") === "true") {
|
|
||||||
selectedChild = child;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(selectedChild as HTMLElement)?.focus();
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onKeyDown = useCallback((evt: KeyboardEvent<HTMLUListElement>) => {
|
|
||||||
const { key } = evt;
|
|
||||||
|
|
||||||
let handled = false;
|
|
||||||
|
|
||||||
switch (key) {
|
|
||||||
case "Enter":
|
|
||||||
case " ": {
|
|
||||||
handled = true;
|
|
||||||
(document.activeElement as HTMLElement).click();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "ArrowDown": {
|
|
||||||
handled = true;
|
|
||||||
const currentFocus = document.activeElement;
|
|
||||||
if (listRef.current?.contains(currentFocus) && currentFocus) {
|
|
||||||
(currentFocus.nextElementSibling as HTMLElement)?.focus();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "ArrowUp": {
|
|
||||||
handled = true;
|
|
||||||
const currentFocus = document.activeElement;
|
|
||||||
if (listRef.current?.contains(currentFocus) && currentFocus) {
|
|
||||||
(currentFocus.previousElementSibling as HTMLElement)?.focus();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "Home": {
|
|
||||||
handled = true;
|
|
||||||
(listRef.current?.firstElementChild as HTMLElement)?.focus();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "End": {
|
|
||||||
handled = true;
|
|
||||||
(listRef.current?.lastElementChild as HTMLElement)?.focus();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handled) {
|
|
||||||
evt.preventDefault();
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
return { listRef, onKeyDown, onFocus };
|
|
||||||
}
|
|
||||||
@@ -1,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";
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.pill {
|
|
||||||
background-color: var(--cpd-color-bg-action-primary-rest);
|
|
||||||
padding: var(--cpd-space-1x) var(--cpd-space-1-5x) var(--cpd-space-1x) var(--cpd-space-1x);
|
|
||||||
border-radius: 99px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
color: var(--cpd-color-text-on-solid-primary);
|
|
||||||
font: var(--cpd-font-body-sm-medium);
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { fn } from "storybook/test";
|
|
||||||
|
|
||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
||||||
import { Pill } from "./Pill";
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
title: "PillInput/Pill",
|
|
||||||
component: Pill,
|
|
||||||
tags: ["autodocs"],
|
|
||||||
args: {
|
|
||||||
label: "Pill",
|
|
||||||
children: <div style={{ width: 20, height: 20, borderRadius: "100%", backgroundColor: "#ccc" }} />,
|
|
||||||
onClick: fn(),
|
|
||||||
},
|
|
||||||
} satisfies Meta<typeof Pill>;
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof meta>;
|
|
||||||
|
|
||||||
export const Default: Story = {};
|
|
||||||
export const WithoutCloseButton: Story = {
|
|
||||||
args: {
|
|
||||||
onClick: undefined,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { composeStories } from "@storybook/react-vite";
|
|
||||||
import { render } from "jest-matrix-react";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import * as stories from "./Pill.stories";
|
|
||||||
|
|
||||||
const { Default, WithoutCloseButton } = composeStories(stories);
|
|
||||||
|
|
||||||
describe("Pill", () => {
|
|
||||||
it("renders the pill", () => {
|
|
||||||
const { container } = render(<Default />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders the pill without close button", () => {
|
|
||||||
const { container } = render(<WithoutCloseButton />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
`;
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.pillInput {
|
|
||||||
background-color: var(--cpd-color-bg-subtle-secondary);
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: var(--cpd-space-2x) var(--cpd-space-3x) var(--cpd-space-2x) var(--cpd-space-3x);
|
|
||||||
/* To match pill height in order to avoid the PillInput to grow when a pill is inserted */
|
|
||||||
min-height: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pillInput:has(.input:focus) {
|
|
||||||
outline: var(--cpd-border-width-1) solid var(--cpd-color-gray-1400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
all: unset;
|
|
||||||
width: 100%;
|
|
||||||
flex: 1;
|
|
||||||
color: var(--cpd-color-text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input::placeholder {
|
|
||||||
color: var(--cpd-color-text-secondary);
|
|
||||||
font: var(--cpd-font-body-md-regular);
|
|
||||||
}
|
|
||||||
|
|
||||||
.largerInput {
|
|
||||||
padding: var(--cpd-space-2x) 0;
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { fn } from "storybook/test";
|
|
||||||
|
|
||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
||||||
import { PillInput } from "./PillInput";
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
title: "PillInput/PillInput",
|
|
||||||
component: PillInput,
|
|
||||||
tags: ["autodocs"],
|
|
||||||
args: {
|
|
||||||
children: (
|
|
||||||
<>
|
|
||||||
<div style={{ minWidth: 162, height: 28, backgroundColor: "#ccc", borderRadius: "99px" }} />
|
|
||||||
<div style={{ minWidth: 162, height: 28, backgroundColor: "#ccc", borderRadius: "99px" }} />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
onChange: fn(),
|
|
||||||
onRemoveChildren: fn(),
|
|
||||||
inputProps: {
|
|
||||||
"placeholder": "Type something...",
|
|
||||||
"aria-label": "pill input",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} satisfies Meta<typeof PillInput>;
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof meta>;
|
|
||||||
|
|
||||||
export const Default: Story = {};
|
|
||||||
export const NoChild: Story = { args: { children: undefined } };
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { render, screen } from "jest-matrix-react";
|
|
||||||
import React from "react";
|
|
||||||
import { composeStories } from "@storybook/react-vite";
|
|
||||||
import userEvent from "@testing-library/user-event";
|
|
||||||
|
|
||||||
import * as stories from "./PillInput.stories";
|
|
||||||
import { PillInput } from "./PillInput";
|
|
||||||
|
|
||||||
const { Default, NoChild } = composeStories(stories);
|
|
||||||
|
|
||||||
describe("PillInput", () => {
|
|
||||||
it("renders the pill input", () => {
|
|
||||||
const { container } = render(<Default />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders only the input without children", () => {
|
|
||||||
const { container } = render(<NoChild />);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calls onRemoveChildren when backspace is pressed and input is empty", async () => {
|
|
||||||
const user = userEvent.setup();
|
|
||||||
const mockOnRemoveChildren = jest.fn();
|
|
||||||
|
|
||||||
render(<PillInput onRemoveChildren={mockOnRemoveChildren} />);
|
|
||||||
|
|
||||||
const input = screen.getByRole("textbox");
|
|
||||||
|
|
||||||
// Focus the input and press backspace (input should be empty by default)
|
|
||||||
await user.click(input);
|
|
||||||
await user.keyboard("{Backspace}");
|
|
||||||
|
|
||||||
expect(mockOnRemoveChildren).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, {
|
|
||||||
type PropsWithChildren,
|
|
||||||
type JSX,
|
|
||||||
useRef,
|
|
||||||
type KeyboardEventHandler,
|
|
||||||
type HTMLAttributes,
|
|
||||||
type HTMLProps,
|
|
||||||
Children,
|
|
||||||
} from "react";
|
|
||||||
import classNames from "classnames";
|
|
||||||
import { omit } from "lodash";
|
|
||||||
import { useMergeRefs } from "react-merge-refs";
|
|
||||||
|
|
||||||
import styles from "./PillInput.module.css";
|
|
||||||
import { Flex } from "../../utils/Flex";
|
|
||||||
|
|
||||||
export interface PillInputProps extends HTMLAttributes<HTMLDivElement> {
|
|
||||||
/**
|
|
||||||
* Callback for when the user presses backspace on an empty input.
|
|
||||||
*/
|
|
||||||
onRemoveChildren?: KeyboardEventHandler;
|
|
||||||
/**
|
|
||||||
* Props to pass to the input element.
|
|
||||||
*/
|
|
||||||
inputProps?: HTMLProps<HTMLInputElement> & { "data-testid"?: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An input component that can contain multiple child elements and an input field.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```tsx
|
|
||||||
* <PillInput>
|
|
||||||
* <div>Child 1</div>
|
|
||||||
* <div>Child 2</div>
|
|
||||||
* </PillInput>
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function PillInput({
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
onRemoveChildren,
|
|
||||||
inputProps,
|
|
||||||
...props
|
|
||||||
}: PropsWithChildren<PillInputProps>): JSX.Element {
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const inputAttributes = omit(inputProps, ["onKeyDown", "ref"]);
|
|
||||||
const ref = useMergeRefs([inputRef, inputProps?.ref]);
|
|
||||||
|
|
||||||
const hasChildren = Children.toArray(children).length > 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
{...props}
|
|
||||||
gap="var(--cpd-space-1x)"
|
|
||||||
direction="column"
|
|
||||||
className={classNames(styles.pillInput, className)}
|
|
||||||
onClick={(evt) => {
|
|
||||||
evt.preventDefault();
|
|
||||||
evt.stopPropagation();
|
|
||||||
inputRef.current?.focus();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{hasChildren && (
|
|
||||||
<Flex gap="var(--cpd-space-1x)" wrap="wrap" align="center">
|
|
||||||
{children}
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
<input
|
|
||||||
ref={ref}
|
|
||||||
autoComplete="off"
|
|
||||||
className={classNames(styles.input, { [styles.largerInput]: hasChildren })}
|
|
||||||
onKeyDown={(evt) => {
|
|
||||||
const value = evt.currentTarget.value.trim();
|
|
||||||
|
|
||||||
// If the input is empty and the user presses backspace, we call the onRemoveChildren handler
|
|
||||||
if (evt.key === "Backspace" && !value) {
|
|
||||||
evt.preventDefault();
|
|
||||||
onRemoveChildren?.(evt);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
inputProps?.onKeyDown?.(evt);
|
|
||||||
}}
|
|
||||||
{...inputAttributes}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://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>
|
|
||||||
`;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { PillInput } from "./PillInput";
|
|
||||||
@@ -1,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");
|
|
||||||
@@ -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 };
|
|
||||||
@@ -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]);
|
|
||||||
}
|
|
||||||
@@ -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!");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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";
|
|
||||||