Compare commits
183 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d4c22d43f3 | |||
| 926bb56723 | |||
| 1dfd92c1eb | |||
| 68e6b0f845 | |||
| d426063a22 | |||
| 09c3afd311 | |||
| f6731ec318 | |||
| 7e04998a58 | |||
| dae5b6b43b | |||
| 8376e43a03 | |||
| e528fefd4c | |||
| 5e87f20cd8 | |||
| 7d9981e5dd | |||
| 1d20a4c5ee | |||
| bc97560532 | |||
| a6316d99d5 | |||
| bea3574b30 | |||
| 0e6bacffed | |||
| 9be323dfd0 | |||
| d6e50598cd | |||
| 41e33a0755 | |||
| 55a7bc5b13 | |||
| b1fdf03236 | |||
| 39464f64ab | |||
| 09598bcb7a | |||
| 6e7dc4cc06 | |||
| 87fd279079 | |||
| 77c41d6789 | |||
| 9e1d0367d0 | |||
| 8fd6e29f2d | |||
| cbcceee970 | |||
| d0a8879971 | |||
| e6e6f87d01 | |||
| cf51b256ce | |||
| 9d973c88f9 | |||
| e33894bed4 | |||
| 0468876aa0 | |||
| c8f1c19517 | |||
| 2598e4ea22 | |||
| ac96ab0d46 | |||
| 949d0dc8a9 | |||
| 0e0e928040 | |||
| c519438dad | |||
| 146e4772ac | |||
| 6cfe197a38 | |||
| a4a44a0c1c | |||
| 2ce2218549 | |||
| a7ddf1c88a | |||
| 1b0863483a | |||
| 8bf2049f8a | |||
| abafde4b64 | |||
| 756e1769ba | |||
| 1e28cd2b49 | |||
| 252546f085 | |||
| a31dcaf157 | |||
| 735d2bd981 | |||
| ccd7f923d0 | |||
| 8f3c1ce05e | |||
| 124afe3bf3 | |||
| 02246020a2 | |||
| b0ee3c11ff | |||
| b7dd05bc97 | |||
| 9c37cdf550 | |||
| 56f6c1ef46 | |||
| b4396f5943 | |||
| 53ce37de9e | |||
| b3188b47be | |||
| 5dc8edcae0 | |||
| 0d1da4ff45 | |||
| c4d6a28473 | |||
| 6838969792 | |||
| 2698ad422e | |||
| c96da5dbf8 | |||
| a2f00b36c5 | |||
| 795bbfc68e | |||
| 34206ff6f6 | |||
| bc7b50f97c | |||
| 3098eba4f2 | |||
| b45488fc84 | |||
| 4db6ff578d | |||
| c219b39c2a | |||
| 4a0e8d661f | |||
| 0fcc4d15c8 | |||
| 1fe35a92cd | |||
| 0f530f636b | |||
| 25f4853d97 | |||
| b653f49119 | |||
| 56b60e845e | |||
| 87509b567a | |||
| 42fe7965d6 | |||
| 34fc921cd3 | |||
| c08775588d | |||
| 87fdf96192 | |||
| 6479b92837 | |||
| e83ddbc98a | |||
| 5f084c28c3 | |||
| da827129c7 | |||
| 7bac218065 | |||
| 4b323f2bd3 | |||
| aa196b046b | |||
| 8599f4b5df | |||
| 2cf79b3fef | |||
| 98dcd10fbe | |||
| f9e718644a | |||
| 2d5f1b3fb7 | |||
| 625595cb8c | |||
| aa073893ab | |||
| b0c81c46ca | |||
| 9cecd52477 | |||
| 3d5749bfc7 | |||
| 8de035ee39 | |||
| cea6e14220 | |||
| 0bc8a9d259 | |||
| ece9230110 | |||
| 2f8e2be09d | |||
| b6046d2120 | |||
| 7039123c46 | |||
| 6e484aeac7 | |||
| 053b2f8845 | |||
| c3755effba | |||
| 5c205350e3 | |||
| 32c7c33e2b | |||
| 736e638e68 | |||
| 7e1590cb0e | |||
| 6562f5ac20 | |||
| e0df420ade | |||
| 7e48f2b6b6 | |||
| a9f4ccaa02 | |||
| 6f00235432 | |||
| 5014f0b411 | |||
| 1415354f2a | |||
| dd8612d76b | |||
| c31444bfda | |||
| 88d4f369eb | |||
| e225c23fba | |||
| 7f39bb61ec | |||
| 75083c2e80 | |||
| 65eb4ce1d3 | |||
| 2d28b79432 | |||
| 6bedb1525d | |||
| 6a233b513a | |||
| 4cd5991cac | |||
| 2f238ed300 | |||
| 15af27b906 | |||
| 55d07e1703 | |||
| b5d8e63c6d | |||
| c8d937655b | |||
| ca3060af69 | |||
| 479b451916 | |||
| b89de61e12 | |||
| 3c6d341357 | |||
| 7f0c0ca599 | |||
| e165d0db1c | |||
| 7007c2095c | |||
| d79becc2a9 | |||
| e3dceb3718 | |||
| 1a77f6126d | |||
| dae1cbf590 | |||
| c1689ad226 | |||
| 03129e8f10 | |||
| 78cb26201b | |||
| 38a0d28453 | |||
| b4ba350770 | |||
| db2e958823 | |||
| 25a8591791 | |||
| 570e263ca1 | |||
| a2e18a61a8 | |||
| 2146ff6f23 | |||
| 115e3cd3d8 | |||
| 155c0b6a0c | |||
| 841f12bd46 | |||
| 6f41ac58bc | |||
| 902cdb0810 | |||
| 4fd752fe29 | |||
| d83a2d2b93 | |||
| fb9d5db6ec | |||
| 0ca23af4a9 | |||
| 5db72f7fab | |||
| d193434b30 | |||
| 1bbe4802e4 | |||
| 169ae025bf | |||
| e666fa33f3 | |||
| c9f375a02b |
@@ -1,11 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
|
||||
extends: [
|
||||
"plugin:matrix-org/babel",
|
||||
"plugin:matrix-org/react",
|
||||
"plugin:matrix-org/a11y",
|
||||
"plugin:storybook/recommended",
|
||||
],
|
||||
extends: ["plugin:matrix-org/babel", "plugin:matrix-org/react", "plugin:matrix-org/a11y"],
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.json"],
|
||||
},
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
/playwright/e2e/settings/encryption-user-tab/ @element-hq/element-crypto-web-reviewers
|
||||
|
||||
|
||||
/src/models/Call.ts @element-hq/element-call-reviewers
|
||||
/src/call-types.ts @element-hq/element-call-reviewers
|
||||
/src/components/views/voip @element-hq/element-call-reviewers
|
||||
/src/models/Call.ts @element-hq/element-call-reviewers
|
||||
/src/call-types.ts @element-hq/element-call-reviewers
|
||||
/src/components/views/voip @element-hq/element-call-reviewers
|
||||
/playwright/e2e/voip/element-call.spec.ts @element-hq/element-call-reviewers
|
||||
|
||||
# Ignore translations as those will be updated by GHA for Localazy download
|
||||
/src/i18n/strings
|
||||
|
||||
@@ -10,8 +10,7 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
# develop pushes and repository_dispatch handled in build_develop.yaml
|
||||
env:
|
||||
# These must be set for fetchdep.sh to get the right branch
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
# This must be set for fetchdep.sh to get the right branch
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
permissions: {} # No permissions required
|
||||
jobs:
|
||||
@@ -45,7 +44,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
# Disable cache on Windows as it is slower than not caching
|
||||
# https://github.com/actions/setup-node/issues/975
|
||||
@@ -56,15 +55,7 @@ jobs:
|
||||
- run: yarn config set network-timeout 300000
|
||||
|
||||
- name: Fetch layered build
|
||||
id: layered_build
|
||||
env:
|
||||
# tell layered.sh to check out the right sha of the JS-SDK & EW, if they were given one
|
||||
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
|
||||
run: |
|
||||
scripts/layered.sh
|
||||
JSSDK_SHA=$(git -C matrix-js-sdk rev-parse --short=12 HEAD)
|
||||
VECTOR_SHA=$(git rev-parse --short=12 HEAD)
|
||||
echo "VERSION=$VECTOR_SHA--js-$JSSDK_SHA" >> $GITHUB_OUTPUT
|
||||
run: ./scripts/layered.sh
|
||||
|
||||
- name: Copy config
|
||||
run: cp element.io/develop/config.json config.json
|
||||
@@ -72,9 +63,7 @@ jobs:
|
||||
- name: Build
|
||||
env:
|
||||
CI_PACKAGE: true
|
||||
VERSION: "${{ steps.layered_build.outputs.VERSION }}"
|
||||
run: |
|
||||
yarn build
|
||||
run: VERSION=$(scripts/get-version-from-git.sh) yarn build
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
@@ -37,14 +37,14 @@ jobs:
|
||||
install: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
cosign sign --yes ${images}
|
||||
|
||||
- name: Update repo description
|
||||
uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4
|
||||
uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5
|
||||
if: github.event_name != 'pull_request'
|
||||
continue-on-error: true
|
||||
with:
|
||||
@@ -141,7 +141,7 @@ jobs:
|
||||
repository: vectorim/element-web
|
||||
|
||||
- name: Repository Dispatch
|
||||
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3
|
||||
uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
repository: element-hq/element-web-pro
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
repository: matrix-org/matrix-js-sdk
|
||||
path: matrix-js-sdk
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
cache: "yarn"
|
||||
cache-dependency-path: element-web/yarn.lock
|
||||
|
||||
@@ -54,21 +54,16 @@ jobs:
|
||||
with:
|
||||
repository: element-hq/element-web
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Fetch layered build
|
||||
id: layered_build
|
||||
env:
|
||||
# tell layered.sh to check out the right sha of the JS-SDK & EW, if they were given one
|
||||
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
|
||||
run: |
|
||||
scripts/layered.sh
|
||||
JSSDK_SHA=$(git -C matrix-js-sdk rev-parse --short=12 HEAD)
|
||||
VECTOR_SHA=$(git rev-parse --short=12 HEAD)
|
||||
echo "VERSION=$VECTOR_SHA--js-$JSSDK_SHA" >> $GITHUB_OUTPUT
|
||||
run: scripts/layered.sh
|
||||
|
||||
- name: Copy config
|
||||
run: cp element.io/develop/config.json config.json
|
||||
@@ -76,9 +71,7 @@ jobs:
|
||||
- name: Build
|
||||
env:
|
||||
CI_PACKAGE: true
|
||||
VERSION: "${{ steps.layered_build.outputs.VERSION }}"
|
||||
run: |
|
||||
yarn build
|
||||
run: VERSION=$(scripts/get-version-from-git.sh) yarn build
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
@@ -89,7 +82,7 @@ jobs:
|
||||
|
||||
- name: Calculate runner variables
|
||||
id: runner-vars
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
const numRunners = parseInt(process.env.NUM_RUNNERS, 10);
|
||||
@@ -140,7 +133,7 @@ jobs:
|
||||
name: webapp
|
||||
path: webapp
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
cache: "yarn"
|
||||
cache-dependency-path: yarn.lock
|
||||
@@ -154,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
|
||||
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
@@ -207,7 +200,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
repository: element-hq/element-web
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
if: inputs.skip != true
|
||||
with:
|
||||
cache: "yarn"
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
name: Tidy closed issues
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
id: main
|
||||
with:
|
||||
# PAT needed as the GITHUB_TOKEN won't be able to see cross-references from other orgs (matrix-org)
|
||||
@@ -142,7 +142,7 @@ jobs:
|
||||
});
|
||||
}
|
||||
}
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
name: Close duplicate as Not Planned
|
||||
if: steps.main.outputs.closeAsNotPlanned
|
||||
with:
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
URL: "https://github.com/pulls?q=is%3Apr+is%3Aopen+repo%3Amatrix-org%2Fmatrix-js-sdk+repo%3Amatrix-org%2Fmatrix-react-sdk+repo%3Aelement-hq%2Felement-web+repo%3Aelement-hq%2Felement-desktop+review-requested%3A%40me+sort%3Aupdated-desc+"
|
||||
RELEASE_BLOCKERS_URL: "https://github.com/pulls?q=is%3Aopen+repo%3Amatrix-org%2Fmatrix-js-sdk+repo%3Amatrix-org%2Fmatrix-react-sdk+repo%3Aelement-hq%2Felement-web+repo%3Aelement-hq%2Felement-desktop+sort%3Aupdated-desc+label%3AX-Release-Blocker+"
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
env:
|
||||
HS_URL: ${{ secrets.BETABOT_HS_URL }}
|
||||
ROOM_ID: ${{ secrets.ROOM_ID }}
|
||||
|
||||
@@ -8,7 +8,7 @@ jobs:
|
||||
name: Check PR base branch
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
const baseBranch = context.payload.pull_request.base.ref;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Publish shared component npm package
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
|
||||
concurrency: release
|
||||
jobs:
|
||||
publish:
|
||||
name: "Publish"
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: 🧮 Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- name: 🔧 Set up node environment
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version-file: ".node-version"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.ELEMENT_NPM_TOKEN }}
|
||||
|
||||
- name: 🛠️ Setup
|
||||
# When running `install` it also calls the `prepare` step which generates
|
||||
# a build
|
||||
run: yarn --cwd packages/shared-components install --pure-lockfile
|
||||
|
||||
- name: 🚀 Publish to npm
|
||||
working-directory: packages/shared-components
|
||||
run: npm publish --access public --provenance
|
||||
@@ -26,45 +26,51 @@ jobs:
|
||||
persist-credentials: false
|
||||
repository: element-hq/element-web
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install element web dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build Element Web resources
|
||||
# Needed to prepare language files
|
||||
run: "yarn build:res"
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: packages/shared-components
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Get installed Playwright version
|
||||
working-directory: packages/shared-components
|
||||
id: playwright
|
||||
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
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }}-onlyshell
|
||||
|
||||
- name: Install Playwright browsers
|
||||
working-directory: packages/shared-components
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
run: "yarn playwright install --with-deps --only-shell"
|
||||
|
||||
- name: Build Element Web resources
|
||||
# Needed to prepare language files
|
||||
run: "yarn build:res"
|
||||
|
||||
- name: Build storybook dependencies
|
||||
# When the first test is ran, it will fail because the dependencies are not yet built.
|
||||
# This step is to ensure that the dependencies are built before running the tests.
|
||||
run: "yarn test:storybook:ci"
|
||||
run: "yarn --cwd packages/shared-components test:storybook:ci"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run Visual tests
|
||||
run: "yarn test:storybook:ci"
|
||||
run: "yarn --cwd packages/shared-components test:storybook:ci"
|
||||
|
||||
- name: Upload received images & diffs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: received-images
|
||||
path: playwright/shared-component-received
|
||||
path: packages/shared-components/playwright/shared-component-received
|
||||
|
||||
@@ -12,8 +12,7 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# These must be set for fetchdep.sh to get the right branch
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
# This must be set for fetchdep.sh to get the right branch
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
|
||||
permissions: {} # No permissions required
|
||||
@@ -25,7 +24,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
@@ -36,6 +35,16 @@ jobs:
|
||||
- name: Typecheck
|
||||
run: "yarn run lint:types"
|
||||
|
||||
- name: Build Element Web resources
|
||||
# Needed to prepare language files for shared components
|
||||
run: "yarn build:res"
|
||||
|
||||
- name: Install Shared Component Dependencies
|
||||
run: "yarn --cwd packages/shared-components install"
|
||||
|
||||
- name: Typecheck Shared Components
|
||||
run: "yarn --cwd packages/shared-components run lint:types"
|
||||
|
||||
i18n_lint:
|
||||
name: "i18n Check"
|
||||
uses: matrix-org/matrix-web-i18n/.github/workflows/i18n_check.yml@main
|
||||
@@ -70,7 +79,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
@@ -82,13 +91,23 @@ jobs:
|
||||
- name: Run Linter
|
||||
run: "yarn run lint:js"
|
||||
|
||||
- name: Build Element Web resources
|
||||
# Needed to prepare language files for shared components
|
||||
run: "yarn build:res"
|
||||
|
||||
- name: Install Shared Component Deps
|
||||
run: "yarn --cwd packages/shared-components install --frozen-lockfile"
|
||||
|
||||
- name: Run Linter
|
||||
run: "yarn --cwd packages/shared-components run lint:js"
|
||||
|
||||
style_lint:
|
||||
name: "Style Lint"
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
@@ -106,7 +125,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
@@ -124,7 +143,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
repository: ${{ inputs.matrix-js-sdk-sha && 'element-hq/element-web' || github.repository }}
|
||||
|
||||
- name: Yarn cache
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
cache: "yarn"
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
JS_SDK_GITHUB_BASE_REF: ${{ inputs.matrix-js-sdk-sha }}
|
||||
|
||||
- name: Jest Cache
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||
with:
|
||||
path: /tmp/jest_cache
|
||||
key: ${{ hashFiles('**/yarn.lock') }}
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
|
||||
- name: Skip SonarCloud in merge queue
|
||||
if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true'
|
||||
uses: guibranco/github-status-action-v2@741ea90ba6c3ca76fe0d43ba11a90cda97d5e685
|
||||
uses: guibranco/github-status-action-v2@5530c593759f489bba08272e96986ffc571c1ea1
|
||||
with:
|
||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
state: success
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor') ||
|
||||
contains(github.event.issue.labels.*.name, 'A-Element-Call')
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'good first issue') ||
|
||||
contains(github.event.issue.labels.*.name, 'Hacktoberfest')
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
|
||||
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10
|
||||
with:
|
||||
operations-per-run: 100
|
||||
|
||||
|
||||
@@ -5,44 +5,25 @@ on:
|
||||
types: [unlabeled]
|
||||
permissions: {}
|
||||
jobs:
|
||||
Move_Unabeled_Issue_On_Project_Board:
|
||||
move_no_longer_needs_info_issues:
|
||||
name: Move no longer X-Needs-Info issues to Triaged
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
repository-projects: read
|
||||
if: >
|
||||
${{
|
||||
!contains(github.event.issue.labels.*.name, 'X-Needs-Info') }}
|
||||
env:
|
||||
BOARD_NAME: "Issue triage"
|
||||
OWNER: ${{ github.repository_owner }}
|
||||
REPO: ${{ github.event.repository.name }}
|
||||
ISSUE: ${{ github.event.issue.number }}
|
||||
!contains(github.event.issue.labels.*.name, 'X-Needs-Info')
|
||||
steps:
|
||||
- name: Check if issue is already in "${{ env.BOARD_NAME }}"
|
||||
run: |
|
||||
json=$(curl -s -H 'Content-Type: application/json' -H "Authorization: bearer ${{ secrets.GITHUB_TOKEN }}" -X POST -d '{"query": "query($issue: Int!, $owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { issue(number: $issue) { projectCards { nodes { project { name } isArchived } } } } } ", "variables" : "{ \"issue\": '${ISSUE}', \"owner\": \"'${OWNER}'\", \"repo\": \"'${REPO}'\" }" }' https://api.github.com/graphql)
|
||||
if echo $json | jq '.data.repository.issue.projectCards.nodes | length'; then
|
||||
if [[ $(echo $json | jq '.data.repository.issue.projectCards.nodes[0].project.name') =~ "${BOARD_NAME}" ]]; then
|
||||
if [[ $(echo $json | jq '.data.repository.issue.projectCards.nodes[0].isArchived') == 'true' ]]; then
|
||||
echo "Issue is already in Project '$BOARD_NAME', but is archived - skipping workflow";
|
||||
echo "SKIP_ACTION=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "Issue is already in Project '$BOARD_NAME', proceeding";
|
||||
echo "ALREADY_IN_BOARD=true" >> $GITHUB_ENV
|
||||
fi
|
||||
else
|
||||
echo "Issue is not in project '$BOARD_NAME', cancelling this workflow"
|
||||
echo "ALREADY_IN_BOARD=false" >> $GITHUB_ENV
|
||||
fi
|
||||
fi
|
||||
- name: Move issue
|
||||
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36
|
||||
if: ${{ env.ALREADY_IN_BOARD == 'true' && env.SKIP_ACTION != 'true' }}
|
||||
- id: set_fields
|
||||
uses: nipe0324/update-project-v2-item-field@c4af58452d1c5a788c1ea4f20e073fa722ec4a6b #v2.0.2
|
||||
with:
|
||||
project: Issue triage
|
||||
column: Triaged
|
||||
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
project-url: ${{ env.PROJECT_URL }}
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
skip-update-script: |
|
||||
const isIssue = item.type === 'ISSUE'
|
||||
const status = item.fieldValues['Status']
|
||||
return !isIssue || status !== 'Needs info'
|
||||
field-name: Status
|
||||
field-value: "Triaged"
|
||||
env:
|
||||
PROJECT_URL: https://github.com/orgs/element-hq/projects/120
|
||||
|
||||
remove_Z-Labs_label:
|
||||
name: Remove Z-Labs label when features behind labs flags are removed
|
||||
@@ -62,7 +43,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'A-Element-Call')) &&
|
||||
contains(github.event.issue.labels.*.name, 'Z-Labs')
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.removeLabel({
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
environment: Matrix
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
env:
|
||||
HS_URL: ${{ secrets.BETABOT_HS_URL }}
|
||||
LOBBY_ROOM_ID: ${{ secrets.ROOM_ID }}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
/key.pem
|
||||
/lib
|
||||
/node_modules
|
||||
/packages/
|
||||
/webapp
|
||||
/.npmrc
|
||||
/*.log
|
||||
@@ -34,3 +33,6 @@ electron/pub
|
||||
|
||||
*storybook.log
|
||||
storybook-static
|
||||
|
||||
/packages/shared-components/node_modules
|
||||
/packages/shared-components/dist
|
||||
|
||||
@@ -1,3 +1,77 @@
|
||||
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)
|
||||
================================================================================================
|
||||
## 🦖 Deprecations
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# syntax=docker.io/docker/dockerfile:1.17-labs@sha256:9187104f31e3a002a8a6a3209ea1f937fb7486c093cbbde1e14b0fa0d7e4f1b5
|
||||
# syntax=docker.io/docker/dockerfile:1.19-labs@sha256:dce1c693ef318bca08c964ba3122ae6248e45a1b96d65c4563c8dc6fe80349a2
|
||||
|
||||
# Builder
|
||||
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:f7f28d1962d93cc096ea6327378d990284757fec281ce48e42436e7b4b167fa2 AS builder
|
||||
FROM --platform=$BUILDPLATFORM node:22-bullseye@sha256:cd951bb228ddf85882951d4bf4acefc5314eb2eb66eee002256f4bb17c2293e3 AS builder
|
||||
|
||||
# Support custom branch of the js-sdk. This also helps us build images of element-web develop.
|
||||
ARG USE_CUSTOM_SDKS=false
|
||||
@@ -19,7 +19,7 @@ RUN /src/scripts/docker-package.sh
|
||||
RUN cp /src/config.sample.json /src/webapp/config.json
|
||||
|
||||
# App
|
||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:0d019e980f83728002de7a6d8819d0d4af7179046d3946b8b37749953fbb28e6
|
||||
FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:304079937327a6490d5c89df47c8951d76f05b346d4c6e3b10cba2e266cd4904
|
||||
|
||||
# Need root user to install packages & manipulate the usr directory
|
||||
USER root
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# MVVM
|
||||
|
||||
_Deprecated_, see [MVVM.md](./MVVM.md) for the current version.
|
||||
|
||||
General description of the pattern can be found [here](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel). But the gist of it is that you divide your code into three sections:
|
||||
|
||||
1. Model: This is where the business logic and data resides.
|
||||
2. View Model: This code exists to provide the logic necessary for the UI. It directly uses the Model code.
|
||||
3. View: This is the UI code itself and depends on the view model.
|
||||
|
||||
If you do MVVM right, your view should be dumb i.e it gets data from the view model and merely displays it.
|
||||
|
||||
### Practical guidelines for MVVM in element-web
|
||||
|
||||
#### Model
|
||||
|
||||
This is anywhere your data or business logic comes from. If your view model is accessing something simple exposed from `matrix-js-sdk`, then the sdk is your model. If you're using something more high level in element-web to get your data/logic (eg: `MemberListStore`), then that becomes your model.
|
||||
|
||||
#### View Model
|
||||
|
||||
1. View model is always a custom react hook named like `useFooViewModel()`.
|
||||
2. The return type of your view model (known as view state) must be defined as a typescript interface:
|
||||
```ts
|
||||
inteface FooViewState {
|
||||
somethingUseful: string;
|
||||
somethingElse: BarType;
|
||||
update: () => Promise<void>
|
||||
...
|
||||
}
|
||||
```
|
||||
3. Any react state that your UI needs must be in the view model.
|
||||
|
||||
#### View
|
||||
|
||||
1. Views are simple react components (eg: `FooView`).
|
||||
2. Views usually start by calling the view model hook, eg:
|
||||
```tsx
|
||||
const FooView: React.FC<IProps> = (props: IProps) => {
|
||||
const vm = useFooViewModel();
|
||||
....
|
||||
return(
|
||||
<div>
|
||||
{vm.somethingUseful}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
3. Views are also allowed to accept the view model as a prop, eg:
|
||||
```tsx
|
||||
const FooView: React.FC<IProps> = ({ vm }: IProps) => {
|
||||
....
|
||||
return(
|
||||
<div>
|
||||
{vm.somethingUseful}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
4. Multiple views can share the same view model if necessary.
|
||||
|
||||
### Benefits
|
||||
|
||||
1. MVVM forces a separation of concern i.e we will no longer have large react components that have a lot of state and rendering code mixed together. This improves code readability and makes it easier to introduce changes.
|
||||
2. Introduces the possibility of code reuse. You can reuse an old view model with a new view or vice versa.
|
||||
3. Adding to the point above, in future you could import element-web view models to your project and supply your own views thus creating something similar to the [hydrogen sdk](https://github.com/element-hq/hydrogen-web/blob/master/doc/SDK.md).
|
||||
|
||||
### Example
|
||||
|
||||
We started experimenting with MVVM in the redesigned memberlist, you can see the code [here](https://github.com/vector-im/element-web/blob/develop/src/components/views/rooms/MemberList/MemberListView.tsx).
|
||||
@@ -10,58 +10,80 @@ If you do MVVM right, your view should be dumb i.e it gets data from the view mo
|
||||
|
||||
### Practical guidelines for MVVM in element-web
|
||||
|
||||
A first documentation and implementation of MVVM was done in [MVVM-v1.md](MVVM-v1.md). This v1 version is now deprecated and this document describes the current implementation.
|
||||
|
||||
#### Model
|
||||
|
||||
This is anywhere your data or business logic comes from. If your view model is accessing something simple exposed from `matrix-js-sdk`, then the sdk is your model. If you're using something more high level in element-web to get your data/logic (eg: `MemberListStore`), then that becomes your model.
|
||||
|
||||
#### View Model
|
||||
|
||||
1. View model is always a custom react hook named like `useFooViewModel()`.
|
||||
2. The return type of your view model (known as view state) must be defined as a typescript interface:
|
||||
```ts
|
||||
inteface FooViewState {
|
||||
somethingUseful: string;
|
||||
somethingElse: BarType;
|
||||
update: () => Promise<void>
|
||||
...
|
||||
}
|
||||
```
|
||||
3. Any react state that your UI needs must be in the view model.
|
||||
|
||||
#### View
|
||||
|
||||
1. Views are simple react components (eg: `FooView`).
|
||||
2. Views usually start by calling the view model hook, eg:
|
||||
1. Located in [`shared-components`](https://github.com/element-hq/element-web/tree/develop/packages/shared-components). Develop it in storybook!
|
||||
2. Views are simple react components (eg: `FooView`).
|
||||
3. Views use [useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore) internally where the view model is the external store.
|
||||
4. Views should define the interface of the view model they expect:
|
||||
|
||||
```tsx
|
||||
const FooView: React.FC<IProps> = (props: IProps) => {
|
||||
const vm = useFooViewModel();
|
||||
....
|
||||
return(
|
||||
<div>
|
||||
{vm.somethingUseful}
|
||||
</div>
|
||||
);
|
||||
// Snapshot is the return type of your view model
|
||||
interface FooViewSnapshot {
|
||||
value: string;
|
||||
}
|
||||
|
||||
// To call function on the view model
|
||||
interface FooViewActions {
|
||||
doSomething: () => void;
|
||||
}
|
||||
|
||||
// ViewModel is a type defining the methods needed for `useSyncExternalStore`
|
||||
// https://github.com/element-hq/element-web/blob/develop/packages/shared-components/src/ViewModel.ts
|
||||
type FooViewModel = ViewModel<FooViewSnapshot> & FooViewActions;
|
||||
|
||||
interface FooViewProps {
|
||||
vm: FooViewModel;
|
||||
}
|
||||
|
||||
function FooView({ vm }: FooViewProps) {
|
||||
// useViewModel is a helper function that uses useSyncExternalStore under the hood
|
||||
const { value } = useViewModel(vm);
|
||||
return (
|
||||
<button type="button" onClick={() => vm.doSomething()}>
|
||||
{value}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
3. Views are also allowed to accept the view model as a prop, eg:
|
||||
```tsx
|
||||
const FooView: React.FC<IProps> = ({ vm }: IProps) => {
|
||||
....
|
||||
return(
|
||||
<div>
|
||||
{vm.somethingUseful}
|
||||
</div>
|
||||
);
|
||||
|
||||
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)
|
||||
|
||||
#### View Model
|
||||
|
||||
1. A View model is a class extending [`BaseViewModel`](https://github.com/element-hq/element-web/blob/develop/src/viewmodels/base/BaseViewModel.ts).
|
||||
2. Implements the interface defined in the view (e.g `FooViewModel` in the example above).
|
||||
3. View models define a snapshot type that defines the data the view will consume. The snapshot is immutable and can only be changed by calling `this.snapshot.set(...)` in the view model. This will trigger a re-render in the view.
|
||||
|
||||
```ts
|
||||
interface Props {
|
||||
propsValue: string;
|
||||
}
|
||||
|
||||
class FooViewModel extends BaseViewModel<FooViewSnapshot, Props> implements FooViewModel {
|
||||
constructor(props: Props) {
|
||||
// Call super with initial snapshot
|
||||
super(props, { value: "initial" });
|
||||
}
|
||||
|
||||
public doSomething() {
|
||||
// Call this.snapshot.set to update the snapshot
|
||||
this.snapshot.set({ value: "changed" });
|
||||
}
|
||||
}
|
||||
```
|
||||
4. Multiple views can share the same view model if necessary.
|
||||
|
||||
4. A full example is available [here](https://github.com/element-hq/element-web/blob/develop/src/viewmodels/audio/AudioPlayerViewModel.ts)
|
||||
|
||||
### Benefits
|
||||
|
||||
1. MVVM forces a separation of concern i.e we will no longer have large react components that have a lot of state and rendering code mixed together. This improves code readability and makes it easier to introduce changes.
|
||||
2. Introduces the possibility of code reuse. You can reuse an old view model with a new view or vice versa.
|
||||
3. Adding to the point above, in future you could import element-web view models to your project and supply your own views thus creating something similar to the [hydrogen sdk](https://github.com/element-hq/hydrogen-web/blob/master/doc/SDK.md).
|
||||
|
||||
### Example
|
||||
|
||||
We started experimenting with MVVM in the redesigned memberlist, you can see the code [here](https://github.com/vector-im/element-web/blob/develop/src/components/views/rooms/MemberList/MemberListView.tsx).
|
||||
|
||||
@@ -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. Untar the tarball on your web server
|
||||
1. Move (or symlink) the `element-x.x.x` directory to an appropriate name
|
||||
1. Configure the correct caching headers in your webserver (see below)
|
||||
1. Configure the correct caching headers in your webserver (see [README.md](../README.md#caching-requirements))
|
||||
1. Configure the app by copying `config.sample.json` to `config.json` and
|
||||
modifying it. See the [configuration docs](config.md) for details.
|
||||
1. Enter the URL into your browser and log into Element!
|
||||
|
||||
@@ -17,7 +17,7 @@ const config: Config = {
|
||||
// This is needed to be able to load dual CJS/ESM WASM packages e.g. rust crypto & matrix-wywiwyg
|
||||
customExportConditions: ["browser", "node"],
|
||||
},
|
||||
testMatch: ["<rootDir>/test/**/*-test.[tj]s?(x)", "<rootDir>/src/shared-components/**/*.test.[t]s?(x)"],
|
||||
testMatch: ["<rootDir>/test/**/*-test.[tj]s?(x)", "<rootDir>/packages/*/src/**/*.test.[t]s?(x)"],
|
||||
globalSetup: "<rootDir>/test/globalSetup.ts",
|
||||
setupFiles: ["jest-canvas-mock", "web-streams-polyfill/polyfill"],
|
||||
setupFilesAfterEnv: ["<rootDir>/test/setupTests.ts"],
|
||||
@@ -41,9 +41,12 @@ const config: Config = {
|
||||
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
|
||||
"^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
|
||||
},
|
||||
transformIgnorePatterns: ["/node_modules/(?!(mime|matrix-js-sdk)).+$"],
|
||||
transformIgnorePatterns: [
|
||||
"/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs)).+$",
|
||||
],
|
||||
collectCoverageFrom: [
|
||||
"<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
|
||||
// not available in that contest. So, turn off coverage instrumentation for it.
|
||||
"!<rootDir>/src/utils/SessionLock.ts",
|
||||
|
||||
@@ -19,6 +19,8 @@ export default {
|
||||
"src/hooks/useTimeout.ts",
|
||||
"src/components/views/elements/InfoTooltip.tsx",
|
||||
"src/components/views/elements/StyledCheckbox.tsx",
|
||||
|
||||
"packages/**/*",
|
||||
],
|
||||
ignoreDependencies: [
|
||||
// Required for `action-validator`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "element-web",
|
||||
"version": "1.12.0",
|
||||
"version": "1.12.3",
|
||||
"description": "Element: the future of secure communication",
|
||||
"author": "New Vector Ltd.",
|
||||
"repository": {
|
||||
@@ -29,7 +29,7 @@
|
||||
"UserFriendlyError"
|
||||
],
|
||||
"scripts": {
|
||||
"i18n": "matrix-gen-i18n && yarn i18n:sort && yarn i18n:lint",
|
||||
"i18n": "matrix-gen-i18n src res packages/shared-components && yarn i18n:sort && yarn i18n:lint",
|
||||
"i18n:sort": "jq --sort-keys '.' src/i18n/strings/en_EN.json > src/i18n/strings/en_EN.json.tmp && mv src/i18n/strings/en_EN.json.tmp src/i18n/strings/en_EN.json",
|
||||
"i18n:lint": "matrix-i18n-lint && prettier --log-level=silent --write src/i18n/strings/ --ignore-path /dev/null",
|
||||
"i18n:diff": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && yarn i18n && matrix-compare-i18n-files src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json",
|
||||
@@ -65,21 +65,16 @@
|
||||
"coverage": "yarn test --coverage",
|
||||
"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",
|
||||
"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"
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"resolutions": {
|
||||
"**/pretty-format/react-is": "19.1.1",
|
||||
"@playwright/test": "1.54.2",
|
||||
"@types/react": "19.1.12",
|
||||
"@types/react-dom": "19.1.9",
|
||||
"**/pretty-format/react-is": "19.2.0",
|
||||
"@playwright/test": "1.56.0",
|
||||
"@types/react": "19.2.2",
|
||||
"@types/react-dom": "19.2.1",
|
||||
"oidc-client-ts": "3.3.0",
|
||||
"jwt-decode": "4.0.0",
|
||||
"caniuse-lite": "1.0.30001724",
|
||||
"caniuse-lite": "1.0.30001750",
|
||||
"testcontainers": "^11.0.0",
|
||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
|
||||
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
|
||||
@@ -98,7 +93,7 @@
|
||||
"@types/png-chunks-extract": "^1.0.2",
|
||||
"@vector-im/compound-design-tokens": "^6.0.0",
|
||||
"@vector-im/compound-web": "^8.1.2",
|
||||
"@vector-im/matrix-wysiwyg": "2.39.0",
|
||||
"@vector-im/matrix-wysiwyg": "2.40.0",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
"@zxcvbn-ts/language-common": "^3.0.4",
|
||||
"@zxcvbn-ts/language-en": "^3.0.2",
|
||||
@@ -116,7 +111,7 @@
|
||||
"emojibase-regex": "15.3.2",
|
||||
"escape-html": "^1.0.3",
|
||||
"file-saver": "^2.0.5",
|
||||
"filesize": "11.0.2",
|
||||
"filesize": "11.0.13",
|
||||
"github-markdown-css": "^5.5.1",
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"highlight.js": "^11.3.1",
|
||||
@@ -127,6 +122,7 @@
|
||||
"jsrsasign": "^11.0.0",
|
||||
"jszip": "^3.7.0",
|
||||
"katex": "^0.16.0",
|
||||
"linkify-html": "4.3.2",
|
||||
"linkify-react": "4.3.2",
|
||||
"linkify-string": "4.3.2",
|
||||
"linkifyjs": "4.3.2",
|
||||
@@ -134,7 +130,7 @@
|
||||
"maplibre-gl": "^5.0.0",
|
||||
"matrix-encrypt-attachment": "^1.0.3",
|
||||
"matrix-events-sdk": "0.0.1",
|
||||
"matrix-js-sdk": "38.3.0",
|
||||
"matrix-js-sdk": "39.1.2",
|
||||
"matrix-widget-api": "^1.10.0",
|
||||
"memoize-one": "^6.0.0",
|
||||
"mime": "^4.0.4",
|
||||
@@ -142,7 +138,7 @@
|
||||
"opus-recorder": "^8.0.3",
|
||||
"pako": "^2.0.3",
|
||||
"png-chunks-extract": "^1.0.0",
|
||||
"posthog-js": "1.261.0",
|
||||
"posthog-js": "1.275.1",
|
||||
"qrcode": "1.5.4",
|
||||
"re-resizable": "6.11.2",
|
||||
"react": "^19.0.0",
|
||||
@@ -150,6 +146,7 @@
|
||||
"react-blurhash": "^0.3.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-focus-lock": "^2.5.1",
|
||||
"react-merge-refs": "^3.0.2",
|
||||
"react-string-replace": "^1.1.1",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"react-virtuoso": "^4.14.0",
|
||||
@@ -159,7 +156,7 @@
|
||||
"tar-js": "^0.3.0",
|
||||
"temporal-polyfill": "^0.3.0",
|
||||
"ua-parser-js": "1.0.40",
|
||||
"uuid": "^11.0.0",
|
||||
"uuid": "^13.0.0",
|
||||
"what-input": "^5.2.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -184,19 +181,14 @@
|
||||
"@babel/preset-typescript": "^7.12.7",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@casualbot/jest-sonar-reporter": "2.2.7",
|
||||
"@element-hq/element-call-embedded": "0.15.0",
|
||||
"@element-hq/element-web-playwright-common": "^1.4.6",
|
||||
"@element-hq/element-call-embedded": "0.16.0",
|
||||
"@element-hq/element-web-playwright-common": "^2.0.0",
|
||||
"@peculiar/webcrypto": "^1.4.3",
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@principalstudio/html-webpack-inject-preload": "^1.2.7",
|
||||
"@rrweb/types": "^2.0.0-alpha.18",
|
||||
"@sentry/webpack-plugin": "^4.0.0",
|
||||
"@storybook/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",
|
||||
"@storybook/react-vite": "^9.1.10",
|
||||
"@stylistic/eslint-plugin": "^5.0.0",
|
||||
"@svgr/webpack": "^8.0.0",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
@@ -223,16 +215,15 @@
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/pako": "^2.0.0",
|
||||
"@types/qrcode": "^1.3.5",
|
||||
"@types/react": "19.1.12",
|
||||
"@types/react": "19.2.2",
|
||||
"@types/react-beautiful-dnd": "^13.0.0",
|
||||
"@types/react-dom": "19.1.9",
|
||||
"@types/react-dom": "19.2.1",
|
||||
"@types/react-transition-group": "^4.4.0",
|
||||
"@types/sanitize-html": "2.16.0",
|
||||
"@types/sdp-transform": "^2.4.10",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/tar-js": "^0.3.5",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.19.0",
|
||||
"@typescript-eslint/parser": "^8.19.0",
|
||||
"babel-jest": "^29.0.0",
|
||||
@@ -250,15 +241,14 @@
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
"eslint-plugin-deprecate": "0.8.5",
|
||||
"eslint-plugin-deprecate": "0.8.7",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-jest": "^28.0.0",
|
||||
"eslint-plugin-jest": "^29.0.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-matrix-org": "^2.0.2",
|
||||
"eslint-plugin-matrix-org": "^3.0.0",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-react-compiler": "^19.0.0-beta-df7b47d-20241124",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-storybook": "^9.0.12",
|
||||
"eslint-plugin-react-hooks": "^7.0.0",
|
||||
"eslint-plugin-unicorn": "^56.0.0",
|
||||
"express": "^5.0.0",
|
||||
"fake-indexeddb": "^6.0.0",
|
||||
@@ -271,7 +261,6 @@
|
||||
"jest": "^29.6.2",
|
||||
"jest-canvas-mock": "^2.5.2",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-image-snapshot": "^6.5.1",
|
||||
"jest-mock": "^29.6.2",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"jsqr": "^1.4.0",
|
||||
@@ -300,7 +289,7 @@
|
||||
"rimraf": "^6.0.0",
|
||||
"semver": "^7.5.2",
|
||||
"source-map-loader": "^5.0.0",
|
||||
"storybook": "^9.0.12",
|
||||
"storybook": "^9.1.10",
|
||||
"stylelint": "^16.23.0",
|
||||
"stylelint-config-standard": "^39.0.0",
|
||||
"stylelint-scss": "^6.0.0",
|
||||
@@ -310,8 +299,6 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "5.8.3",
|
||||
"util": "^0.12.5",
|
||||
"vite": "^7.0.1",
|
||||
"vite-plugin-node-polyfills": "^0.24.0",
|
||||
"web-streams-polyfill": "^4.0.0",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-bundle-analyzer": "^4.8.0",
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
module.exports = {
|
||||
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
|
||||
extends: [
|
||||
"plugin:matrix-org/babel",
|
||||
"plugin:matrix-org/react",
|
||||
"plugin:matrix-org/a11y",
|
||||
"plugin:storybook/recommended",
|
||||
],
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.json"],
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
rules: {
|
||||
// Bind or arrow functions in props causes performance issues (but we
|
||||
// currently use them in some places).
|
||||
// It's disabled here, but we should using it sparingly.
|
||||
"react/jsx-no-bind": "off",
|
||||
"react/jsx-key": ["error"],
|
||||
"matrix-org/require-copyright-header": "error",
|
||||
"react-compiler/react-compiler": "error",
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["src/**/*.{ts,tsx}", "test/**/*.{ts,tsx}"],
|
||||
extends: ["plugin:matrix-org/typescript", "plugin:matrix-org/react"],
|
||||
rules: {
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"error",
|
||||
{
|
||||
allowExpressions: true,
|
||||
},
|
||||
],
|
||||
|
||||
// Remove Babel things manually due to override limitations
|
||||
"@babel/no-invalid-this": ["off"],
|
||||
|
||||
// We're okay being explicit at the moment
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
// We disable this while we're transitioning
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
// We'd rather not do this but we do
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
// We're okay with assertion errors when we ask for them
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-empty-object-type": [
|
||||
"error",
|
||||
{
|
||||
// We do this sometimes to brand interfaces
|
||||
allowInterfaces: "with-single-extends",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
dist/
|
||||
@@ -12,7 +12,7 @@ import { GlobeIcon } from "@storybook/icons";
|
||||
|
||||
// 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.
|
||||
import json from "../webapp/i18n/languages.json";
|
||||
import json from "../../../webapp/i18n/languages.json";
|
||||
const languages = Object.keys(json).filter((lang) => lang !== "default");
|
||||
|
||||
/**
|
||||
@@ -11,8 +11,8 @@ import { nodePolyfills } from "vite-plugin-node-polyfills";
|
||||
import { mergeConfig } from "vite";
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/shared-components/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
staticDirs: ["../webapp"],
|
||||
stories: ["../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
staticDirs: ["../../../webapp"],
|
||||
addons: ["@storybook/addon-docs", "@storybook/addon-designs", "@storybook/addon-a11y"],
|
||||
framework: "@storybook/react-vite",
|
||||
core: {
|
||||
@@ -26,7 +26,7 @@ const config: StorybookConfig = {
|
||||
resolve: {
|
||||
alias: {
|
||||
// Alias used by i18n.tsx
|
||||
$webapp: path.resolve("webapp"),
|
||||
$webapp: path.resolve("../../webapp"),
|
||||
},
|
||||
},
|
||||
// Needed for counterpart to work
|
||||
@@ -36,5 +36,11 @@ const config: StorybookConfig = {
|
||||
},
|
||||
});
|
||||
},
|
||||
refs: {
|
||||
"compound-web": {
|
||||
title: "Compound Web",
|
||||
url: "https://element-hq.github.io/compound-web/",
|
||||
},
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
@@ -1,12 +1,11 @@
|
||||
import type { ArgTypes, Preview, Decorator } from "@storybook/react-vite";
|
||||
import { addons } from "storybook/preview-api";
|
||||
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 React, { useLayoutEffect } from "react";
|
||||
import { FORCE_RE_RENDER } from "storybook/internal/core-events";
|
||||
import { setLanguage } from "../src/shared-components/utils/i18n";
|
||||
import { setLanguage } from "../src/utils/i18n";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
import { StoryContext } from "storybook/internal/csf";
|
||||
|
||||
export const globalTypes = {
|
||||
theme: {
|
||||
@@ -59,29 +58,9 @@ const withThemeProvider: Decorator = (Story, context) => {
|
||||
);
|
||||
};
|
||||
|
||||
const LanguageSwitcher: React.FC<{
|
||||
language: string;
|
||||
}> = ({ language }) => {
|
||||
useLayoutEffect(() => {
|
||||
const changeLanguage = async (language: string) => {
|
||||
await setLanguage(language);
|
||||
// Force the component to re-render to apply the new language
|
||||
addons.getChannel().emit(FORCE_RE_RENDER);
|
||||
};
|
||||
changeLanguage(language);
|
||||
}, [language]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const withLanguageProvider: Decorator = (Story, context) => {
|
||||
return (
|
||||
<>
|
||||
<LanguageSwitcher language={context.globals.language} />
|
||||
<Story />
|
||||
</>
|
||||
);
|
||||
};
|
||||
async function languageLoader(context: StoryContext<ReactRenderer, StrictArgs>): Promise<void> {
|
||||
await setLanguage(context.globals.language);
|
||||
}
|
||||
|
||||
const withTooltipProvider: Decorator = (Story) => {
|
||||
return (
|
||||
@@ -93,7 +72,7 @@ const withTooltipProvider: Decorator = (Story) => {
|
||||
|
||||
const preview: Preview = {
|
||||
tags: ["autodocs"],
|
||||
decorators: [withThemeProvider, withLanguageProvider, withTooltipProvider],
|
||||
decorators: [withThemeProvider, withTooltipProvider],
|
||||
parameters: {
|
||||
options: {
|
||||
storySort: {
|
||||
@@ -108,6 +87,7 @@ const preview: Preview = {
|
||||
test: "error",
|
||||
},
|
||||
},
|
||||
loaders: [languageLoader],
|
||||
};
|
||||
|
||||
export default preview;
|
||||
@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { waitForPageReady } from "@storybook/test-runner";
|
||||
import { toMatchImageSnapshot } from "jest-image-snapshot";
|
||||
|
||||
const customSnapshotsDir = `${process.cwd()}/playwright/shared-component-snapshots/`;
|
||||
const customReceivedDir = `${process.cwd()}/playwright/shared-component-received/`;
|
||||
const customSnapshotsDir = `${process.cwd()}/playwright/snapshots/`;
|
||||
const customReceivedDir = `${process.cwd()}/playwright/received/`;
|
||||
|
||||
/**
|
||||
* @type {import('@storybook/test-runner').TestRunnerConfig}
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "@element-hq/web-shared-components",
|
||||
"version": "0.0.0-test.6",
|
||||
"description": "Shared components for Element",
|
||||
"author": "New Vector Ltd.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/element-hq/element-web"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"require": {
|
||||
"style": "./dist/element-web-shared-components.css",
|
||||
"types": "./dist/element-web-shared-components.d.ts",
|
||||
"default": "./dist/element-web-shared-components.umd.js"
|
||||
},
|
||||
"import": {
|
||||
"style": "./dist/element-web-shared-components.css",
|
||||
"types": "./dist/element-web-shared-components.d.ts",
|
||||
"default": "./dist/element-web-shared-components.mjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"types": "dist/element-web-shared-components.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"src",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"package.json"
|
||||
],
|
||||
"scripts": {
|
||||
"postinstall": "patch-package",
|
||||
"prepare": "vite build",
|
||||
"storybook": "storybook dev -p 6007",
|
||||
"build-storybook": "storybook build",
|
||||
"lint": "yarn lint:types && yarn lint:js",
|
||||
"lint:js": "eslint --max-warnings 0 src && prettier --check .",
|
||||
"lint:types": "tsc --noEmit --jsx react",
|
||||
"test:storybook": "test-storybook --url http://localhost:6007/",
|
||||
"test:storybook:ci": "concurrently -k -s first -n \"SB,TEST\" \"yarn storybook --no-open\" \"wait-on tcp:6007 && yarn test-storybook --url http://localhost:6007/ --ci --maxWorkers=2\"",
|
||||
"test:storybook:update": "playwright-screenshots --entrypoint yarn --with-node-modules && playwright-screenshots --entrypoint /work/node_modules/.bin/test-storybook --with-node-modules --url http://host.docker.internal:6007/ --updateSnapshot"
|
||||
},
|
||||
"dependencies": {
|
||||
"matrix-web-i18n": "^3.4.0",
|
||||
"patch-package": "^8.0.1",
|
||||
"counterpart": "^0.18.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-a11y": "^9.1.10",
|
||||
"@storybook/addon-designs": "^10.0.2",
|
||||
"@storybook/addon-docs": "^9.1.10",
|
||||
"@storybook/icons": "^1.6.0",
|
||||
"@storybook/react-vite": "^9.1.10",
|
||||
"@storybook/test-runner": "^0.23.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"eslint": "8",
|
||||
"eslint-plugin-storybook": "^9.1.10",
|
||||
"jest-image-snapshot": "^6.5.1",
|
||||
"patch-package": "^8.0.1",
|
||||
"prettier": "^3.6.2",
|
||||
"storybook": "^9.1.10",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.1.9",
|
||||
"vite-plugin-dts": "^4.5.4",
|
||||
"vite-plugin-node-polyfills": "^0.24.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
declare module "*.css";
|
||||
@@ -9,18 +9,18 @@ import React, { type JSX } from "react";
|
||||
import { fn } from "storybook/test";
|
||||
|
||||
import type { Meta, StoryFn } from "@storybook/react-vite";
|
||||
import {
|
||||
AudioPlayerView,
|
||||
type AudioPlayerViewActions,
|
||||
type AudioPlayerViewSnapshot,
|
||||
type AudioPlayerViewModel,
|
||||
} from "./AudioPlayerView";
|
||||
import { ViewWrapper } from "../../ViewWrapper";
|
||||
import { AudioPlayerView, type AudioPlayerViewActions, type AudioPlayerViewSnapshot } from "./AudioPlayerView";
|
||||
import { useMockedViewModel } from "../../useMockedViewModel";
|
||||
|
||||
type AudioPlayerProps = AudioPlayerViewSnapshot & AudioPlayerViewActions;
|
||||
const AudioPlayerViewWrapper = (props: AudioPlayerProps): JSX.Element => (
|
||||
<ViewWrapper<AudioPlayerViewSnapshot, AudioPlayerViewModel> Component={AudioPlayerView} props={props} />
|
||||
);
|
||||
const AudioPlayerViewWrapper = ({ togglePlay, onKeyDown, onSeekbarChange, ...rest }: AudioPlayerProps): JSX.Element => {
|
||||
const vm = useMockedViewModel(rest, {
|
||||
togglePlay,
|
||||
onKeyDown,
|
||||
onSeekbarChange,
|
||||
});
|
||||
return <AudioPlayerView vm={vm} />;
|
||||
};
|
||||
|
||||
export default {
|
||||
title: "Audio/AudioPlayerView",
|
||||
@@ -13,7 +13,7 @@ import { fireEvent } from "@testing-library/dom";
|
||||
|
||||
import * as stories from "./AudioPlayerView.stories.tsx";
|
||||
import { AudioPlayerView, type AudioPlayerViewActions, type AudioPlayerViewSnapshot } from "./AudioPlayerView";
|
||||
import { MockViewModel } from "../../MockViewModel";
|
||||
import { MockViewModel } from "../../viewmodel/MockViewModel.ts";
|
||||
|
||||
const { Default, NoMediaName, NoSize, HasError } = composeStories(stories);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import React, { type ChangeEventHandler, type JSX, type KeyboardEventHandler, type MouseEventHandler } from "react";
|
||||
|
||||
import { type ViewModel } from "../../ViewModel";
|
||||
import { type ViewModel } from "../../viewmodel/ViewModel";
|
||||
import { useViewModel } from "../../useViewModel";
|
||||
import { MediaBody } from "../../message-body/MediaBody";
|
||||
import { Flex } from "../../utils/Flex";
|
||||
@@ -15,7 +15,7 @@ exports[`AudioPlayerView renders the audio player in default state 1`] = `
|
||||
<button
|
||||
aria-disabled="false"
|
||||
aria-label="Play"
|
||||
aria-labelledby="«r0»"
|
||||
aria-labelledby="_r_0_"
|
||||
class="_icon-button_1pz9o_8 button"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
@@ -106,7 +106,7 @@ exports[`AudioPlayerView renders the audio player in error state 1`] = `
|
||||
<button
|
||||
aria-disabled="false"
|
||||
aria-label="Play"
|
||||
aria-labelledby="«ri»"
|
||||
aria-labelledby="_r_i_"
|
||||
class="_icon-button_1pz9o_8 button"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
@@ -202,7 +202,7 @@ exports[`AudioPlayerView renders the audio player without media name 1`] = `
|
||||
<button
|
||||
aria-disabled="false"
|
||||
aria-label="Play"
|
||||
aria-labelledby="«r6»"
|
||||
aria-labelledby="_r_6_"
|
||||
class="_icon-button_1pz9o_8 button"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
@@ -293,7 +293,7 @@ exports[`AudioPlayerView renders the audio player without size 1`] = `
|
||||
<button
|
||||
aria-disabled="false"
|
||||
aria-label="Play"
|
||||
aria-labelledby="«rc»"
|
||||
aria-labelledby="_r_c_"
|
||||
class="_icon-button_1pz9o_8 button"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
@@ -5,7 +5,7 @@ exports[`PlayPauseButton renders the button in default state 1`] = `
|
||||
<button
|
||||
aria-disabled="false"
|
||||
aria-label="Play"
|
||||
aria-labelledby="«r0»"
|
||||
aria-labelledby="_r_0_"
|
||||
class="_icon-button_1pz9o_8 button"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
@@ -37,7 +37,7 @@ exports[`PlayPauseButton renders the button in playing state 1`] = `
|
||||
<button
|
||||
aria-disabled="false"
|
||||
aria-label="Pause"
|
||||
aria-labelledby="«r6»"
|
||||
aria-labelledby="_r_6_"
|
||||
class="_icon-button_1pz9o_8 button"
|
||||
data-kind="primary"
|
||||
role="button"
|
||||
@@ -9,7 +9,7 @@ import React from "react";
|
||||
import { type Meta, type StoryFn } from "@storybook/react-vite";
|
||||
|
||||
import { TextualEventView as TextualEventComponent } from "./TextualEventView";
|
||||
import { MockViewModel } from "../../MockViewModel";
|
||||
import { MockViewModel } from "../../viewmodel/MockViewModel";
|
||||
|
||||
export default {
|
||||
title: "Event/TextualEvent",
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React, { type ReactNode, type JSX } from "react";
|
||||
|
||||
import { type ViewModel } from "../../ViewModel";
|
||||
import { type ViewModel } from "../../viewmodel/ViewModel";
|
||||
import { useViewModel } from "../../useViewModel";
|
||||
|
||||
export type TextualEventViewSnapshot = {
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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);
|
||||
});
|
||||
});
|
||||