Compare commits

..

52 Commits

Author SHA1 Message Date
Half-Shot
3af6093755 fix lint 2025-11-07 13:37:42 +00:00
Half-Shot
ab7051cc51 Add tracking for setting up key backups. 2025-11-07 11:10:09 +00:00
Michael Telatynski
3848d570be Inhibit keyboard highlights in dialogs when effector is not in focus (#31181)
* Inhibit keyboard highlight in spotlight dialog when effector is not in focus

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Inhibit keyboard highlight in forward dialog when effector is not in focus

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix search box height bouncing on focus/blur

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update screenshot

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-11-06 16:32:46 +00:00
Richard van der Hoff
eade32a80c Playwright test: withheld sessions for MSC4268 (#31153)
* Playwright test: withheld sessions for MSC4268

Test what happens when a session is withheld as part of MSC4268

* Add comment on `openRoomInfoPanel`
2025-11-06 12:14:14 +00:00
Ben Banfield-Zanin
a0de60a045 Use dedicated bug report subdomain (#31185)
* Update docs for 'bug_report_endpoint_url' to use the dedicated subdomain

* Update {app,develop}.element.io to use the dedicated subdomain for 'bug_report_endpoint_url'
2025-11-06 12:10:24 +00:00
ElementRobot
a88a3575d5 [create-pull-request] automated change (#31184)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-11-06 06:21:04 +00:00
Florian Duros
abb93545fe fix(e2e): look at the location map before clicking (#31180) 2025-11-05 16:49:41 +00:00
R Midhun Suresh
24aa759544 Add license (#31179) 2025-11-05 16:44:59 +00:00
David Baker
77aa26bcdb Remove duplicate test step (#31166)
As the comment indicated, this apparently was necessary to get stuff
built the first time, but does not appear to be necessary anymore and
looks to be causing occasional flakiness, so let's remove it.
2025-11-05 13:55:19 +00:00
Florian Duros
4d66a85e73 Add options to hide header and composer of room view for the module api (#31095)
* feat: add props to hide header in `RoomView`

* feat: add props to hide composer in `RoomView`

* feat: pass `RoomViewProps` to room view in `renderRoomView`

* refactor: add doc and use existing types

* test: add tests for new room view props
2025-11-05 12:52:10 +00:00
Tol Wassman
52eb8a9979 Strip mentions from forwarded messages (#30884)
* strip mentions from forwarded messages

fixes element-hq/element-web#30883

* call attachMentions() for empty m.mentions in forwarded messages

As there is no EditorModel, attachMentions() currently does nothing

* fix lint and typecheck

* update test "should be navigable using arrow keys"

* update test "forwards pin drop event"

add empty mentions to expected content

* add doc to transformEvent() & elaborate on attachMentions()

* add test "strips mentions from forwarded messages"

* fix lint

* update source of `attachMentions()` import

---------

Co-authored-by: David Baker <dbkr@users.noreply.github.com>
2025-11-05 11:28:24 +00:00
David Baker
c4ef57b5f1 Fix the slow test reporter (#31170)
Only runs on develop so we didn't notice
2025-11-05 11:16:48 +00:00
David Baker
d9e3aa52e2 Hopefully fix manual device verification test (#31175)
Looks like it's failing to clear modals between tests
2025-11-05 10:19:21 +00:00
ElementRobot
b8e7c725e2 [create-pull-request] automated change (#31176)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-11-05 09:17:48 +00:00
ElementRobot
2617a7c3a5 [create-pull-request] automated change (#31177)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-11-05 09:07:24 +00:00
David Baker
42f8247c2e Experimental Module API Additions (#30863)
* Module API experiments

* Move ResizerNotifier into SDKContext

so we don't have to pass it into RoomView

* Add the MultiRoomViewStore

* Make RoomViewStore able to take a roomId prop

* Different interface to add space panel items

A bit less flexible but probably simpler and will help keep things
actually consistent rather than just allowing modules to stick any
JSX into the space panel (which means they also have to worry about
styling if they *do* want it to be consistent).

* Allow space panel items to be updated

and manage which one is selected, allowing module "spaces" to be
considered spaces

* Remove fetchRoomFn from SpaceNotificationStore

which didn't really seem to have any point as it was only called from
one place

* Switch to using module api via .instance

* Fairly awful workaround

to actually break the dependency nightmare

* Add test for multiroomviewstore

* add test

* Make room names deterministic

So the tests don't fail if you add other tests or run them individually

* Add test for builtinsapi

* Update module api

* RVS is not needed as prop anymore

Since it's passed through context

* Add roomId to prop

* Remove RoomViewStore from state

This is now accessed through class field

* Fix test

* No need to pass RVS from LoggedInView

* Add RoomContextType

* Implement new builtins api

* Add tests

* Fix import

* Fix circular dependency issue

* Fix import

* Add more tests

* Improve comment

* room-id is optional

* Update license

* Add implementation for AccountDataApi

* Add implementation for Room

* Add implementation for ClientApi

* Create ClientApi in Api.ts

* Write tests

* Use nullish coalescing assignment

* Implement openRoom in NavigationApi

* Write tests

* Add implementation for StoresApi

* Write tests

* Fix circular dependency

* Add comments in lieu of type

and fix else block

* Change to class field

---------

Co-authored-by: R Midhun Suresh <hi@midhun.dev>
2025-11-05 07:24:26 +00:00
David Langley
514dd07a28 Fix test:storybook:update (#31173) 2025-11-04 17:27:34 +00:00
RiotRobot
53dc281a24 Reset matrix-js-sdk back to develop branch 2025-11-04 14:13:04 +00:00
RiotRobot
efc5b0260b Merge branch 'master' into develop 2025-11-04 14:12:52 +00:00
RiotRobot
d4c22d43f3 v1.12.3 2025-11-04 14:09:39 +00:00
RiotRobot
926bb56723 Upgrade dependency to matrix-js-sdk@39.1.2 2025-11-04 14:03:39 +00:00
David Baker
3dca2da5d6 Add test tag while we're publishing test versions 2025-11-04 11:29:03 +00:00
David Baker
71604c9e19 Fx shared component publish workflow (#31161) 2025-11-04 11:06:42 +00:00
David Langley
4e57b80556 Change polls to use fieldset/legend markup (#31160)
* Change polls to use fieldset/legend markup

* Update MPollEndBody-test.tsx.snap

* Fix letter spacing in legend

* Update PollHistory-test.tsx.snap
2025-11-04 09:55:35 +00:00
ElementRobot
c14d072cb7 [create-pull-request] automated change (#31163)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-11-04 09:25:04 +00:00
David Langley
dcf3e536ab Don't allow pin or edit of messages with a send status (#31158)
* Don't allow pin or edit of messages with a send status

* lint and improve comments.
2025-11-03 17:33:18 +00:00
David Baker
18a5565b70 Bump shared components version 2025-11-03 17:25:06 +00:00
David Baker
b0cdbf5eff Make shared component build work in isolation (#31066)
* Make shared component build work in isolation

 * Add deps that were missing because they were getting picked up
   from element-web main but shared-components needs itself
 * Exclude test files from dts generation
 * Bump version

* Change all the shared-component import to be the built artifact

* Don't randomly inhale eslint configs in parent dirs please

* maybe we don't need this anymore?

* maybe fix build

* Maybe fix docker build

* More build faff

 * build:res on the parent as part of shared component prepare
 * link shared component repo inn docker build

* 💅

* 💅x2

* Try converting the translation keys to a .d.ts file manually

so it gets bundled rather than left as a relative import to the json
file

* add the script

* Add this back for 2nd time now I think

* Shouldn't need this anymore

* patch-package on prepare

because we're patching a dev dependency so it won't be there if we're
installed as a dependency

* Unused import

* Prettier compliance

* Only use counterpart from shared components

as per comment

* Import shared components CSS

* Prettier

* Call the one from shared components

rather than recurse infinitely

* Hopefully make tests work

* wake up, comment goes before import

* Fix lint errors

* Fix dupe TranslationKey export

* Update compound-web to fix type error

An update to @types.react adds the 'hint' value to the enum of the
'popover' attribute and this version of compound-web uses the maching
verson of @types/react so they don't conflict.

* Maybe, hopefully, get the types working?

Please?

* Add copyright header to i18nkeys

as eslint complains otherwise since it's now in src

* prettier

* stop running shared-component tests in EW

* update snapshots

because flex is now from an external stylesheet I guess

* More snapshots

* Manual class update

* Avoid bundling compound bits

Because a) it's silly and b) it means we end up bundling a copy of
floating-ui too which causes absolute madness with its useDelayGroup
contexts.

* ignore test util files for coverage

* Add !important

because the styles are being applied in a different order now

* Another !important because css order has changed

* Try adding it here to make the test files ignored

* More !important

* commit yarn lock change

* Add shared components coverage file

* Update snapshots

Because the line height was being overridden to 22.5px somehow by
something I can't find, and now isn't: surely the normal 1.5rem is
more sensible.

* Update snapshots, attempt 2

* Another !important

* More snapshot updates

* Add test for i18n wrappers

& add test script

* lint

* Prettier

* Hopefully run shared component tests

* don't need this bit for non-matrix

* install ew deps

* rigfht coverage location

* Rename job here too

* Try different coverage filename

* Fix copyrights & comment

* Typo

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-11-03 16:26:47 +00:00
Will Hunt
486d4d59bc Use compound Button styles for Jitsi button (#31159)
* Use compound styles for jitsi button

* lint
2025-11-03 16:02:21 +00:00
Will Hunt
54f967efd5 Hide room header buttons if the room hasn't been created yet. (#31092)
* Hide room header buttons if the room isn't ready

* update test

* update unit tests

* Remove on-click actions
2025-11-03 13:25:08 +00:00
ElementRobot
24fc018845 [create-pull-request] automated change (#31150)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-11-01 06:18:47 +00:00
David Langley
36ccc1ae9a Fix screen readers not indicating the emoji picker search field is focused. (#31128)
* Only set active descendant when the user starts typing.

* Fix jest tests.

* Remove aria-hidden

It was failing code quality checks and it actually wasn't addressing the issue.

* Only show highlight on arrow key navigation or updating the search query.

* Update screenshots

* Enter should not select an emoji if it is not highlighted.

* On clearing a query and using arrow kets again the highlighted emoji should be reset to the first.

* Update selector in picker tests
2025-10-31 17:10:02 +00:00
Michael Telatynski
017aee9a8f Fix emoji picker highlight missing when not active element (#31148)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-10-31 15:20:57 +00:00
David Langley
e0a94a05ea Add FocusLock to emoji picker (#31146)
* Add focus lock to emoji picker and e2e test.

* Remove direct use of FocusLock in favour of the ContextMenu prop.

* Apply returnFocus for ContextMenu focusLocks

* Remove import
2025-10-31 13:54:26 +00:00
renovate[bot]
23f372ca08 Update dependency eslint-plugin-react-hooks to v7.0.1 (#31103)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-31 12:52:40 +00:00
Skye Elliot
73fa27887d Move room name, avatar, and topic to IOpts. (#30981) 2025-10-31 12:20:02 +00:00
renovate[bot]
a9bb046e52 Update storybook to v9.1.16 (#31105)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-31 11:59:07 +00:00
Michael Telatynski
43485594b5 Add relevant aria attribute for selected emoji in the emoji picker (#31125)
* Add relevant aria attribute for selected emoji in the emoji picker

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add aria-multiselectable

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Do not specify aria-selected/pressed when element is disabled

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Use checkbox role for reaction picker as gridcell + aria-selected has very inconsistent screenreader support

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix keyboard handling for modified DOM structure

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix enter behaviour

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-10-31 11:49:59 +00:00
ElementRobot
fdf54dd9c2 Localazy Download (#31145)
* [create-pull-request] automated change

* Update snapshot

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-10-31 11:22:39 +00:00
Michael Telatynski
c7f07f4c29 Fix tooltips within context menu portals being unreliable (#31129)
* Fix tooltips within context menu portals being unreliable

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update snapshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-10-31 09:00:01 +00:00
renovate[bot]
bbb16f7ea9 Update definitelyTyped (#31099)
* Update definitelyTyped

* Make typescript happy

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-10-31 08:34:26 +00:00
ElementRobot
3fd102602e [create-pull-request] automated change (#31144)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-10-31 06:19:34 +00:00
Michael Telatynski
af150b94ce Switch to NPM Trusted Publishing for shared-components (#31139)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-10-30 17:17:49 +00:00
Florian Duros
299d7baf8b Avoid excessive re-render of room list and member list (#31131)
* fix(list view): avoid re-create `onFocus` function at each render of the child items

* fix(room list): update `onFocus` signature

* fix(member list): update `onFocus` signature

* fix(room list): avoid re-render at the beginning and end of the scroll

* test(room list): remove scrolling test and props

* test(member list): update member tile view tests

* test(room list): update `ListView` focus test

* test(member list): add `onFocus` test for member list tile
2025-10-30 14:55:15 +00:00
Florian Duros
f297282bf6 fix: force counterpart resolve in jest config (#31136) 2025-10-30 09:59:27 +00:00
David Langley
3ae91d69da Make emoji picker height responsive. (#31130)
* Make the height of the emoji picker responsive.

* Add screenshots for the emoji picker and a one with reduced height.

* prettier
2025-10-30 09:31:02 +00:00
RiotRobot
1dfd92c1eb Upgrade dependency to matrix-js-sdk@39.1.0-rc.1 2025-10-30 09:04:27 +00:00
ElementRobot
1ec692c1e9 [create-pull-request] automated change (#31134)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-10-30 06:19:19 +00:00
David Langley
d558fa79e0 Emoji Picker: Focused emoji does not move with the arrow keys (#30893)
* We should focus the node in the DOM so that the browser focus(with outline) matches the our internal RovingIndex state

* Don't move focus from the input if we are in "virtual" focus(via active descendant)
2025-10-29 16:16:03 +00:00
Florian Duros
2ab42df0c8 Fix audio player seek bar position (#31127)
* fix(audio): stop clock when the source audio reaches the end

* test(audio): add test for playback when audio source ended
2025-10-29 16:05:04 +00:00
RiotRobot
68e6b0f845 v1.12.3-rc.0 2025-10-28 15:07:21 +00:00
RiotRobot
d426063a22 Upgrade dependency to matrix-js-sdk@39.1.0-rc.0 2025-10-28 14:05:15 +00:00
216 changed files with 5257 additions and 1430 deletions

View File

@@ -1,3 +1,10 @@
/*
Copyright 2025 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
module.exports = {
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
extends: ["plugin:matrix-org/babel", "plugin:matrix-org/react", "plugin:matrix-org/a11y"],

View File

@@ -21,8 +21,14 @@ jobs:
cache: "yarn"
node-version-file: ".node-version"
registry-url: "https://registry.npmjs.org"
env:
NODE_AUTH_TOKEN: ${{ secrets.ELEMENT_NPM_TOKEN }}
# 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
@@ -31,4 +37,4 @@ jobs:
- name: 🚀 Publish to npm
working-directory: packages/shared-components
run: npm publish --access public --provenance
run: npm publish --access public --tag test --provenance

View File

@@ -34,10 +34,6 @@ jobs:
- 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
@@ -59,12 +55,6 @@ jobs:
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: "yarn playwright install --with-deps --only-shell"
- name: Build storybook dependencies
# When the first test is ran, it will fail because the dependencies are not yet built.
# This step is to ensure that the dependencies are built before running the tests.
run: "yarn --cwd packages/shared-components test:storybook:ci"
continue-on-error: true
- name: Run Visual tests
run: "yarn --cwd packages/shared-components test:storybook:ci"

View File

@@ -35,10 +35,6 @@ 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"
@@ -91,10 +87,6 @@ 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"

View File

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

1
.gitignore vendored
View File

@@ -36,3 +36,4 @@ storybook-static
/packages/shared-components/node_modules
/packages/shared-components/dist
/packages/shared-components/src/i18nKeys.d.ts

View File

@@ -1,3 +1,27 @@
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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,7 @@ const config: Config = {
// This is needed to be able to load dual CJS/ESM WASM packages e.g. rust crypto & matrix-wywiwyg
customExportConditions: ["browser", "node"],
},
testMatch: ["<rootDir>/test/**/*-test.[tj]s?(x)", "<rootDir>/packages/*/src/**/*.test.[t]s?(x)"],
testMatch: ["<rootDir>/test/**/*-test.[tj]s?(x)"],
globalSetup: "<rootDir>/test/globalSetup.ts",
setupFiles: ["jest-canvas-mock", "web-streams-polyfill/polyfill"],
setupFilesAfterEnv: ["<rootDir>/test/setupTests.ts"],
@@ -40,6 +40,7 @@ const config: Config = {
"^!!raw-loader!.*": "jest-raw-loader",
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
"^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
"counterpart": "<rootDir>/node_modules/counterpart",
},
transformIgnorePatterns: [
"/node_modules/(?!(mime|matrix-js-sdk|uuid|p-retry|is-network-error|react-merge-refs)).+$",

View File

@@ -1,6 +1,6 @@
{
"name": "element-web",
"version": "1.12.2",
"version": "1.12.3",
"description": "Element: the future of secure communication",
"author": "New Vector Ltd.",
"repository": {
@@ -72,6 +72,7 @@
"@playwright/test": "1.56.1",
"@types/react": "19.2.2",
"@types/react-dom": "19.2.2",
"@types/serve-static": "1.15.10",
"oidc-client-ts": "3.3.0",
"jwt-decode": "4.0.0",
"caniuse-lite": "1.0.30001751",
@@ -82,6 +83,7 @@
"dependencies": {
"@babel/runtime": "^7.12.5",
"@element-hq/element-web-module-api": "1.5.0",
"@element-hq/web-shared-components": "file:packages/shared-components",
"@fontsource/inconsolata": "^5",
"@fontsource/inter": "^5",
"@formatjs/intl-segmenter": "^11.5.7",
@@ -103,7 +105,6 @@
"browserslist": "^4.23.2",
"classnames": "^2.2.6",
"commonmark": "^0.31.0",
"counterpart": "^0.18.6",
"css-tree": "^3.0.0",
"diff-dom": "^5.0.0",
"diff-match-patch": "^1.0.5",

View File

@@ -1,4 +1,12 @@
/*
Copyright 2025 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
module.exports = {
root: true,
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
extends: [
"plugin:matrix-org/babel",

View File

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

View File

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

View File

@@ -0,0 +1,58 @@
/*
Copyright 2025 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { env } from "process";
import type { Config } from "jest";
const config: Config = {
testEnvironment: "jsdom",
testEnvironmentOptions: {
url: "http://localhost/",
},
testMatch: ["<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)).+$",
],
collectCoverageFrom: [
"<rootDir>/src/**/*.{js,ts,tsx}",
"<rootDir>/packages/**/*.{js,ts,tsx}",
// Coverage chokes on type definition files
"!<rootDir>/src/**/*.d.ts",
],
coverageReporters: ["text-summary", "lcov"],
testResultsProcessor: "@casualbot/jest-sonar-reporter",
prettierPath: null,
moduleDirectories: ["node_modules", "./src/test/utils"],
};
// if we're running under GHA, enable the GHA reporter
if (env["GITHUB_ACTIONS"] !== undefined) {
const reporters: Config["reporters"] = [["github-actions", { silent: false }], "summary"];
// if we're running against the develop branch, also enable the slow test reporter
if (env["GITHUB_REF"] == "refs/heads/develop") {
reporters.push("<rootDir>/../../test/slowReporter.cjs");
}
config.reporters = reporters;
}
export default config;

View File

@@ -1,6 +1,6 @@
{
"name": "@element-hq/web-shared-components",
"version": "0.0.0-test.6",
"version": "0.0.0-test.7",
"description": "Shared components for Element",
"author": "New Vector Ltd.",
"repository": {
@@ -19,6 +19,10 @@
"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",
@@ -30,8 +34,8 @@
"package.json"
],
"scripts": {
"postinstall": "patch-package",
"prepare": "vite build",
"test": "jest",
"prepare": "patch-package && yarn --cwd ../.. build:res && ts-node scripts/gatherTranslationKeys.ts && vite build",
"storybook": "storybook dev -p 6007",
"build-storybook": "storybook build",
"lint": "yarn lint:types && yarn lint:js",
@@ -39,27 +43,41 @@
"lint:types": "tsc --noEmit --jsx react",
"test:storybook": "test-storybook --url http://localhost:6007/",
"test:storybook:ci": "concurrently -k -s first -n \"SB,TEST\" \"yarn storybook --no-open\" \"wait-on tcp:6007 && yarn test-storybook --url http://localhost:6007/ --ci --maxWorkers=2\"",
"test:storybook:update": "playwright-screenshots --entrypoint yarn --with-node-modules && playwright-screenshots --entrypoint /work/node_modules/.bin/test-storybook --with-node-modules --url http://host.docker.internal:6007/ --updateSnapshot"
"test:storybook:update": "playwright-screenshots --entrypoint /work/node_modules/.bin/test-storybook --with-node-modules --url http://host.docker.internal:6007/ --updateSnapshot"
},
"dependencies": {
"classnames": "^2.5.1",
"counterpart": "^0.18.6",
"lodash": "^4.17.21",
"matrix-web-i18n": "^3.4.0",
"patch-package": "^8.0.1",
"counterpart": "^0.18.6"
"react-merge-refs": "^3.0.2",
"temporal-polyfill": "^0.3.0"
},
"devDependencies": {
"@element-hq/element-web-playwright-common": "^2.0.0",
"@playwright/test": "^1.50.1",
"@storybook/addon-a11y": "^9.1.10",
"@storybook/addon-designs": "^10.0.2",
"@storybook/addon-docs": "^9.1.10",
"@storybook/icons": "^1.6.0",
"@storybook/react-vite": "^9.1.10",
"@storybook/test-runner": "^0.23.0",
"@testing-library/dom": "^10.4.1",
"@testing-library/react": "^16.3.0",
"@types/counterpart": "^0.18.4",
"@types/lodash": "^4.17.20",
"@types/react": "^19.2.2",
"concurrently": "^9.2.1",
"eslint": "8",
"eslint-plugin-matrix-org": "^3.0.0",
"eslint-plugin-storybook": "^10.0.0",
"jest": "^30.2.0",
"jest-image-snapshot": "^6.5.1",
"patch-package": "^8.0.1",
"prettier": "^3.6.2",
"storybook": "^9.1.10",
"ts-node": "^10.9.2",
"typescript": "^5.9.3",
"vite": "^7.1.9",
"vite-plugin-dts": "^4.5.4",
@@ -68,5 +86,9 @@
"engines": {
"node": ">=20.0.0"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
"peerDependencies": {
"@vector-im/compound-design-tokens": "^6.0.0",
"@vector-im/compound-web": "^8.2.5"
}
}

View File

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

View File

@@ -6,7 +6,7 @@
*/
.audioPlayer {
padding: var(--cpd-space-4x) var(--cpd-space-3x) var(--cpd-space-3x) var(--cpd-space-3x);
padding: var(--cpd-space-4x) var(--cpd-space-3x) var(--cpd-space-3x) var(--cpd-space-3x) !important;
}
.mediaInfo {

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`AudioPlayerView renders the audio player in default state 1`] = `
<div>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Clock renders the clock 1`] = `
<div>

View File

@@ -6,6 +6,6 @@
*/
.button {
border-radius: 32px;
background-color: var(--cpd-color-bg-subtle-primary);
border-radius: 32px !important;
background-color: var(--cpd-color-bg-subtle-primary) !important;
}

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`PlayPauseButton renders the button in default state 1`] = `
<div>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Seekbar renders the clock 1`] = `
<div>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`AvatarWithDetails renders a textual event 1`] = `
<div>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`TextualEventView renders a textual event 1`] = `
<div>

View File

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

View File

@@ -21,12 +21,17 @@ export * from "./utils/Box";
export * from "./utils/Flex";
// Utils
export { setLanguage } from "./utils/i18n";
export * from "./utils/i18n";
export * from "./utils/humanize";
export * from "./utils/DateUtils";
export * from "./utils/numbers";
export * from "./utils/FormattingUtils";
// MVVM
export * from "./viewmodel";
export * from "./useMockedViewModel";
export * from "./useViewModel";
// i18n (we must export this directly in order to not confuse the type bundler, it seems,
// otherwise it will leave it as a relative import rather than bundling it)
export type * from "./i18nKeys.d.ts";

View File

@@ -7,7 +7,7 @@
.mediaBody {
background-color: var(--cpd-color-bg-subtle-secondary);
border-radius: var(--cpd-space-2x);
border-radius: var(--cpd-space-2x) !important;
max-width: 243px; /* use max-width instead of width so it fits within right panels */
font: var(--cpd-font-body-md-regular);

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`MediaBody renders the media body 1`] = `
<div>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Pill renders the pill 1`] = `
<div>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`PillInput renders only the input without children 1`] = `
<div>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`RichItem renders the item in default state 1`] = `
<div>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`RichItem renders the list 1`] = `
<div>

View File

@@ -0,0 +1,22 @@
/*
Copyright 2025 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import fetchMock from "fetch-mock-jest";
import { setLanguage } from "../../src/utils/i18n";
import en from "../../../../src/i18n/strings/en_EN.json";
export function setupLanguageMock(): void {
fetchMock
.get("/i18n/languages.json", {
en: "en_EN.json",
})
.get("end:en_EN.json", en);
}
setupLanguageMock();
setLanguage("en");

View File

@@ -0,0 +1,47 @@
/*
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";
const wrapWithTooltipProvider = (Wrapper: RenderOptions["wrapper"]) => {
return ({ children }: { children: React.ReactNode }) => {
if (Wrapper) {
return (
<Wrapper>
<TooltipProvider>{children}</TooltipProvider>
</Wrapper>
);
} else {
return <TooltipProvider>{children}</TooltipProvider>;
}
};
};
const customRender = (ui: ReactElement, options: RenderOptions = {}): ReturnType<typeof render> => {
return render(ui, {
...options,
wrapper: wrapWithTooltipProvider(options?.wrapper) as RenderOptions["wrapper"],
}) as ReturnType<typeof render>;
};
// eslint-disable-next-line no-restricted-imports
export * from "@testing-library/react";
/**
* This custom render function wraps your component with a TooltipProvider.
* See https://testing-library.com/docs/react-testing-library/setup/#custom-render
*/
export { customRender as render };

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import counterpart from "counterpart";
import { registerTranslations, setMissingEntryGenerator, getLocale, setLocale } from "./i18n";
describe("i18n utils", () => {
beforeEach(() => {
jest.clearAllMocks();
});
it("should wrap registerTranslations", () => {
jest.spyOn(counterpart, "registerTranslations");
registerTranslations("en", { test: "This is a test" });
expect(counterpart.registerTranslations).toHaveBeenCalledWith("en", { test: "This is a test" });
});
it("should wrap setMissingEntryGenerator", () => {
jest.spyOn(counterpart, "setMissingEntryGenerator");
const dummyFn = jest.fn();
setMissingEntryGenerator(dummyFn);
expect(counterpart.setMissingEntryGenerator).toHaveBeenCalledWith(dummyFn);
});
it("should wrap getLocale", () => {
jest.spyOn(counterpart, "getLocale");
getLocale();
expect(counterpart.getLocale).toHaveBeenCalled();
});
it("should wrap setLocale", () => {
jest.spyOn(counterpart, "setLocale");
setLocale("en");
expect(counterpart.setLocale).toHaveBeenCalledWith("en");
});
});

View File

@@ -22,10 +22,10 @@
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string
*/
import React from "react";
import { type TranslationKey as _TranslationKey, KEY_SEPARATOR } from "matrix-web-i18n";
import { KEY_SEPARATOR } from "matrix-web-i18n";
import counterpart from "counterpart";
import type Translations from "../../../../src/i18n/strings/en_EN.json";
import type { TranslationKey } from "../index";
// @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config
import webpackLangJsonUrl from "$webapp/i18n/languages.json";
@@ -45,16 +45,23 @@ counterpart.setSeparator(KEY_SEPARATOR);
const FALLBACK_LOCALE = "en";
counterpart.setFallbackLocale(FALLBACK_LOCALE);
/**
* A type representing the union of possible keys into the translation file using `|` delimiter to access nested fields.
* @example `common|error` to access `error` within the `common` sub-object.
* {
* "common": {
* "error": "Error"
* }
* }
*/
export type TranslationKey = _TranslationKey<typeof Translations>;
// export wrappers around these functions because if we used counterpart directly from
// element-web, it operates on a different instance of counterpart
export function registerTranslations(locale: string, data: object): void {
counterpart.registerTranslations(locale, data);
}
export function setMissingEntryGenerator(callback: (value: string) => void): void {
counterpart.setMissingEntryGenerator(callback);
}
export function getLocale(): string {
return counterpart.getLocale();
}
export function setLocale(value: string): string {
return counterpart.setLocale(value);
}
// Function which only purpose is to mark that a string is translatable
// Does not actually do anything. It's helpful for automatic extraction of translatable strings

View File

@@ -17,7 +17,7 @@
"lib": ["es2022", "es2024.promise", "dom", "dom.iterable"],
"strict": true,
"paths": {
"jest-matrix-react": ["../../test/test-utils/jest-matrix-react"],
"jest-matrix-react": ["./src/test/utils/jest-matrix-react"],
"rollup/parseAst": ["./node_modules/rollup/dist/parseAst"]
}
},

View File

@@ -26,7 +26,7 @@ export default defineConfig({
rollupOptions: {
// make sure to externalize deps that shouldn't be bundled
// into your library
external: ["react", "react-dom"],
external: ["react", "react-dom", "@vector-im/compound-design-tokens", "@vector-im/compound-web"],
output: {
// Provide global variables to use in the UMD build
// for externalized deps
@@ -43,5 +43,12 @@ export default defineConfig({
$webapp: resolve(__dirname, "..", "..", "webapp"),
},
},
plugins: [dts({ rollupTypes: true, include: ["src/**/*.{ts,tsx}"], copyDtsFiles: true })],
plugins: [
dts({
rollupTypes: true,
include: ["src/**/*.{ts,tsx}"],
exclude: ["src/**/*.test.{ts,tsx}"],
copyDtsFiles: true,
}),
],
});

File diff suppressed because it is too large Load Diff

View File

@@ -76,6 +76,57 @@ test.describe("Composer", () => {
await expect(page.locator(".mx_EventTile_body", { hasText: "😇" })).toBeVisible();
});
test.describe("render emoji picker with larger viewport height", async () => {
test.use({ viewport: { width: 1280, height: 720 } });
test("render emoji picker", { tag: "@screenshot" }, async ({ page, app }) => {
await app.getComposer(false).getByRole("button", { name: "Emoji" }).click();
await expect(page.getByTestId("mx_EmojiPicker")).toMatchScreenshot("emoji-picker.png");
});
});
test.describe("render emoji picker with small viewport height", async () => {
test.use({ viewport: { width: 1280, height: 360 } });
test("render emoji picker", { tag: "@screenshot" }, async ({ page, app }) => {
await app.getComposer(false).getByRole("button", { name: "Emoji" }).click();
await expect(page.getByTestId("mx_EmojiPicker")).toMatchScreenshot("emoji-picker-small.png");
});
});
test("should have focus lock in emoji picker", async ({ page, app }) => {
const emojiButton = app.getComposer(false).getByRole("button", { name: "Emoji" });
// Open emoji picker by clicking the button
await emojiButton.click();
// Wait for emoji picker to be visible
const emojiPicker = page.getByTestId("mx_EmojiPicker");
await expect(emojiPicker).toBeVisible();
// Get initial focused element (should be search input)
const searchInput = emojiPicker.getByRole("textbox", { name: "Search" });
await expect(searchInput).toBeFocused();
// Try to tab multiple times - focus should stay within emoji picker
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
// Verify we're still within the emoji picker (not back to composer)
const focusedElement = await page.evaluate(() => document.activeElement?.closest(".mx_EmojiPicker"));
expect(focusedElement).not.toBeNull();
// Close with Escape key
await page.keyboard.press("Escape");
// Verify emoji picker is closed
await expect(emojiPicker).not.toBeVisible();
// Verify focus returns to emoji button
await expect(emojiButton).toBeFocused();
});
test.describe("when Control+Enter is required to send", () => {
test.beforeEach(async ({ app }) => {
await app.settings.setValue("MessageComposerInput.ctrlEnterToSend", null, SettingLevel.ACCOUNT, true);

View File

@@ -56,4 +56,85 @@ test.describe("History sharing", function () {
});
},
);
test("Messages sent when we believed the room history was unshared should not be visible", async ({
labsFlags,
browser,
page: alicePage,
user: aliceCredentials,
app: aliceElementApp,
homeserver,
}, testInfo) => {
test.setTimeout(60000);
// In this test:
// 1. Alice creates an encrypted room with Bob.
// 2. She sets the history visibility to "shared", but Bob doesn't receive the memo
// 3. Bob sends a message
// 4. Alice invites Charlie
// 5. Charlie can't see the message.
await aliceElementApp.client.bootstrapCrossSigning(aliceCredentials);
await createRoom(alicePage, "TestRoom", true);
// Register a second user, and open it in a second instance of the app
const bobCredentials = await homeserver.registerUser(`user_${testInfo.testId}_bob`, "password", "Bob");
const bobPage = await createNewInstance(browser, bobCredentials, {}, labsFlags);
const bobElementApp = new ElementAppPage(bobPage);
await bobElementApp.client.bootstrapCrossSigning(bobCredentials);
// ... and a third
const charlieCredentials = await homeserver.registerUser(
`user_${testInfo.testId}_charlie`,
"password",
"Charlie",
);
const charliePage = await createNewInstance(browser, charlieCredentials, {}, labsFlags);
const charlieElementApp = new ElementAppPage(charliePage);
await charlieElementApp.client.bootstrapCrossSigning(charlieCredentials);
// Alice invites Bob, and Bob accepts
const roomId = await aliceElementApp.getCurrentRoomIdFromUrl();
await aliceElementApp.inviteUserToCurrentRoom(bobCredentials.userId);
await bobPage.getByRole("option", { name: "TestRoom" }).click();
await bobPage.getByRole("button", { name: "Accept" }).click();
// Bob sends a message with "shared" visibility
await sendMessageInCurrentRoom(bobPage, "Message1: 'shared' visibility");
await expect(alicePage.getByText("Message1")).toBeVisible();
// Alice sets the history visibility to "joined"
await aliceElementApp.client.sendStateEvent(roomId, "m.room.history_visibility", {
history_visibility: "joined",
});
await expect(
bobPage.getByText(
"Alice made future room history visible to all room members, from the point they joined.",
),
).toBeVisible();
// Bob stops syncing, and sends a message with "joined" visibility.
// (Stopping syncing *before* sending the message means that the active sync will be flushed by sending the
// message, so that Alice's change to the history viz below won't be seen by Bob.)
await bobPage.route(`**/sync*`, (route) => route.fulfill({}));
await sendMessageInCurrentRoom(bobPage, "Message2: 'joined' visibility");
await expect(alicePage.getByText("Message2")).toBeVisible();
// Alice changes the history viz, but Bob doesn't receive the memo
await aliceElementApp.client.sendStateEvent(roomId, "m.room.history_visibility", {
history_visibility: "shared",
});
await sendMessageInCurrentRoom(bobPage, "Message3: 'shared' visibility, but Bob thinks it is still 'joined'");
// Alice now invites Charlie
await aliceElementApp.inviteUserToCurrentRoom(charlieCredentials.userId);
await charliePage.getByRole("option", { name: "TestRoom" }).click();
await charliePage.getByRole("button", { name: "Accept" }).click();
// Message1 should be visible
// Message2 should be invisible
// Message3 should be undecryptable
await expect(charliePage.getByText("Message1")).toBeVisible();
await expect(charliePage.getByText("You don't have access to this message")).toBeVisible();
});
});

View File

@@ -30,33 +30,42 @@ test.describe("Location sharing", { tag: "@no-firefox" }, () => {
});
});
test("sends and displays pin drop location message successfully", async ({ page, user, app }) => {
const roomId = await app.client.createRoom({});
await page.goto(`/#/room/${roomId}`);
test(
"sends and displays pin drop location message successfully",
{ tag: "@screenshot" },
async ({ page, user, app }) => {
const roomId = await app.client.createRoom({});
await page.goto(`/#/room/${roomId}`);
const composerOptions = await app.openMessageComposerOptions();
await composerOptions.getByRole("menuitem", { name: "Location", exact: true }).click();
const composerOptions = await app.openMessageComposerOptions();
await composerOptions.getByRole("menuitem", { name: "Location", exact: true }).click();
await selectLocationShareTypeOption(page, "Pin").click();
await selectLocationShareTypeOption(page, "Pin").click();
await page.locator("#mx_LocationPicker_map").click();
await page.locator("#mx_LocationPicker_map").click();
await submitShareLocation(page);
await submitShareLocation(page);
await page.locator(".mx_RoomView_body .mx_EventTile .mx_MLocationBody").click({
position: {
x: 225,
y: 150,
},
});
await page.locator(".mx_RoomView_body .mx_EventTile .mx_MLocationBody").click({
position: {
x: 225,
y: 150,
},
});
// clicking location tile opens maximised map
await expect(page.getByRole("dialog")).toBeVisible();
// Wait for map to load
await expect(page.getByRole("region", { name: "Map" })).toMatchScreenshot(
"location-pin-drop-message-map.png",
);
await app.closeDialog();
// clicking location tile opens maximised map
await expect(page.getByRole("dialog")).toBeVisible();
await expect(page.locator(".mx_Marker")).toBeVisible();
});
await app.closeDialog();
await expect(page.locator(".mx_Marker")).toBeVisible();
},
);
test(
"is prompted for and can consent to live location sharing",

View File

@@ -199,6 +199,21 @@ export class ElementAppPage {
return this.page.locator(".mx_RightPanel");
}
/**
* Opens the room info panel if it is not already open.
*
* TODO: fix this so that it works correctly if, say, the member list was open instead of the room info panel.
*
* @returns locator to the right panel
*/
public async openRoomInfoPanel(): Promise<Locator> {
const locator = this.page.getByTestId("right-panel");
if (!(await locator.isVisible())) {
await this.page.getByRole("button", { name: "Room info" }).first().click();
}
return locator;
}
/**
* Opens/closes the memberlist panel
* @returns locator to the memberlist panel
@@ -217,8 +232,8 @@ export class ElementAppPage {
* @param userId - The user to invite to the room.
*/
public async inviteUserToCurrentRoom(userId: string): Promise<void> {
await this.toggleRoomInfoPanel(); // TODO skip this if the room info panel is already open
await this.page.getByTestId("right-panel").getByRole("menuitem", { name: "Invite" }).click();
const rightPanel = await this.openRoomInfoPanel();
await rightPanel.getByRole("menuitem", { name: "Invite" }).click();
const input = this.page.getByRole("dialog").getByTestId("invite-dialog-input");
await input.fill(userId);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -10,7 +10,7 @@ import {
type StartedPostgreSqlContainer,
} from "@element-hq/element-web-playwright-common/lib/testcontainers";
const TAG = "main@sha256:3d139198268dd06c42fe0fc7ffea5df0f2924704867f68e0a8eb3e542f91b9e8";
const TAG = "main@sha256:6157bb499c2d4c1a04e540afd4f541c3d95d93f1de9a57ca013c6384cfe59095";
/**
* MatrixAuthenticationServiceContainer which freezes the docker digest to

View File

@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
import { SynapseContainer as BaseSynapseContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers";
const TAG = "develop@sha256:5a505af08294414dd832517ee0eafadc1faa33a675e577ca073c6fa731c7e5a8";
const TAG = "develop@sha256:b533a18575d8bc501c2be655da28eadd49df5614a3e5f403688420c02cb227d5";
/**
* SynapseContainer which freezes the docker digest to stabilise tests,

View File

@@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
.mx_SearchBox {
flex: 1 1 0;
min-width: 0;
min-height: 36px; /* to avoid jumping when the X to clear shows/hides */
&.mx_SearchBox_blurred:not(:hover) {
background-color: transparent;

View File

@@ -61,6 +61,11 @@ Please see LICENSE files in the repository root for full details.
/* To match the space around the title */
margin: 0 0 15px 0;
flex-grow: 0;
&:not(:focus-within) + .mx_ForwardList_content {
/* Inhibit the styling if focus is not within the input which handles keyboard accessibility */
--mx_ForwardList_entry_selectedBgColor: transparent;
}
}
.mx_ForwardList_content {
@@ -90,7 +95,7 @@ Please see LICENSE files in the repository root for full details.
&:hover,
&.mx_ForwardList_entry_active {
background-color: $spacePanel-bg-color;
background-color: var(--mx_ForwardList_entry_selectedBgColor, $spacePanel-bg-color);
}
.mx_ForwardList_roomButton {

View File

@@ -150,6 +150,16 @@ Please see LICENSE files in the repository root for full details.
height: unset;
margin-left: $spacing-16;
}
&:not(:focus-within) + #mx_SpotlightDialog_content {
/* Inhibit the styling if focus is not within the input which handles keyboard accessibility */
--mx_SpotlightDialog_option_selectedBgColor: transparent;
/* Hide the enter prompt as in this state pressing enter would not actuate that option */
.mx_SpotlightDialog_enterPrompt {
visibility: hidden;
}
}
}
#mx_SpotlightDialog_content {
@@ -193,6 +203,17 @@ Please see LICENSE files in the repository root for full details.
}
}
.mx_SpotlightDialog_option {
border-radius: 8px;
overflow: hidden;
text-overflow: ellipsis;
&:hover,
&[aria-selected="true"] {
background-color: var(--mx_SpotlightDialog_option_selectedBgColor, $quinary-content);
}
}
.mx_SpotlightDialog_recentlyViewed {
> div {
display: flex;
@@ -202,7 +223,6 @@ Please see LICENSE files in the repository root for full details.
}
.mx_SpotlightDialog_option {
border-radius: 8px;
padding: $spacing-4;
color: $primary-content;
font-size: $font-12px;
@@ -213,8 +233,6 @@ Please see LICENSE files in the repository root for full details.
min-width: 58px;
box-sizing: border-box;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
.mx_DecoratedRoomAvatar {
margin: 0 9px $spacing-4; /* maintain centering */
@@ -223,11 +241,6 @@ Please see LICENSE files in the repository root for full details.
& + .mx_SpotlightDialog_option {
margin-left: $spacing-16;
}
&:hover,
&[aria-selected="true"] {
background-color: $quinary-content;
}
}
}
@@ -236,8 +249,9 @@ Please see LICENSE files in the repository root for full details.
.mx_SpotlightDialog_otherSearches,
.mx_SpotlightDialog_hiddenResults {
.mx_SpotlightDialog_option {
--mx_SpotlightDialog_option_selectedBgColor: $system;
padding: 6px $spacing-4;
border-radius: 8px;
font-size: $font-15px;
line-height: $font-24px;
color: $primary-content;
@@ -245,8 +259,6 @@ Please see LICENSE files in the repository root for full details.
display: flex;
align-items: center;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
.mx_SpotlightDialog_option--endAdornment {
display: inline-flex;
@@ -350,7 +362,7 @@ Please see LICENSE files in the repository root for full details.
}
&:hover::before,
&[aria-selected="true"]::before {
&:focus-visible::before {
background-color: $secondary-content;
}
}
@@ -361,8 +373,6 @@ Please see LICENSE files in the repository root for full details.
&:hover,
&[aria-selected="true"] {
background-color: $system;
.mx_SpotlightDialog_option--menu,
.mx_SpotlightDialog_option--notifications {
display: block;

View File

@@ -8,7 +8,8 @@ Please see LICENSE files in the repository root for full details.
.mx_EmojiPicker {
width: 340px;
height: 450px;
height: min(450px, 80vh);
max-height: 80vh;
border-radius: 4px;
@@ -181,7 +182,7 @@ Please see LICENSE files in the repository root for full details.
}
}
.mx_EmojiPicker_body .mx_EmojiPicker_item_wrapper[tabindex="0"] .mx_EmojiPicker_item {
.mx_EmojiPicker_body_showHighlight .mx_EmojiPicker_item_wrapper [tabindex="0"] .mx_EmojiPicker_item {
background-color: $focus-bg-color;
}

View File

@@ -8,13 +8,18 @@ Please see LICENSE files in the repository root for full details.
.mx_MPollBody {
margin-top: 8px;
min-width: 0; /* Override fieldset default min-width: min-content */
width: 100%; /* Ensure fieldset takes full available width */
border: none; /* Remove default fieldset border */
padding: 0; /* Remove default fieldset padding */
h2 {
legend {
font-weight: var(--cpd-font-weight-semibold);
font-size: $font-15px;
line-height: $font-24px;
margin-top: 0;
margin-bottom: 8px;
letter-spacing: var(--cpd-font-letter-spacing-heading-lg);
.mx_MPollBody_edited {
color: $roomtopic-color;
@@ -23,7 +28,7 @@ Please see LICENSE files in the repository root for full details.
}
}
h2::before {
legend::before {
content: "";
position: relative;
display: inline-block;

View File

@@ -153,7 +153,7 @@ Please see LICENSE files in the repository root for full details.
.mx_MediaBody {
/* leave space for the timestamp */
padding-right: 48px;
padding-right: 48px !important;
}
.mx_MImageBody {

View File

@@ -17,4 +17,10 @@ else
fi
DIST_VERSION=$("$DIR"/normalize-version.sh "$DIST_VERSION")
yarn --cwd packages/shared-components install
yarn --cwd packages/shared-components link
yarn link @element-hq/web-shared-components
VERSION=$DIST_VERSION yarn build

View File

@@ -44,3 +44,11 @@ fi
yarn link matrix-js-sdk
[ -d matrix-analytics-events ] && yarn link @matrix-org/analytics-events
yarn install --frozen-lockfile $@
# Link shared components
pushd packages/shared-components
yarn link
yarn install --frozen-lockfile
popd
yarn link @element-hq/web-shared-components

View File

@@ -6,12 +6,12 @@ sonar.organization=element-hq
sonar.sources=src,res,packages/shared-components/src
sonar.tests=test,playwright,src,packages
sonar.test.inclusions=test/*,playwright/*,src/**/*.test.*,packages/*/src/**/*.test.*
sonar.test.inclusions=test/*,playwright/*,src/**/*.test.*,packages/*/src/**/*.test.*,packages/*/src/test/**/*
sonar.exclusions=__mocks__,docs,element.io,nginx
sonar.cpd.exclusions=src/i18n/strings/*.json
sonar.typescript.tsconfigPath=./tsconfig.json
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.javascript.lcov.reportPaths=coverage/lcov.info,packages/shared-components/coverage/lcov.info
sonar.coverage.exclusions=\
test/**/*,\
playwright/**/*,\
@@ -21,5 +21,6 @@ sonar.coverage.exclusions=\
src/components/views/dialogs/devtools/**/*,\
src/utils/SessionLock.ts,\
src/**/*.d.ts,\
src/vector/mobile_guide/**/*
src/vector/mobile_guide/**/* \
packages/shared-components/src/test/**/*
sonar.testExecutionReportPaths=coverage/jest-sonar-report.xml

View File

@@ -13,7 +13,7 @@ import { type Optional } from "matrix-events-sdk";
import { _t, getUserLanguage } from "./languageHandler";
import { getUserTimezone } from "./TimezoneHandler";
export { formatSeconds } from "../packages/shared-components/src/utils/DateUtils";
export { formatSeconds } from "@element-hq/web-shared-components";
export const MINUTE_MS = 60000;
export const HOUR_MS = MINUTE_MS * 60;

View File

@@ -9,7 +9,8 @@ Please see LICENSE files in the repository root for full details.
*/
// Import i18n.tsx instead of languageHandler to avoid circular deps
import { _td, type TranslationKey } from "../../packages/shared-components/src/utils/i18n";
import { _td, type TranslationKey } from "@element-hq/web-shared-components";
import { IS_MAC, IS_ELECTRON, Key } from "../Keyboard";
import { type IBaseSetting } from "../settings/Settings";
import { type KeyCombo } from "../KeyBindingsManager";

View File

@@ -8,9 +8,9 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX, type ReactNode } from "react";
import { Text, Heading, Button, Separator } from "@vector-im/compound-web";
import PopOutIcon from "@vector-im/compound-design-tokens/assets/web/icons/pop-out";
import { Flex } from "@element-hq/web-shared-components";
import SdkConfig from "../../SdkConfig";
import { Flex } from "../../../packages/shared-components/src/utils/Flex";
import { _t } from "../../languageHandler";
import { Icon as AppleIcon } from "../../../res/themes/element/img/compound/apple.svg";
import { Icon as MicrosoftIcon } from "../../../res/themes/element/img/compound/microsoft.svg";

View File

@@ -9,13 +9,13 @@ Please see LICENSE files in the repository root for full details.
import EventEmitter from "events";
import { SimpleObservable } from "matrix-widget-api";
import { logger } from "matrix-js-sdk/src/logger";
import { clamp } from "@element-hq/web-shared-components";
import { UPDATE_EVENT } from "../stores/AsyncStore";
import { arrayFastResample } from "../utils/arrays";
import { type IDestroyable } from "../utils/IDestroyable";
import { PlaybackClock } from "./PlaybackClock";
import { createAudioContext, decodeOgg } from "./compat";
import { clamp } from "../../packages/shared-components/src/utils/numbers";
import { DEFAULT_WAVEFORM, PLAYBACK_WAVEFORM_SAMPLES } from "./consts";
import { PlaybackEncoder } from "../PlaybackEncoder";
@@ -202,6 +202,7 @@ export class Playback extends EventEmitter implements IDestroyable, PlaybackInte
private onPlaybackEnd = async (): Promise<void> => {
await this.context.suspend();
this.emit(PlaybackState.Stopped);
this.clock.flagStop();
};
public async play(): Promise<void> {
@@ -248,9 +249,8 @@ export class Playback extends EventEmitter implements IDestroyable, PlaybackInte
this.emit(PlaybackState.Paused);
}
public async stop(): Promise<void> {
await this.onPlaybackEnd();
this.clock.flagStop();
public stop(): Promise<void> {
return this.onPlaybackEnd();
}
public async toggle(): Promise<void> {

View File

@@ -1,16 +1,14 @@
/*
Copyrimport { type IAmplitudePayload, type ITimingPayload, PayloadEvent, WORKLET_NAME } from "./consts";
import { percentageOf } from "../../packages/shared-components/src/utils/numbers";
// from AudioWorkletGlobalScope: https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletGlobalScope" 2024 New Vector Ltd.
Copyright 2024 New Vector Ltd.
Copyright 2021 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.
*/
import { percentageOf } from "@element-hq/web-shared-components";
import { type IAmplitudePayload, type ITimingPayload, PayloadEvent, WORKLET_NAME } from "./consts";
import { percentageOf } from "../../packages/shared-components/src/utils/numbers";
// from AudioWorkletGlobalScope: https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletGlobalScope
declare const currentTime: number;

View File

@@ -11,6 +11,7 @@ import encoderPath from "opus-recorder/dist/encoderWorker.min.js";
import { SimpleObservable } from "matrix-widget-api";
import EventEmitter from "events";
import { logger } from "matrix-js-sdk/src/logger";
import { clamp } from "@element-hq/web-shared-components";
import MediaDeviceHandler from "../MediaDeviceHandler";
import { type IDestroyable } from "../utils/IDestroyable";
@@ -19,7 +20,6 @@ import { PayloadEvent, WORKLET_NAME } from "./consts";
import { UPDATE_EVENT } from "../stores/AsyncStore";
import { createAudioContext } from "./compat";
import { FixedRollingArray } from "../utils/FixedRollingArray";
import { clamp } from "../../packages/shared-components/src/utils/numbers";
import recorderWorkletFactory from "./recorderWorkletFactory";
const CHANNELS = 1; // stereo isn't important

View File

@@ -12,6 +12,7 @@ import React, { type JSX, type CSSProperties, type RefObject, type SyntheticEven
import ReactDOM from "react-dom";
import classNames from "classnames";
import FocusLock from "react-focus-lock";
import { TooltipProvider } from "@vector-im/compound-web";
import { type Writeable } from "../../@types/common";
import UIStore from "../../stores/UIStore";
@@ -404,7 +405,7 @@ export default class ContextMenu extends React.PureComponent<React.PropsWithChil
);
if (focusLock) {
body = <FocusLock>{body}</FocusLock>;
body = <FocusLock returnFocus>{body}</FocusLock>;
}
// filter props that are invalid for DOM elements
@@ -425,15 +426,17 @@ export default class ContextMenu extends React.PureComponent<React.PropsWithChil
onContextMenu={this.onContextMenuPreventBubbling}
>
{background}
<div
className={menuClasses}
style={menuStyle}
ref={this.collectContextMenuRect}
role={managed ? "menu" : undefined}
{...divProps}
>
{body}
</div>
<TooltipProvider>
<div
className={menuClasses}
style={menuStyle}
ref={this.collectContextMenuRect}
role={managed ? "menu" : undefined}
{...divProps}
>
{body}
</div>
</TooltipProvider>
</div>
)}
</RovingTabIndexProvider>

View File

@@ -1924,10 +1924,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
userId: userId,
subAction: params?.action,
});
} else {
if (ModuleApi.instance.navigation.locationRenderers.get(screen)) {
this.setState({ page_type: screen });
}
} else if (ModuleApi.instance.navigation.locationRenderers.get(screen)) {
this.setState({ page_type: screen });
}
}

View File

@@ -165,6 +165,16 @@ interface IRoomProps extends RoomViewProps {
* Omitting this will mean that RoomView renders for the room held in SDKContext.RoomViewStore.
*/
roomId?: string;
/*
* If true, hide the header
*/
hideHeader?: boolean;
/*
* If true, hide the composer
*/
hideComposer?: boolean;
}
export { MainSplitContentType };
@@ -2455,6 +2465,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
let messageComposer;
const showComposer =
!this.props.hideComposer &&
!isRoomEncryptionLoading &&
// joined and not showing search results
myMembership === KnownMembership.Join &&
@@ -2665,10 +2676,12 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
ref={this.roomViewBody}
data-layout={this.state.layout}
>
<RoomHeader
room={this.state.room}
additionalButtons={this.state.viewRoomOpts.buttons}
/>
{!this.props.hideHeader && (
<RoomHeader
room={this.state.room}
additionalButtons={this.state.viewRoomOpts.buttons}
/>
)}
{mainSplitBody}
</div>
</MainSplit>

View File

@@ -329,8 +329,8 @@ const SpaceSetupFirstRooms: React.FC<{
return createRoom(space.client, {
createOpts: {
preset: isPublic ? Preset.PublicChat : Preset.PrivateChat,
name,
},
name,
spinner: false,
encryption: false,
andView: false,
@@ -423,7 +423,7 @@ const SpaceSetupPublicShare: React.FC<ISpaceSetupPublicShareProps> = ({
<div className="mx_SpaceRoomView_publicShare">
<h1>
{_t("create_space|share_heading", {
name: justCreatedOpts?.createOpts?.name || space.name,
name: justCreatedOpts?.name || space.name,
})}
</h1>
<div className="mx_SpaceRoomView_description">{_t("create_space|share_description")}</div>
@@ -449,7 +449,7 @@ const SpaceSetupPrivateScope: React.FC<{
<h1>{_t("create_space|private_personal_heading")}</h1>
<div className="mx_SpaceRoomView_description">
{_t("create_space|private_personal_description", {
name: justCreatedOpts?.createOpts?.name || space.name,
name: justCreatedOpts?.name || space.name,
})}
</div>
@@ -686,7 +686,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
<SpaceSetupFirstRooms
space={this.props.space}
title={_t("create_space|setup_rooms_community_heading", {
spaceName: this.props.justCreatedOpts?.createOpts?.name || this.props.space.name,
spaceName: this.props.justCreatedOpts?.name || this.props.space.name,
})}
description={
<>

View File

@@ -42,7 +42,7 @@ export interface IListViewProps<Item, Context>
index: number,
item: Item,
context: ListContext<Context>,
onFocus: (e: React.FocusEvent) => void,
onFocus: (item: Item, e: React.FocusEvent) => void,
) => JSX.Element;
/**
@@ -230,19 +230,26 @@ export function ListView<Item, Context = any>(props: IListViewProps<Item, Contex
virtuosoDomRef.current = element;
}, []);
const getItemComponentInternal = useCallback(
(index: number, item: Item, context: ListContext<Context>): JSX.Element => {
const onFocus = (e: React.FocusEvent): void => {
// If one of the item components has been focused directly, set the focused and tabIndex state
// and stop propagation so the ListViews onFocus doesn't also handle it.
const key = getItemKey(item);
setIsFocused(true);
setTabIndexKey(key);
e.stopPropagation();
};
return getItemComponent(index, item, context, onFocus);
/**
* Focus handler passed to each item component.
* Don't declare inside getItemComponent to avoid re-creating on each render.
*/
const onFocusForGetItemComponent = useCallback(
(item: Item, e: React.FocusEvent) => {
// If one of the item components has been focused directly, set the focused and tabIndex state
// and stop propagation so the ListViews onFocus doesn't also handle it.
const key = getItemKey(item);
setIsFocused(true);
setTabIndexKey(key);
e.stopPropagation();
},
[getItemComponent, getItemKey],
[getItemKey],
);
const getItemComponentInternal = useCallback(
(index: number, item: Item, context: ListContext<Context>): JSX.Element =>
getItemComponent(index, item, context, onFocusForGetItemComponent),
[getItemComponent, onFocusForGetItemComponent],
);
/**
* Handles focus events on the list.

View File

@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
*/
import React, { type ChangeEvent, type CSSProperties, type ReactNode } from "react";
import { percentageOf } from "@element-hq/web-shared-components";
import { type PlaybackInterface } from "../../../audio/Playback";
import { MarkedExecution } from "../../../utils/MarkedExecution";
import { percentageOf } from "../../../../packages/shared-components/src/utils/numbers";
import { _t } from "../../../languageHandler";
interface IProps {

View File

@@ -7,9 +7,9 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { Clock } from "@element-hq/web-shared-components";
import { type IRecordingUpdate } from "../../../audio/VoiceRecording";
import { Clock } from "../../../../packages/shared-components/src/audio/Clock";
import { MarkedExecution } from "../../../utils/MarkedExecution";
import { type VoiceMessageRecording } from "../../../audio/VoiceMessageRecording";

View File

@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { Clock } from "@element-hq/web-shared-components";
import { Clock } from "../../../../packages/shared-components/src/audio/Clock";
import { type Playback, PlaybackState } from "../../../audio/Playback";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";

View File

@@ -7,11 +7,11 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { percentageOf } from "@element-hq/web-shared-components";
import { arraySeed, arrayTrimFill } from "../../../utils/arrays";
import Waveform from "./Waveform";
import { type Playback } from "../../../audio/Playback";
import { percentageOf } from "../../../../packages/shared-components/src/utils/numbers";
import { PLAYBACK_WAVEFORM_SAMPLES } from "../../../audio/consts";
interface IProps {

View File

@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type HTMLProps, useContext } from "react";
import { type Beacon, BeaconEvent, LocationAssetType } from "matrix-js-sdk/src/matrix";
import { humanizeTime } from "@element-hq/web-shared-components";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
@@ -18,7 +19,6 @@ import BeaconStatus from "./BeaconStatus";
import { BeaconDisplayStatus } from "./displayStatus";
import StyledLiveBeaconIcon from "./StyledLiveBeaconIcon";
import ShareLatestLocation from "./ShareLatestLocation";
import { humanizeTime } from "../../../../packages/shared-components/src/utils/humanize";
interface Props {
beacon: Beacon;

View File

@@ -735,7 +735,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (this.state.reactionPickerDisplayed) {
const buttonRect = (this.reactButtonRef.current as HTMLElement)?.getBoundingClientRect();
reactionPicker = (
<ContextMenu {...toRightOf(buttonRect)} onFinished={this.closeMenu} managed={false}>
<ContextMenu {...toRightOf(buttonRect)} onFinished={this.closeMenu} managed={false} focusLock>
<ReactionPicker mxEvent={mxEvent} onFinished={this.onCloseReactionPicker} reactions={reactions} />
</ContextMenu>
);

View File

@@ -126,7 +126,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
const opts: IOpts = {};
const createOpts: IOpts["createOpts"] = (opts.createOpts = {});
opts.roomType = this.props.type;
createOpts.name = this.state.name;
opts.name = this.state.name;
if (this.state.joinRule === JoinRule.Public) {
createOpts.visibility = Visibility.Public;
@@ -139,7 +139,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
}
if (this.state.topic) {
createOpts.topic = this.state.topic;
opts.topic = this.state.topic;
}
if (this.state.noFederate) {
createOpts.creation_content = { "m.federate": false };

View File

@@ -57,6 +57,7 @@ import {
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { OverflowTileView } from "../rooms/OverflowTileView";
import { attachMentions } from "../../../utils/messages";
const AVATAR_SIZE = 30;
@@ -178,7 +179,18 @@ const Entry: React.FC<IEntryProps<any>> = ({ room, type, content, matrixClient:
);
};
const transformEvent = (event: MatrixEvent): { type: string; content: IContent } => {
/**
* Transform content of a MatrixEvent before forwarding:
* 1. Strip all relations.
* 2. Convert location events into a static pin-drop location share,
* and remove description from self-location shares.
* 3. Pass through attachMentions() to strip mentions (as no EditorModel is present to recalculate from).
*
* @param event - The MatrixEvent to transform.
* @param userId - Current user MXID (passed through to attachMentions()).
* @returns The transformed event type and content.
*/
const transformEvent = (event: MatrixEvent, userId: string): { type: string; content: IContent } => {
const {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
"m.relates_to": _, // strip relations - in future we will attach a relation pointing at the original event
@@ -213,6 +225,13 @@ const transformEvent = (event: MatrixEvent): { type: string; content: IContent }
};
}
// Mentions can leak information about the context of the original message,
// so pass through attachMentions() to recalculate mentions.
// Currently, this strips all mentions (forces an empty m.mentions),
// as there is no EditorModel to parse pills from.
// Future improvements could actually recalculate mentions based on the message body.
attachMentions(userId, content, null, undefined);
return { type, content };
};
@@ -223,7 +242,7 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
cli.getProfileInfo(userId).then((info) => setProfileInfo(info));
}, [cli, userId]);
const { type, content } = transformEvent(event);
const { type, content } = transformEvent(event, userId);
// For the message preview we fake the sender as ourselves
const mockEvent = new MatrixEvent({

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