Compare commits

...

81 Commits

Author SHA1 Message Date
Robin
7d7a9ee847 Watch for a 'join' action to know when the call is connected
Previously we were watching for changes to the room state to know when you become connected to a call. However, the room state might not change if you had a stuck membership event prior to re-joining the call. It's going to be more reliable to watch for the 'join' action that Element Call sends, and use that to track the connection state.
2025-03-14 12:52:16 -04:00
Robin
5df083f009 Stop sending call notification events
Since I'd like Element Web to stop doing any more inspection than it needs to into the MatrixRTC session state, I suggest we move all responsibility for sending MatrixRTC events to Element Call.
2025-03-13 17:04:42 -04:00
Robin
991ce70209 Remove unused layout tracking code 2025-03-13 15:38:52 -04:00
Robin
60de81b824 Remove the unused 'preload' option 2025-03-13 15:38:52 -04:00
Florian Duros
20d8abf7c2 New room list: add primary filters (#29481)
* feat(room filter): add component for the primary filters

* feat(room filter): add filter component to room list view

* test(room filter): add tests to primary filters

* test: update snapshots

* test(e2e): update snapshots

* test(e2e): add tests for primary filters

* refactor: change aria-label of primary filters
2025-03-13 17:29:57 +00:00
Tulir Asokan
fda658182a Implement MSC4142: Remove unintentional intentional mentions in replies (#28209)
* Implement MSC4142: Remove unintentional intentional mentions in replies

* Fix comment
2025-03-13 16:00:54 +00:00
renovate[bot]
9bfea92b66 Update testcontainers-node monorepo to v10.19.0 (#29491)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-13 15:46:27 +00:00
Michael Telatynski
962136d453 Avoid using /tmp/ for bind mounts and non-tmpfs binds (#29488)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-13 14:51:01 +00:00
Florian Duros
917d53a56f Add wrap props to flex component (#29480)
* feat(flex): add wrap props to flex component

* test: update snapshot
2025-03-13 13:32:48 +00:00
Máté Gyöngyösi
e44ca88a7e Change ToggleHiddenEventVisibility & GoToHome KeyBindingActions (#29374)
The current keyboard shortcuts for GoToHome and ToggleHiddenEventVisibility are:

|                             	| other        	| macOS        	|
|-----------------------------	|--------------	|--------------	|
| GoToHome                    	| Ctrl–Alt–H   	| Ctrl–Shift–H 	|
| ToggleHiddenEventVisibility 	| Ctrl–Shift–H 	| Cmd–Shift–H  	|

This removes both distinctions for macOS in order ToggleHiddenEventVisibility not to conflict with...
1. the built-in Safari keyboard shortcut for opening the Home page (Cmd–Shift–H)
2. the KeyBindingAction for GoToHome.

Co-authored-by: Florian Duros <florianduros@element.io>
2025-03-13 09:46:29 +00:00
renovate[bot]
cb7d77de45 Update dependency @babel/runtime to v7.26.10 [SECURITY] (#29478)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-12 12:45:21 +00:00
renovate[bot]
cd6737942f Update dependency @babel/runtime to v7.26.10 [SECURITY] (#29478)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-12 12:45:21 +00:00
renovate[bot]
a058d85c21 Update playwright to v1.51.0 (#29469)
* Update playwright to v1.51.0

* Update screenshots

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-03-12 11:50:10 +00:00
Andy Balaam
bf6ae73d39 White background for 'They do not match' button (#29470) 2025-03-12 08:04:17 +00:00
ElementRobot
273cdf41e9 [create-pull-request] automated change (#29476)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-12 06:20:02 +00:00
ElementRobot
3ab3041c45 [create-pull-request] automated change (#29475)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-12 06:14:50 +00:00
Ben Banfield-Zanin
2052080d7d Fix Docker Healthcheck (#29471)
* Correct test for docker container being healthy

* Correct docker healthcheck for busybox wget

* Repeatedly check the health state of the docker container

* Use until loop & rely on timeout-minutes

* Fix check to look at healthy state

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-11 18:04:33 +00:00
dependabot[bot]
cc95d154fb Bump axios from 1.8.1 to 1.8.2 (#29468)
Bumps [axios](https://github.com/axios/axios) from 1.8.1 to 1.8.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.8.1...v1.8.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-11 15:03:42 +00:00
RiotRobot
4e696d2dc6 Reset matrix-js-sdk back to develop branch 2025-03-11 14:44:08 +00:00
RiotRobot
610b14adc1 Merge branch 'master' into develop 2025-03-11 14:43:56 +00:00
RiotRobot
324dd5a858 v1.11.95 2025-03-11 14:40:55 +00:00
RiotRobot
bc4bc6c25e Upgrade dependency to matrix-js-sdk@37.1.0 2025-03-11 14:36:16 +00:00
R Midhun Suresh
3f3fba99eb RoomListViewModel: Support secondary filters in the view model (#29465)
* Support secondary filters in the view model

* Write view model tests

* Fix RoomList test

* Add more comments
2025-03-11 13:29:55 +00:00
ElementRobot
26a17f9314 [create-pull-request] automated change (#29455)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-11 10:34:05 +00:00
R Midhun Suresh
fd91e78152 RoomListViewModel: Support primary filters in the view model (#29454)
* Track available filters and expose this info from the vm

- Adds a separate hook that tracks the filtered rooms and the available
  filters.
- When secondary filters are added, some of the primary filters will be
  selectively hidden. So track this info in the vm.

* Write tests

* Fix typescript error

* Fix translation

* Explain what a primary filter is
2025-03-10 13:23:38 +00:00
R Midhun Suresh
af476905b6 Room List Store: Fetch rooms after space store is ready + attach store to window (#29453)
* Attach the new store to window

* Fetch rooms after space store is ready

If we fetch a list of rooms and then wait for the space store to be
ready, we will need some way of handling the onAction calls we get
while we wait. These calls are dropped now.
2025-03-10 12:33:06 +00:00
R Midhun Suresh
da87bbe854 Room List Store: Fix bug where left rooms appear in room list (#29452)
* Write failing test

* Remove room when membership changes from JOIN to LEAVE
2025-03-10 11:58:12 +00:00
R Midhun Suresh
47976447b5 Room List Store: Implement secondary filters (#29458)
* Implement the secondary filters

* Use the new filters in the store

* Write tests
2025-03-10 11:37:37 +00:00
Will Hunt
53065f9437 Add E2E test for quick settings dialog (#29441)
* Update quick settings menu to use a11y roles.

* Add e2e test to test quick menu rendering

* Use a testid for now.

* lint lint

* Revert aria changes

* revert managed

* write screenshot
2025-03-10 09:12:38 +00:00
ElementRobot
179b368809 [create-pull-request] automated change (#29447)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-08 06:14:47 +00:00
R Midhun Suresh
82957507d0 Room List Store: Implement rest of the primary filters (#29444)
* Implement rest of the primary filters

* Support the new filters in the store
2025-03-07 10:59:39 +00:00
ElementRobot
27c1b38dab [create-pull-request] automated change (#29443)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-07 06:20:44 +00:00
R Midhun Suresh
7ff1fd259d Room List Store: Support filters by implementing just the favourite filter (#29433)
* Implement the favourite filter

* Make the room node capable of dealing with filters

- Holds data to indicate which filters apply
- Provides method to check if a given set of filters apply to this node
- Provides a method to recalculate which filters apply

* Wire up the filtering mechanism in skip list

* Use filters in the store

* Remove else

* Use a set instead of map
2025-03-06 12:43:25 +00:00
Will Hunt
8d891cde53 Move toggle switch for integration manager for a11y (#29436)
* Move toggle switch for integration manager for a11y

* Update test

* add toggle_label

* lint
2025-03-06 11:55:41 +00:00
Florian Duros
90cc44b340 New room list: basic flat list (#29368)
* chore: make the room list panel a flexbox

* feat(new room list): add `RoomListCell` component

* feat(new room list): add virtualized `RoomList` component

* feat(new room list): add `RoomListView` component

* feat(new room list): use `RoomListView` in `RoomListPanel`

* test(new room list): add test for room cell

* test(new room list): update room list panel tests

* test(new room list): add test to virtualized room list

* test(e2e): add room list tests

* test(e2e): update room panel tests
2025-03-06 10:01:55 +00:00
David Langley
b6c872142b Add space to the bottom of the room summary actions below leave room (#29270)
* Add space to the bottom of the room summary actions below leave room

* 8x not 6x

* Spacing needs to be within the scoll content, add it to the bottom of the leave action

* Update RoomSummaryCard-test.tsx.snap

* Fix snapshot and add screenshot test
2025-03-06 08:44:29 +00:00
Will Hunt
3762d40620 Improve rageshake upload experience by providing useful error information (#29378)
* Refactor submit rageshake so that it uses the new error codes.

* Improve error information given in Bug Report Dialog

* use type

* Refactor with generic error & policy link.

* lint

* lint

* Add BugReportDialog test

* fix time travel

* use waitFor while waiting for fetch to finish

* lint

* Drop error prefix as per 3973bb38ef

* small fixes

* Don't change string here.

* Fixup i18n strings.
2025-03-06 08:38:41 +00:00
ElementRobot
42192cbe06 [create-pull-request] automated change (#29431)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-06 06:15:39 +00:00
Robin
aa996010b4 Show error screens in group calls (#29254)
* Avoid destroying calls until they are hidden from the UI

We often want calls to exist even when no more participants are left in the MatrixRTC session. So, we should avoid destroying calls as long as they're being presented in the UI; this means that the user has an intent to either join the call or continue looking at an error screen, and we shouldn't interrupt that interaction.

The RoomViewStore is now what takes care of creating and destroying calls, rather than the CallView. In general it seems kinda impossible to safely create and destroy model objects from React lifecycle hooks, so moving this responsibility to a store seemed appropriate and resolves existing issues with calls in React strict mode.

* Wait for a close action before closing a call

This creates a distinction between the user hanging up and the widget being ready to close, which is useful for allowing Element Call to show error screens when disconnected from the call, for example.

* Don't expect a 'close' action in video rooms

These use the returnToLobby option and are expected to remain visible when the user leaves the call.
2025-03-05 20:41:26 +00:00
R Midhun Suresh
e9a3625bd6 Add more functionality to the room list vm (#29402)
* Add more vm functionality

- Listen for updates from the store
- Provide a method to open rooms

* Write test
2025-03-05 08:54:04 +00:00
ElementRobot
343dd06929 [create-pull-request] automated change (#29428)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-05 06:20:01 +00:00
ElementRobot
77b6c3b526 [create-pull-request] automated change (#29427)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-05 06:15:34 +00:00
renovate[bot]
b721505211 Update all non-major dependencies (#29415)
* Update all non-major dependencies

* Iterate

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-03-04 17:03:32 +00:00
Hubert Chathi
56c7fc1948 Prevent user from accidentally triggering multiple identity resets (#29388)
* prevent user from accidentally triggering multiple identity resets

* apply changes from review and update to latest design

* Use a CSS class and compound variable

* update snapshot

* Update test/unit-tests/components/views/settings/encryption/ResetIdentityPanel-test.tsx

---------

Co-authored-by: Richard van der Hoff <richard@matrix.org>
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
2025-03-04 16:40:35 +00:00
renovate[bot]
9d8efacede Update dependency @vector-im/matrix-wysiwyg to v2.38.2 (#28708)
* Update dependency @vector-im/matrix-wysiwyg to v2.38.2

* Fix types

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-03-04 15:36:57 +00:00
renovate[bot]
1770b94ed3 Update dependency typescript to v5.8.2 (#29417)
* Update dependency typescript to v5.8.2

* Fix types

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-03-04 15:01:20 +00:00
renovate[bot]
dfdac8ef63 Update dependency copy-webpack-plugin to v13 (#29423)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 14:51:37 +00:00
renovate[bot]
f1ebd85af1 Update dependency babel-loader to v10 (#29422)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 14:48:57 +00:00
renovate[bot]
4776a9971d Update typescript-eslint monorepo to v8.25.0 (#29420)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 14:48:06 +00:00
renovate[bot]
20ac69f379 Update guibranco/github-status-action-v2 digest to 5ef6e17 (#29409)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 14:48:00 +00:00
renovate[bot]
8c42b0bed8 Update stylelint (#29419)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 14:46:16 +00:00
renovate[bot]
fbc6f12408 Update dependency @types/react-virtualized to v9.22.2 (#29413)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 14:46:13 +00:00
renovate[bot]
b82c8554e3 Update fontsource monorepo to v5.2.5 (#29418)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 14:46:12 +00:00
renovate[bot]
3d705b1895 Update dependency caniuse-lite to v1.0.30001701 (#29414)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 14:45:47 +00:00
renovate[bot]
81c12db5ee Update dependency @sentry/browser to v9.3.0 (#29416)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 14:45:44 +00:00
renovate[bot]
e1d76e77a5 Update definitelyTyped (#29412)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 14:45:39 +00:00
renovate[bot]
54e015706c Update peter-evans/create-pull-request digest to 271a8d0 (#29410)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 14:45:16 +00:00
renovate[bot]
cef25c2cab Update sigstore/cosign-installer digest to d7d6bc7 (#29411)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 14:44:45 +00:00
renovate[bot]
59c26fc3ad Update docker (#29408)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 14:43:55 +00:00
Michael Telatynski
31af8b07dd Remove buggy tooltip on room intro & homepage (#29406)
* Remove buggy tooltip on room intro & homepage

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

* Add knip ignore

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-04 13:24:38 +00:00
RiotRobot
c0d14daa17 v1.11.95-rc.0 2025-03-04 12:51:06 +00:00
RiotRobot
ed35a7cba4 Upgrade dependency to matrix-js-sdk@37.1.0-rc.0 2025-03-04 12:46:15 +00:00
R Midhun Suresh
21e9d93e69 Room List Store: Filter rooms by active space (#29399)
* Add method to await space store setup

Otherwise, the room list store will get incorrect information about
spaces and thus will produce an incorrect roomlist.

* Implement a way to filter by active space

Implement a way to filter by active space

* Fix broken jest tests

* Fix typo

* Rename `isReady` to `storeReadyPromise`

* Fix mock in test
2025-03-04 11:27:41 +00:00
Richard van der Hoff
ffa8971195 Device dehydration: remove .well-known check (#29404)
* Device dehydrateion: remove .well-known check

Per https://github.com/element-hq/element-web/issues/29387, this is redundant

* Update src/utils/device/dehydration.ts

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

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-04 10:41:16 +00:00
ElementRobot
072ee0cf36 [create-pull-request] automated change (#29405)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-04 09:26:49 +00:00
Richard van der Hoff
4b02520453 Remove redundant UserIdentityWarning test (#29403)
* Remove redundant `UserIdentityWarning` test

Since the refactoring in https://github.com/element-hq/element-web/pull/29067,
this test is redundant.

It is also flaky and hard to understand. Time for it to die.

* delint
2025-03-03 15:28:46 +00:00
R Midhun Suresh
bf48100d31 Room List - Update the room list store on actions from the dispatcher (#29397)
* Update the store on action

* Add more tests

* Add newlines between case blocks

* Make code more readable

- Make if/else more consistent
- Add comment on findAndAddRoom()

* Add more tests

* Remove redundant code

On a timeline action, we return early if payload.room is falsy.
So then why do we need to retry fetching the room?
I think this can be removed but will ask others if there's some
conext I'm missing.

* Fix test

* Remove more redundant code

* Add more tests

* Explain intention in comment

* Emit only once even when adding multiple rooms

* Add missing tsdoc
2025-03-03 15:08:14 +00:00
R Midhun Suresh
2da21248bb Room List - Implement a minimal view model (#29357)
* Implement enough of the new store to get a list of rooms

* Make it possible to swap sorting algorithm

* Don't attach to window object

We don't want the store to be created if the labs flag is off

* Remove the store class

Probably best to include this PR with the minimal vm implmentation

* Create a new room list store that wraps around the skip list

* Create a minimal view model

* Fix CI

* Add some basic tests for the store

* Write more tests

* Add some jsdoc comments

* Add more documentation

* Add more docs
2025-03-03 11:12:00 +00:00
Florian Duros
3c57323595 Fix edited code block width (#29394)
* fix (event tile): make the markdown body take all the width when edited

* test (e2e): add code block test
2025-03-03 09:17:41 +00:00
ElementRobot
4c72f0c0b2 [create-pull-request] automated change (#29400)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-03 06:20:30 +00:00
ElementRobot
dfd08a8c01 Playwright Docker image updates (#29355)
* [create-pull-request] automated change

* Update synapse.ts

* Update synapse.ts

---------

Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-28 11:32:53 +00:00
Florian Duros
7db909a47d new room list: keep space name in one line in header (#29369)
* fix(new room list): keep space name in one line in header

* test(new room list): update tests
2025-02-28 08:34:06 +00:00
ElementRobot
1ad1387e05 [create-pull-request] automated change (#29389)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-02-28 06:21:55 +00:00
Michael Telatynski
c6b3bf962a Enable babel plugins to enable js-sdk to use decorators (#29376)
* Enable @babel/plugin-proposal-decorators

Only needed because we consume js-sdk code directly so its own transpiling isn't in play

This should separately be fixed, we should not need to have a superset of js-sdk's babel plugins

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

* Iterate

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-27 14:20:01 +00:00
Florian Duros
e749b017c9 refactor: rename mx_RoomList as mx_LegacyRoomList (#29362) 2025-02-27 13:34:54 +00:00
RiotRobot
c00262f0c5 Merge branch 'master' into develop 2025-02-27 13:19:26 +00:00
RiotRobot
7a513a2dc2 v1.11.94 2025-02-27 13:16:17 +00:00
ElementRobot
808412c6be fix: /tmp/element-web-config may already exist preventing the container from booting up (#29372) (#29377)
* fix: /tmp/element-web-config may already exist preventing the container from booting up

* Update docker/docker-entrypoint.d/18-load-element-modules.sh

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

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
(cherry picked from commit 45497905be)

Co-authored-by: Itay Grudev <itay+github.com@grudev.com>
2025-02-27 12:41:59 +00:00
Itay Grudev
45497905be fix: /tmp/element-web-config may already exist preventing the container from booting up (#29372)
* fix: /tmp/element-web-config may already exist preventing the container from booting up

* Update docker/docker-entrypoint.d/18-load-element-modules.sh

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

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-02-27 12:13:23 +00:00
Florian Duros
0997e0a747 refactor: rename RoomListView as RoomListPanel (#29361) 2025-02-26 11:14:04 +00:00
ElementRobot
6173c1224b [create-pull-request] automated change (#29364)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-02-26 06:23:19 +00:00
204 changed files with 5155 additions and 2036 deletions

View File

@@ -25,14 +25,14 @@ jobs:
fetch-depth: 0 # needed for docker-package to be able to calculate the version
- name: Install Cosign
uses: sigstore/cosign-installer@c56c2d3e59e4281cc41dea2217323ba5694b171e # v3
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3
if: github.event_name != 'pull_request'
- name: Set up QEMU
uses: docker/setup-qemu-action@4574d27a4764455b42196d70a065bc6853246a25 # v3
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
with:
install: true
@@ -53,7 +53,7 @@ jobs:
- name: Build and load
id: test-build
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
with:
context: .
load: true
@@ -61,6 +61,7 @@ jobs:
- name: Test the image
env:
IMAGEID: ${{ steps.test-build.outputs.imageid }}
timeout-minutes: 2
run: |
set -x
@@ -76,7 +77,7 @@ jobs:
--rm \
-e "ELEMENT_WEB_PORT=$ELEMENT_WEB_PORT" \
-dp "$ELEMENT_WEB_PORT:$ELEMENT_WEB_PORT" \
-v $(pwd)/modules:/tmp/element-web-modules \
-v $(pwd)/modules:/modules \
"$IMAGEID" \
)
@@ -86,14 +87,16 @@ jobs:
test "$MODULE_0" = "/${MODULE_PATH}"
# Check healthcheck
test "$(docker inspect -f {{.State.Running}} $CONTAINER_ID)" == "true"
until test "$(docker inspect -f {{.State.Health.Status}} $CONTAINER_ID)" == "healthy"; do
sleep 1
done
# Clean up
docker stop "$CONTAINER_ID"
- name: Docker meta
id: meta
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
if: github.event_name != 'pull_request'
with:
images: |
@@ -107,7 +110,7 @@ jobs:
- name: Build and push
id: build-and-push
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6
if: github.event_name != 'pull_request'
with:
context: .

View File

@@ -23,7 +23,7 @@ jobs:
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7
with:
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
branch: actions/playwright-image-updates

View File

@@ -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@7ca807c2ba3401be532d29a876b93262108099fb
uses: guibranco/github-status-action-v2@5ef6e175c333bc629f3718b083c8a2ff6e0bbfbc
with:
authToken: ${{ secrets.GITHUB_TOKEN }}
state: success

View File

@@ -23,7 +23,7 @@ jobs:
run: "yarn update:jitsi"
- name: Create Pull Request
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7
with:
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
branch: actions/jitsi-update

View File

@@ -1,3 +1,35 @@
Changes in [1.11.95](https://github.com/element-hq/element-web/releases/tag/v1.11.95) (2025-03-11)
==================================================================================================
## ✨ Features
* Room List Store: Filter rooms by active space ([#29399](https://github.com/element-hq/element-web/pull/29399)). Contributed by @MidhunSureshR.
* Room List - Update the room list store on actions from the dispatcher ([#29397](https://github.com/element-hq/element-web/pull/29397)). Contributed by @MidhunSureshR.
* Room List - Implement a minimal view model ([#29357](https://github.com/element-hq/element-web/pull/29357)). Contributed by @MidhunSureshR.
* New room list: add space menu in room header ([#29352](https://github.com/element-hq/element-web/pull/29352)). Contributed by @florianduros.
* Room List - Store sorted rooms in skip list ([#29345](https://github.com/element-hq/element-web/pull/29345)). Contributed by @MidhunSureshR.
* New room list: add dial to search section ([#29359](https://github.com/element-hq/element-web/pull/29359)). Contributed by @florianduros.
* New room list: add compose menu for spaces in header ([#29347](https://github.com/element-hq/element-web/pull/29347)). Contributed by @florianduros.
* Use EditInPlace control for Identity Server picker to improve a11y ([#29280](https://github.com/element-hq/element-web/pull/29280)). Contributed by @Half-Shot.
* First step to add header to new room list ([#29320](https://github.com/element-hq/element-web/pull/29320)). Contributed by @florianduros.
* Add Windows 64-bit arm link and remove 32-bit link on compatibility page ([#29312](https://github.com/element-hq/element-web/pull/29312)). Contributed by @t3chguy.
* Honour the backup disable flag from Element X ([#29290](https://github.com/element-hq/element-web/pull/29290)). Contributed by @dbkr.
## 🐛 Bug Fixes
* Fix edited code block width ([#29394](https://github.com/element-hq/element-web/pull/29394)). Contributed by @florianduros.
* new room list: keep space name in one line in header ([#29369](https://github.com/element-hq/element-web/pull/29369)). Contributed by @florianduros.
* Dismiss "Key storage out of sync" toast when secrets received ([#29348](https://github.com/element-hq/element-web/pull/29348)). Contributed by @richvdh.
* Minor CSS fixes for the new room list ([#29334](https://github.com/element-hq/element-web/pull/29334)). Contributed by @florianduros.
* Add padding to room header icon ([#29271](https://github.com/element-hq/element-web/pull/29271)). Contributed by @langleyd.
Changes in [1.11.94](https://github.com/element-hq/element-web/releases/tag/v1.11.94) (2025-02-27)
==================================================================================================
## 🐛 Bug Fixes
* [Backport staging] fix: /tmp/element-web-config may already exist preventing the container from booting up ([#29377](https://github.com/element-hq/element-web/pull/29377)). Contributed by @RiotRobot.
Changes in [1.11.93](https://github.com/element-hq/element-web/releases/tag/v1.11.93) (2025-02-25)
==================================================================================================
## ✨ Features

View File

@@ -1,4 +1,4 @@
# syntax=docker.io/docker/dockerfile:1.7-labs
# syntax=docker.io/docker/dockerfile:1.14-labs
# Builder
FROM --platform=$BUILDPLATFORM node:22-bullseye AS builder
@@ -47,4 +47,4 @@ USER nginx
# HTTP listen port
ENV ELEMENT_WEB_PORT=80
HEALTHCHECK --start-period=5s CMD wget --retry-connrefused --tries=5 -q --wait=3 --spider http://localhost:$ELEMENT_WEB_PORT/config.json
HEALTHCHECK --start-period=5s CMD wget -q --spider http://localhost:$ELEMENT_WEB_PORT/config.json

View File

@@ -31,5 +31,7 @@ module.exports = {
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-runtime",
["@babel/plugin-proposal-decorators", { version: "2023-11" }], // only needed by the js-sdk
"@babel/plugin-transform-class-static-block", // only needed by the js-sdk for decorators
],
};

View File

@@ -1,6 +1,6 @@
#!/bin/sh
# Loads modules from `/tmp/element-web-modules` into config.json's `modules` field
# Loads modules from `/modules` into config.json's `modules` field
set -e
@@ -11,19 +11,19 @@ entrypoint_log() {
}
# Copy these config files as a base
mkdir /tmp/element-web-config
mkdir -p /tmp/element-web-config
cp /app/config*.json /tmp/element-web-config/
# If there are modules to be loaded
if [ -d "/tmp/element-web-modules" ]; then
cd /tmp/element-web-modules
if [ -d "/modules" ]; then
cd /modules
for MODULE in *
do
# If the module has a package.json, use its main field as the entrypoint
ENTRYPOINT="index.js"
if [ -f "/tmp/element-web-modules/$MODULE/package.json" ]; then
ENTRYPOINT=$(jq -r '.main' "/tmp/element-web-modules/$MODULE/package.json")
if [ -f "/modules/$MODULE/package.json" ]; then
ENTRYPOINT=$(jq -r '.main' "/modules/$MODULE/package.json")
fi
entrypoint_log "Loading module $MODULE with entrypoint $ENTRYPOINT"

View File

@@ -22,7 +22,7 @@ server {
add_header Cache-Control "no-cache";
}
location /modules {
alias /tmp/element-web-modules;
alias /modules;
}
# redirect server error pages to the static page /50x.html
#

View File

@@ -67,7 +67,7 @@ image as root (`docker run --user 0`) or, better, change the port that nginx
listens on via the `ELEMENT_WEB_PORT` environment variable.
[Element Web Modules](https://github.com/element-hq/element-modules/tree/main/packages/element-web-module-api) can be dynamically loaded
by being made available (e.g. via bind mount) in a directory within `/tmp/element-web-modules/`.
by being made available (e.g. via bind mount) in a directory within `/modules/`.
The default entrypoint will be index.js in that directory but can be overridden if a package.json file is found with a `main` directive.
These modules will be presented in a `/modules` subdirectory within the webroot, and automatically added to the config.json `modules` field.
@@ -75,7 +75,7 @@ If you wish to use docker in read-only mode,
you should follow the [upstream instructions](https://hub.docker.com/_/nginx#:~:text=Running%20nginx%20in%20read%2Donly%20mode)
but additionally include the following directories:
- /tmp/element-web-config/
- /tmp/
- /etc/nginx/conf.d/
The behaviour of the docker image can be customised via the following

View File

@@ -19,6 +19,7 @@ export default {
ignore: [
// Keep for now
"src/hooks/useLocalStorageState.ts",
"src/hooks/useTimeout.ts",
"src/components/views/elements/InfoTooltip.tsx",
"src/components/views/elements/StyledCheckbox.tsx",
],

View File

@@ -1,6 +1,6 @@
{
"name": "element-web",
"version": "1.11.93",
"version": "1.11.95",
"description": "Element: the future of secure communication",
"author": "New Vector Ltd.",
"repository": {
@@ -74,7 +74,7 @@
"@types/react-dom": "18.3.5",
"oidc-client-ts": "3.1.0",
"jwt-decode": "4.0.0",
"caniuse-lite": "1.0.30001699",
"caniuse-lite": "1.0.30001701",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
"wrap-ansi": "npm:wrap-ansi@^7.0.0"
},
@@ -93,7 +93,7 @@
"@types/react-virtualized": "^9.21.30",
"@vector-im/compound-design-tokens": "^4.0.0",
"@vector-im/compound-web": "^7.6.4",
"@vector-im/matrix-wysiwyg": "2.38.0",
"@vector-im/matrix-wysiwyg": "2.38.2",
"@zxcvbn-ts/core": "^3.0.4",
"@zxcvbn-ts/language-common": "^3.0.4",
"@zxcvbn-ts/language-en": "^3.0.2",
@@ -138,7 +138,7 @@
"png-chunks-extract": "^1.0.0",
"posthog-js": "1.157.2",
"qrcode": "1.5.4",
"re-resizable": "6.10.3",
"re-resizable": "6.11.2",
"react": "^18.3.1",
"react-beautiful-dnd": "^13.1.0",
"react-blurhash": "^0.3.0",
@@ -162,9 +162,11 @@
"@babel/core": "^7.12.10",
"@babel/eslint-parser": "^7.12.10",
"@babel/eslint-plugin": "^7.12.10",
"@babel/plugin-proposal-decorators": "^7.25.9",
"@babel/plugin-proposal-export-default-from": "^7.12.1",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-class-properties": "^7.12.1",
"@babel/plugin-transform-class-static-block": "^7.26.0",
"@babel/plugin-transform-logical-assignment-operators": "^7.20.7",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.12.1",
"@babel/plugin-transform-numeric-separator": "^7.12.7",
@@ -218,12 +220,12 @@
"@typescript-eslint/eslint-plugin": "^8.19.0",
"@typescript-eslint/parser": "^8.19.0",
"babel-jest": "^29.0.0",
"babel-loader": "^9.0.0",
"babel-loader": "^10.0.0",
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
"blob-polyfill": "^9.0.0",
"chokidar": "^4.0.0",
"concurrently": "^9.0.0",
"copy-webpack-plugin": "^12.0.0",
"copy-webpack-plugin": "^13.0.0",
"core-js": "^3.38.1",
"cronstrue": "^2.41.0",
"css-loader": "^7.0.0",
@@ -274,7 +276,7 @@
"postcss-preset-env": "^10.0.0",
"postcss-scss": "^4.0.4",
"postcss-simple-vars": "^7.0.1",
"prettier": "3.5.1",
"prettier": "3.5.2",
"process": "^0.11.10",
"raw-loader": "^4.0.2",
"rimraf": "^6.0.0",
@@ -288,7 +290,7 @@
"terser-webpack-plugin": "^5.3.9",
"testcontainers": "^10.16.0",
"ts-node": "^10.9.1",
"typescript": "5.7.3",
"typescript": "5.8.2",
"util": "^0.12.5",
"web-streams-polyfill": "^4.0.0",
"webpack": "^5.89.0",

View File

@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/playwright:v1.50.1-noble
FROM mcr.microsoft.com/playwright:v1.51.0-noble
WORKDIR /work

View File

@@ -28,7 +28,7 @@ const checkDMRoom = async (page: Page) => {
};
const startDMWithBob = async (page: Page, bob: Bot) => {
await page.locator(".mx_RoomList").getByRole("button", { name: "Start chat" }).click();
await page.locator(".mx_LegacyRoomList").getByRole("button", { name: "Start chat" }).click();
await page.getByTestId("invite-dialog-input").fill(bob.credentials.userId);
await page.locator(".mx_InviteDialog_tile_nameStack_name").getByText("Bob").click();
await expect(

View File

@@ -22,18 +22,6 @@ test.use({
msc3814_enabled: true,
},
},
config: async ({ config, context }, use) => {
const wellKnown = {
...config.default_server_config,
"org.matrix.msc3814": true,
};
await context.route("https://localhost/.well-known/matrix/client", async (route) => {
await route.fulfill({ json: wellKnown });
});
await use(config);
},
});
test.describe("Dehydration", () => {

View File

@@ -77,7 +77,7 @@ test.describe("Invite dialog", function () {
"should support inviting a user to Direct Messages",
{ tag: "@screenshot" },
async ({ page, app, user, bot }) => {
await page.locator(".mx_RoomList").getByRole("button", { name: "Start chat" }).click();
await page.locator(".mx_LegacyRoomList").getByRole("button", { name: "Start chat" }).click();
const other = page.locator(".mx_InviteDialog_other");
// Assert that the header is rendered

View File

@@ -0,0 +1,88 @@
/*
* 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 { expect, test } from "../../../element-web-test";
import type { Page } from "@playwright/test";
test.describe("Room list filters and sort", () => {
test.use({
displayName: "Alice",
botCreateOpts: {
displayName: "BotBob",
autoAcceptInvites: true,
},
labsFlags: ["feature_new_room_list"],
});
/**
* Get the room list
* @param page
*/
function getRoomList(page: Page) {
return page.getByTestId("room-list");
}
function getPrimaryFilters(page: Page) {
return page.getByRole("listbox", { name: "Room list filters" });
}
test.beforeEach(async ({ page, app, bot, user }) => {
// The notification toast is displayed above the search section
await app.closeNotificationToast();
await app.client.createRoom({ name: "empty room" });
const unReadDmId = await bot.createRoom({
name: "unread dm",
invite: [user.userId],
is_direct: true,
});
await bot.sendMessage(unReadDmId, "I am a robot. Beep.");
const unReadRoomId = await app.client.createRoom({ name: "unread room" });
await app.client.inviteUser(unReadRoomId, bot.credentials.userId);
await bot.joinRoom(unReadRoomId);
await bot.sendMessage(unReadRoomId, "I am a robot. Beep.");
const favouriteId = await app.client.createRoom({ name: "favourite room" });
await app.client.evaluate(async (client, favouriteId) => {
await client.setRoomTag(favouriteId, "m.favourite", { order: 0.5 });
}, favouriteId);
});
test("should filter the list (with primary filters)", { tag: "@screenshot" }, async ({ page, app, user }) => {
const roomList = getRoomList(page);
const primaryFilters = getPrimaryFilters(page);
const allFilters = await primaryFilters.locator("option").all();
for (const filter of allFilters) {
expect(await filter.getAttribute("aria-selected")).toBe("false");
}
await expect(primaryFilters).toMatchScreenshot("unselected-primary-filters.png");
await primaryFilters.getByRole("option", { name: "Unread" }).click();
// only one room should be visible
await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible();
expect(await roomList.locator("role=gridcell").count()).toBe(2);
await expect(primaryFilters).toMatchScreenshot("unread-primary-filters.png");
await primaryFilters.getByRole("option", { name: "Favourite" }).click();
await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible();
expect(await roomList.locator("role=gridcell").count()).toBe(1);
await primaryFilters.getByRole("option", { name: "People" }).click();
await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible();
expect(await roomList.locator("role=gridcell").count()).toBe(1);
await primaryFilters.getByRole("option", { name: "Rooms" }).click();
await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible();
await expect(roomList.getByRole("gridcell", { name: "empty room" })).toBeVisible();
expect(await roomList.locator("role=gridcell").count()).toBe(3);
});
});

View File

@@ -9,7 +9,7 @@ import { type Page } from "@playwright/test";
import { test, expect } from "../../../element-web-test";
test.describe("Search section of the room list", () => {
test.describe("Room list panel", () => {
test.use({
labsFlags: ["feature_new_room_list"],
});
@@ -19,16 +19,23 @@ test.describe("Search section of the room list", () => {
* @param page
*/
function getRoomListView(page: Page) {
return page.getByTestId("room-list-view");
return page.getByTestId("room-list-panel");
}
test.beforeEach(async ({ page, app, user }) => {
// The notification toast is displayed above the search section
await app.closeNotificationToast();
// Populate the room list
for (let i = 0; i < 20; i++) {
await app.client.createRoom({ name: `room${i}` });
}
});
test("should render the room list view", { tag: "@screenshot" }, async ({ page, app, user }) => {
test("should render the room list panel", { tag: "@screenshot" }, async ({ page, app, user }) => {
const roomListView = getRoomListView(page);
await expect(roomListView).toMatchScreenshot("room-list-view.png");
// Wait for the last room to be visible
await expect(roomListView.getByRole("gridcell", { name: "Open room room19" })).toBeVisible();
await expect(roomListView).toMatchScreenshot("room-list-panel.png");
});
});

View File

@@ -0,0 +1,50 @@
/*
* 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 Page } from "@playwright/test";
import { test, expect } from "../../../element-web-test";
test.describe("Room list", () => {
test.use({
labsFlags: ["feature_new_room_list"],
});
/**
* Get the room list
* @param page
*/
function getRoomList(page: Page) {
return page.getByTestId("room-list");
}
test.beforeEach(async ({ page, app, user }) => {
// The notification toast is displayed above the search section
await app.closeNotificationToast();
for (let i = 0; i < 30; i++) {
await app.client.createRoom({ name: `room${i}` });
}
});
test("should render the room list", { tag: "@screenshot" }, async ({ page, app, user }) => {
const roomListView = getRoomList(page);
await expect(roomListView.getByRole("gridcell", { name: "Open room room29" })).toBeVisible();
await expect(roomListView).toMatchScreenshot("room-list.png");
await roomListView.hover();
// Scroll to the end of the room list
await page.mouse.wheel(0, 1000);
await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible();
await expect(roomListView).toMatchScreenshot("room-list-scrolled.png");
});
test("should open the room when it is clicked", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
await expect(page.getByRole("heading", { name: "room29", level: 1 })).toBeVisible();
});
});

View File

@@ -67,6 +67,15 @@ test.describe("RightPanel", () => {
},
);
test("should have padding under leave room", { tag: "@screenshot" }, async ({ page, app }) => {
await viewRoomSummaryByName(page, app, ROOM_NAME);
const leaveButton = await page.getByRole("menuitem", { name: "Leave Room" });
await leaveButton.scrollIntoViewIfNeeded();
await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("with-leave-room.png");
});
test("should handle clicking add widgets", async ({ page, app }) => {
await viewRoomSummaryByName(page, app, ROOM_NAME);

View File

@@ -0,0 +1,18 @@
/*
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 { test, expect } from "../../element-web-test";
test.describe("Quick settings menu", () => {
test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => {
await page.getByRole("button", { name: "Quick settings" }).click();
// Assert that the top heading is renderedc
const settings = page.getByTestId("quick-settings-menu");
await expect(settings).toBeVisible();
await expect(settings).toMatchScreenshot("quick-settings.png");
});
});

View File

@@ -91,7 +91,7 @@ test.describe("Security user settings tab", () => {
await expect(tab.getByText(`Identity server (identity.example.org)`, { exact: true })).toBeVisible();
});
test("should enable show integrations as enabled", async ({ app, page, user }) => {
test("should show integrations as enabled", async ({ app, page, user }) => {
const tab = await app.settings.openUserSettings("Security");
const setIntegrationManager = tab.locator(".mx_SetIntegrationManager");
@@ -102,7 +102,9 @@ test.describe("Security user settings tab", () => {
}),
).toBeVisible();
// Make sure integration manager's toggle switch is enabled
await expect(setIntegrationManager.locator(".mx_ToggleSwitch_enabled")).toBeVisible();
const toggleswitch = setIntegrationManager.getByLabel("Enable the integration manager");
await expect(toggleswitch).toBeVisible();
await expect(toggleswitch).toBeChecked();
await expect(setIntegrationManager.locator(".mx_SetIntegrationManager_heading_manager")).toHaveText(
"Manage integrations(scalar.vector.im)",
);

View File

@@ -875,6 +875,40 @@ test.describe("Timeline", () => {
);
});
});
test("should render a code block", { tag: "@screenshot" }, async ({ page, app, room }) => {
await page.goto(`/#/room/${room.roomId}`);
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
// Wait until configuration is finished
await expect(
page
.locator(".mx_GenericEventListSummary_summary")
.getByText(`${OLD_NAME} created and configured the room.`),
).toBeVisible();
// Send a code block
const composer = app.getComposerField();
await composer.fill("```\nconsole.log('Hello, world!');\n```");
await composer.press("Enter");
const tile = page.locator(".mx_EventTile");
await expect(tile).toBeVisible();
await expect(tile).toMatchScreenshot("code-block.png", { mask: [page.locator(".mx_MessageTimestamp")] });
// Edit a code block and assert the edited code block has been correctly rendered
await tile.hover();
await page.getByRole("toolbar", { name: "Message Actions" }).getByRole("button", { name: "Edit" }).click();
await page
.getByRole("textbox", { name: "Edit message" })
.fill("```\nconsole.log('Edited: Hello, world!');\n```");
await page.getByRole("textbox", { name: "Edit message" }).press("Enter");
const newTile = page.locator(".mx_EventTile");
await expect(newTile).toMatchScreenshot("edited-code-block.png", {
mask: [page.locator(".mx_MessageTimestamp")],
});
});
});
test.describe("message sending", { tag: ["@no-firefox", "@no-webkit"] }, () => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -25,7 +25,7 @@ import { type HomeserverContainer, type StartedHomeserverContainer } from "./Hom
import { type StartedMatrixAuthenticationServiceContainer } from "./mas.ts";
import { Api, ClientServerApi, type Verb } from "../plugins/utils/api.ts";
const TAG = "develop@sha256:8d1c531cf6010b63142a04e1b138a60720946fa131ad404813232f02db4ce7ba";
const TAG = "develop@sha256:26e0d9c5ca96218243432d48a9f8596e4c1bc10b748f0a1bddf9916b914d1216";
const DEFAULT_CONFIG = {
server_name: "localhost",
@@ -144,6 +144,7 @@ const DEFAULT_CONFIG = {
enabled: true,
include_offline_users_on_sync: true,
},
room_list_publication_rules: [{ action: "allow" }],
};
export type SynapseConfig = Partial<typeof DEFAULT_CONFIG>;

View File

@@ -269,9 +269,12 @@
@import "./views/right_panel/_VerificationPanel.pcss";
@import "./views/right_panel/_WidgetCard.pcss";
@import "./views/room_settings/_AliasSettings.pcss";
@import "./views/rooms/RoomListView/_RoomListHeaderView.pcss";
@import "./views/rooms/RoomListView/_RoomListSearch.pcss";
@import "./views/rooms/RoomListView/_RoomListView.pcss";
@import "./views/rooms/RoomListPanel/_RoomList.pcss";
@import "./views/rooms/RoomListPanel/_RoomListCell.pcss";
@import "./views/rooms/RoomListPanel/_RoomListHeaderView.pcss";
@import "./views/rooms/RoomListPanel/_RoomListPanel.pcss";
@import "./views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss";
@import "./views/rooms/RoomListPanel/_RoomListSearch.pcss";
@import "./views/rooms/_AppsDrawer.pcss";
@import "./views/rooms/_Autocomplete.pcss";
@import "./views/rooms/_AuxPanel.pcss";
@@ -289,6 +292,7 @@
@import "./views/rooms/_IRCLayout.pcss";
@import "./views/rooms/_InvitedIconView.pcss";
@import "./views/rooms/_JumpToBottomButton.pcss";
@import "./views/rooms/_LegacyRoomList.pcss";
@import "./views/rooms/_LegacyRoomListHeader.pcss";
@import "./views/rooms/_LinkPreviewGroup.pcss";
@import "./views/rooms/_LinkPreviewWidget.pcss";
@@ -313,7 +317,6 @@
@import "./views/rooms/_RoomHeader.pcss";
@import "./views/rooms/_RoomInfoLine.pcss";
@import "./views/rooms/_RoomKnocksBar.pcss";
@import "./views/rooms/_RoomList.pcss";
@import "./views/rooms/_RoomPreviewBar.pcss";
@import "./views/rooms/_RoomPreviewCard.pcss";
@import "./views/rooms/_RoomSearchAuxPanel.pcss";
@@ -362,6 +365,7 @@
@import "./views/settings/encryption/_EncryptionCard.pcss";
@import "./views/settings/encryption/_EncryptionCardEmphasisedContent.pcss";
@import "./views/settings/encryption/_RecoveryPanelOutOfSync.pcss";
@import "./views/settings/encryption/_ResetIdentityPanel.pcss";
@import "./views/settings/tabs/_SettingsBanner.pcss";
@import "./views/settings/tabs/_SettingsIndent.pcss";
@import "./views/settings/tabs/_SettingsSection.pcss";

View File

@@ -12,4 +12,5 @@ Please see LICENSE files in the repository root for full details.
align-items: var(--mx-flex-align, unset);
justify-content: var(--mx-flex-justify, unset);
gap: var(--mx-flex-gap, unset);
flex-wrap: var(--mx-flex-wrap, unset);
}

View File

@@ -100,3 +100,7 @@ Please see LICENSE files in the repository root for full details.
.mx_RoomSummaryCard_roomName {
margin: $spacing-12 0 $spacing-4;
}
.mx_RoomSummaryCard_leave {
margin: 0 0 var(--cpd-space-8x);
}

View File

@@ -0,0 +1,15 @@
/*
* 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.
*/
.mx_RoomList {
height: 100%;
.mx_RoomList_List {
/* Avoid when on hover, the background color to be on top of the right border */
padding-right: 1px;
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.
*/
/**
* The RoomCell has the following structure:
* button----------------------------------------|
* | <-12px-> container--------------------------|
* | | room avatar <-12px-> content-----|
* | | | room_name |
* | | | ----------| <-- border
* |---------------------------------------------|
*/
.mx_RoomListCell {
all: unset;
&:hover {
background-color: var(--cpd-color-bg-action-secondary-hovered);
}
.mx_RoomListCell_container {
padding-left: var(--cpd-space-3x);
font: var(--cpd-font-body-md-regular);
height: 100%;
.mx_RoomListCell_content {
height: 100%;
flex: 1;
/* The border is only under the room name and the future hover menu */
border-bottom: var(--cpd-border-width-0-5) solid var(--cpd-color-bg-subtle-secondary);
box-sizing: border-box;
min-width: 0;
span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}

View File

@@ -6,12 +6,19 @@
*/
.mx_RoomListHeaderView {
height: 60px;
flex: 0 0 60px;
padding: 0 var(--cpd-space-3x);
h1 {
all: unset;
font: var(--cpd-font-heading-sm-semibold);
.mx_RoomListHeaderView_title {
min-width: 0;
h1 {
all: unset;
font: var(--cpd-font-heading-sm-semibold);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
button {

View File

@@ -5,7 +5,7 @@
* Please see LICENSE files in the repository root for full details.
*/
.mx_RoomListView {
.mx_RoomListPanel {
background-color: var(--cpd-color-bg-canvas-default);
height: 100%;
border-right: 1px solid var(--cpd-color-bg-subtle-primary);

View File

@@ -0,0 +1,12 @@
/*
* 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.
*/
.mx_RoomListPrimaryFilters {
margin: unset;
list-style-type: none;
padding: var(--cpd-space-2x) var(--cpd-space-3x);
}

View File

@@ -7,7 +7,7 @@
.mx_RoomListSearch {
/* From figma, this should be aligned with the room header */
height: 64px;
flex: 0 0 64px;
box-sizing: border-box;
border-bottom: var(--cpd-border-width-1) solid var(--cpd-color-bg-subtle-primary);
padding: 0 var(--cpd-space-3x);

View File

@@ -683,6 +683,7 @@ $left-gutter: 64px;
line-height: inherit !important;
background-color: inherit;
color: inherit; /* inherit the colour from the dark or light theme by default (but not for code blocks) */
flex: 1;
pre,
code {

View File

@@ -6,31 +6,31 @@ 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.
*/
.mx_RoomList {
.mx_LegacyRoomList {
padding-right: 7px; /* width of the scrollbar, to line things up */
}
.mx_RoomList_iconPlus::before {
.mx_LegacyRoomList_iconPlus::before {
mask-image: url("$(res)/img/element-icons/roomlist/plus-circle.svg");
}
.mx_RoomList_iconNewRoom::before {
.mx_LegacyRoomList_iconNewRoom::before {
mask-image: url("$(res)/img/element-icons/roomlist/hash-plus.svg");
}
.mx_RoomList_iconNewVideoRoom::before {
.mx_LegacyRoomList_iconNewVideoRoom::before {
mask-image: url("$(res)/img/element-icons/roomlist/hash-video.svg");
}
.mx_RoomList_iconAddExistingRoom::before {
.mx_LegacyRoomList_iconAddExistingRoom::before {
mask-image: url("$(res)/img/element-icons/roomlist/hash.svg");
}
.mx_RoomList_iconExplore::before {
.mx_LegacyRoomList_iconExplore::before {
mask-image: url("$(res)/img/element-icons/roomlist/hash-search.svg");
}
.mx_RoomList_iconDialpad::before {
.mx_LegacyRoomList_iconDialpad::before {
mask-image: url("$(res)/img/element-icons/roomlist/dialpad.svg");
}
.mx_RoomList_iconStartChat::before {
.mx_LegacyRoomList_iconStartChat::before {
mask-image: url("@vector-im/compound-design-tokens/icons/user-add-solid.svg");
}
.mx_RoomList_iconInvite::before {
.mx_LegacyRoomList_iconInvite::before {
mask-image: url("$(res)/img/element-icons/room/share.svg");
}

View File

@@ -7,19 +7,13 @@ Please see LICENSE files in the repository root for full details.
*/
.mx_SetIntegrationManager {
.mx_SettingsFlag {
.mx_SetIntegrationManager_heading_manager {
display: flex;
align-items: center;
.mx_SetIntegrationManager_heading_manager {
display: flex;
align-items: center;
flex-wrap: wrap;
column-gap: $spacing-4;
}
.mx_ToggleSwitch {
align-self: flex-start;
min-width: var(--ToggleSwitch-min-width); /* avoid compression */
}
flex-wrap: wrap;
column-gap: $spacing-4;
}
form {
margin-top: var(--cpd-space-3x);
}
}

View File

@@ -0,0 +1,11 @@
/*
* Copyright 2024 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.
*/
// Red text for the "Do not close this window" warning
.mx_ResetIdentityPanel_warning {
color: var(--cpd-color-text-critical-primary);
}

View File

@@ -47,6 +47,7 @@ import { type DeepReadonly } from "./common";
import type MatrixChat from "../components/structures/MatrixChat";
import { type InitialCryptoSetupStore } from "../stores/InitialCryptoSetupStore";
import { type ModuleApiType } from "../modules/Api.ts";
import type { RoomListStoreV3Class } from "../stores/room-list-v3/RoomListStoreV3.ts";
/* eslint-disable @typescript-eslint/naming-convention */
@@ -99,6 +100,7 @@ declare global {
mxToastStore: ToastStore;
mxDeviceListener: DeviceListener;
mxRoomListStore: RoomListStore;
mxRoomListStoreV3: RoomListStoreV3Class;
mxRoomListLayoutStore: RoomListLayoutStore;
mxPlatformPeg: PlatformPeg;
mxIntegrationManagers: typeof IntegrationManagers;

View File

@@ -521,8 +521,7 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
[KeyBindingAction.GoToHome]: {
default: {
ctrlKey: true,
altKey: !IS_MAC,
shiftKey: IS_MAC,
altKey: true,
key: Key.H,
},
displayName: _td("keyboard|go_home_view"),
@@ -585,7 +584,7 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
},
[KeyBindingAction.ToggleHiddenEventVisibility]: {
default: {
ctrlOrCmdKey: true,
ctrlKey: true,
shiftKey: true,
key: Key.H,
},

View File

@@ -37,7 +37,7 @@ import PosthogTrackers from "../../PosthogTrackers";
import type PageType from "../../PageTypes";
import { Landmark, LandmarkNavigation } from "../../accessibility/LandmarkNavigation";
import SettingsStore from "../../settings/SettingsStore";
import { RoomListView } from "../views/rooms/RoomListView";
import { RoomListPanel } from "../views/rooms/RoomListPanel";
interface IProps {
isMinimized: boolean;
@@ -390,7 +390,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
return (
<div className={containerClasses}>
<div className="mx_LeftPanel_roomListContainer">
<RoomListView activeSpace={this.state.activeSpace} />
<RoomListPanel activeSpace={this.state.activeSpace} />
</div>
</div>
);

View File

@@ -118,8 +118,6 @@ import { RoomStatusBarUnsentMessages } from "./RoomStatusBarUnsentMessages";
import { LargeLoader } from "./LargeLoader";
import { isVideoRoom } from "../../utils/video-rooms";
import { SDKContext } from "../../contexts/SDKContext";
import { CallStore, CallStoreEvent } from "../../stores/CallStore";
import { type Call } from "../../models/Call";
import { RoomSearchView } from "./RoomSearchView";
import eventSearch, { type SearchInfo, SearchScope } from "../../Searching";
import VoipUserMapper from "../../VoipUserMapper";
@@ -190,7 +188,6 @@ export interface IRoomState {
*/
search?: SearchInfo;
callState?: CallState;
activeCall: Call | null;
canPeek: boolean;
canSelfRedact: boolean;
showApps: boolean;
@@ -401,7 +398,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
membersLoaded: !llMembers,
numUnreadMessages: 0,
callState: undefined,
activeCall: null,
canPeek: false,
canSelfRedact: false,
showApps: false,
@@ -577,7 +573,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
mainSplitContentType: room ? this.getMainSplitContentType(room) : undefined,
initialEventId: undefined, // default to clearing this, will get set later in the method if needed
showRightPanel: roomId ? this.context.rightPanelStore.isOpenForRoom(roomId) : false,
activeCall: roomId ? CallStore.instance.getActiveCall(roomId) : null,
promptAskToJoin: this.context.roomViewStore.promptAskToJoin(),
viewRoomOpts: this.context.roomViewStore.getViewRoomOpts(),
};
@@ -727,23 +722,17 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
});
};
private onConnectedCalls = (): void => {
if (this.state.roomId === undefined) return;
const activeCall = CallStore.instance.getActiveCall(this.state.roomId);
if (activeCall === null) {
// We disconnected from the call, so stop viewing it
defaultDispatcher.dispatch<ViewRoomPayload>(
{
action: Action.ViewRoom,
room_id: this.state.roomId,
view_call: false,
metricsTrigger: undefined,
},
true,
); // Synchronous so that CallView disappears immediately
}
this.setState({ activeCall });
private onCallClose = (): void => {
// Stop viewing the call
defaultDispatcher.dispatch<ViewRoomPayload>(
{
action: Action.ViewRoom,
room_id: this.state.roomId,
view_call: false,
metricsTrigger: undefined,
},
true,
); // Synchronous so that CallView disappears immediately
};
private getRoomId = (): string | undefined => {
@@ -900,8 +889,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
this.context.widgetStore.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
CallStore.instance.on(CallStoreEvent.ConnectedCalls, this.onConnectedCalls);
this.props.resizeNotifier.on("isResizing", this.onIsResizing);
this.settingWatchers = [
@@ -1027,7 +1014,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
);
}
CallStore.instance.off(CallStoreEvent.ConnectedCalls, this.onConnectedCalls);
this.context.legacyCallHandler.off(LegacyCallHandlerEvent.CallState, this.onCallState);
// cancel any pending calls to the throttled updated
@@ -2562,9 +2548,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
<CallView
room={this.state.room}
resizing={this.state.resizing}
waitForCall={isVideoRoom(this.state.room)}
skipLobby={this.context.roomViewStore.skipCallLobby() ?? false}
role="main"
onClose={this.onCallClose}
/>
{previewBar}
</>

View File

@@ -117,7 +117,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
<>
<IconizedContextMenuOption
label={_t("action|new_room")}
iconClassName="mx_RoomList_iconNewRoom"
iconClassName="mx_LegacyRoomList_iconNewRoom"
onClick={async (e): Promise<void> => {
e.preventDefault();
e.stopPropagation();
@@ -132,7 +132,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
{videoRoomsEnabled && (
<IconizedContextMenuOption
label={_t("action|new_video_room")}
iconClassName="mx_RoomList_iconNewVideoRoom"
iconClassName="mx_LegacyRoomList_iconNewVideoRoom"
onClick={async (e): Promise<void> => {
e.preventDefault();
e.stopPropagation();
@@ -157,7 +157,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
)}
<IconizedContextMenuOption
label={_t("action|add_existing_room")}
iconClassName="mx_RoomList_iconAddExistingRoom"
iconClassName="mx_LegacyRoomList_iconAddExistingRoom"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -168,7 +168,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
{canCreateSpace && (
<IconizedContextMenuOption
label={_t("room_list|add_space_label")}
iconClassName="mx_RoomList_iconPlus"
iconClassName="mx_LegacyRoomList_iconPlus"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();

View File

@@ -39,6 +39,11 @@ type FlexProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any
* @default start
*/
justify?: "start" | "center" | "end" | "space-between";
/**
* The wrapping of the flex children
* @default nowrap
*/
wrap?: "wrap" | "nowrap" | "wrap-reverse";
/**
* The spacing between the flex children, expressed with the CSS unit
* @default 0
@@ -60,6 +65,7 @@ export function Flex<T extends keyof JSX.IntrinsicElements | JSXElementConstruct
align = "start",
justify = "start",
gap = "0",
wrap = "nowrap",
className,
children,
...props
@@ -71,8 +77,9 @@ export function Flex<T extends keyof JSX.IntrinsicElements | JSXElementConstruct
"--mx-flex-align": align,
"--mx-flex-justify": justify,
"--mx-flex-gap": gap,
"--mx-flex-wrap": wrap,
}),
[align, direction, display, gap, justify],
[align, direction, display, gap, justify, wrap],
);
return React.createElement(as, { ...props, className: classNames("mx_Flex", className), style }, children);

View File

@@ -0,0 +1,66 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { useCallback } from "react";
import type { Room } from "matrix-js-sdk/src/matrix";
import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import dispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { type PrimaryFilter, type SecondaryFilters, useFilteredRooms } from "./useFilteredRooms";
export interface RoomListViewState {
/**
* A list of rooms to be displayed in the left panel.
*/
rooms: Room[];
/**
* Open the room having given roomId.
*/
openRoom: (roomId: string) => void;
/**
* A list of objects that provide the view enough information
* to render primary room filters.
*/
primaryFilters: PrimaryFilter[];
/**
* A function to activate a given secondary filter.
*/
activateSecondaryFilter: (filter: SecondaryFilters) => void;
/**
* The currently active secondary filter.
*/
activeSecondaryFilter: SecondaryFilters;
}
/**
* View model for the new room list
* @see {@link RoomListViewState} for more information about what this view model returns.
*/
export function useRoomListViewModel(): RoomListViewState {
const { primaryFilters, rooms, activateSecondaryFilter, activeSecondaryFilter } = useFilteredRooms();
const openRoom = useCallback((roomId: string): void => {
dispatcher.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
room_id: roomId,
metricsTrigger: "RoomList",
});
}, []);
return {
rooms,
openRoom,
primaryFilters,
activateSecondaryFilter,
activeSecondaryFilter,
};
}

View File

@@ -0,0 +1,188 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { useCallback, useMemo, useState } from "react";
import type { Room } from "matrix-js-sdk/src/matrix";
import { FilterKey } from "../../../stores/room-list-v3/skip-list/filters";
import { _t, _td, type TranslationKey } from "../../../languageHandler";
import RoomListStoreV3 from "../../../stores/room-list-v3/RoomListStoreV3";
import { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
import { useEventEmitter } from "../../../hooks/useEventEmitter";
/**
* Provides information about a primary filter.
* A primary filter is a commonly used filter that is given
* more precedence in the UI. For eg, primary filters may be
* rendered as pills above the room list.
*/
export interface PrimaryFilter {
// A function to toggle this filter on and off.
toggle: () => void;
// Whether this filter is currently applied
active: boolean;
// Text that can be used in the UI to represent this filter.
name: string;
}
interface FilteredRooms {
primaryFilters: PrimaryFilter[];
rooms: Room[];
activateSecondaryFilter: (filter: SecondaryFilters) => void;
activeSecondaryFilter: SecondaryFilters;
}
const filterKeyToNameMap: Map<FilterKey, TranslationKey> = new Map([
[FilterKey.UnreadFilter, _td("room_list|filters|unread")],
[FilterKey.FavouriteFilter, _td("room_list|filters|favourite")],
[FilterKey.PeopleFilter, _td("room_list|filters|people")],
[FilterKey.RoomsFilter, _td("room_list|filters|rooms")],
]);
/**
* These are the secondary filters which are not prominently shown
* in the UI.
*/
export const enum SecondaryFilters {
AllActivity,
MentionsOnly,
InvitesOnly,
LowPriority,
}
/**
* A map from {@link SecondaryFilters} which the UI understands to
* {@link FilterKey} which the store understands.
*/
const secondaryFiltersToFilterKeyMap = new Map([
[SecondaryFilters.AllActivity, undefined],
[SecondaryFilters.MentionsOnly, FilterKey.MentionsFilter],
[SecondaryFilters.InvitesOnly, FilterKey.InvitesFilter],
[SecondaryFilters.LowPriority, FilterKey.LowPriorityFilter],
]);
/**
* Use this function to determine if a given primary filter is compatible with
* a given secondary filter. Practically, this determines whether it makes sense
* to expose two filters together in the UI - for eg, it does not make sense to show the
* favourite primary filter if the active secondary filter is low priority.
* @param primary Primary filter key
* @param secondary Secondary filter key
* @returns true if compatible, false otherwise
*/
function isPrimaryFilterCompatible(primary: FilterKey, secondary: FilterKey): boolean {
if (secondary === FilterKey.MentionsFilter) {
if (primary === FilterKey.UnreadFilter) return false;
} else if (secondary === FilterKey.InvitesFilter) {
if (primary === FilterKey.UnreadFilter || primary === FilterKey.FavouriteFilter) return false;
} else if (secondary === FilterKey.LowPriorityFilter) {
if (primary === FilterKey.FavouriteFilter) return false;
}
return true;
}
/**
* Track available filters and provide a filtered list of rooms.
*/
export function useFilteredRooms(): FilteredRooms {
/**
* Primary filter refers to the pill based filters
* rendered above the room list.
*/
const [primaryFilter, setPrimaryFilter] = useState<FilterKey | undefined>();
/**
* Secondary filters are also filters but they are hidden
* away in a popup menu.
*/
const [activeSecondaryFilter, setActiveSecondaryFilter] = useState<SecondaryFilters>(SecondaryFilters.AllActivity);
const secondaryFilter = useMemo(
() => secondaryFiltersToFilterKeyMap.get(activeSecondaryFilter),
[activeSecondaryFilter],
);
const [rooms, setRooms] = useState(() => RoomListStoreV3.instance.getSortedRoomsInActiveSpace());
const updateRoomsFromStore = useCallback((filters: FilterKey[] = []): void => {
const newRooms = RoomListStoreV3.instance.getSortedRoomsInActiveSpace(filters);
setRooms(newRooms);
}, []);
const filterUndefined = (array: (FilterKey | undefined)[]): FilterKey[] =>
array.filter((f) => f !== undefined) as FilterKey[];
const getAppliedFilters = (): FilterKey[] => {
return filterUndefined([primaryFilter, secondaryFilter]);
};
useEventEmitter(RoomListStoreV3.instance, LISTS_UPDATE_EVENT, () => {
const filters = getAppliedFilters();
updateRoomsFromStore(filters);
});
/**
* Secondary filters are activated using this function.
* This is different to how primary filters work because the secondary
* filters are static i.e they are always available and don't need to be
* hidden.
*/
const activateSecondaryFilter = useCallback(
(filter: SecondaryFilters): void => {
// If the filter is already active, just return.
if (filter === activeSecondaryFilter) return;
// SecondaryFilter is an enum for the UI, let's convert it to something
// that the store will understand.
const secondary = secondaryFiltersToFilterKeyMap.get(filter);
// Active primary filter may need to be toggled off when applying this secondary filer.
let primary = primaryFilter;
if (
primaryFilter !== undefined &&
secondary !== undefined &&
!isPrimaryFilterCompatible(primaryFilter, secondary)
) {
primary = undefined;
}
setActiveSecondaryFilter(filter);
setPrimaryFilter(primary);
updateRoomsFromStore(filterUndefined([primary, secondary]));
},
[activeSecondaryFilter, primaryFilter, updateRoomsFromStore],
);
/**
* This tells the view which primary filters are available, how to toggle them
* and whether a given primary filter is active. @see {@link PrimaryFilter}
*/
const primaryFilters = useMemo(() => {
const createPrimaryFilter = (key: FilterKey, name: string): PrimaryFilter => {
return {
toggle: () => {
setPrimaryFilter((currentFilter) => {
const filter = currentFilter === key ? undefined : key;
updateRoomsFromStore(filterUndefined([filter, secondaryFilter]));
return filter;
});
},
active: primaryFilter === key,
name,
};
};
const filters: PrimaryFilter[] = [];
for (const [key, name] of filterKeyToNameMap.entries()) {
if (secondaryFilter && !isPrimaryFilterCompatible(key, secondaryFilter)) {
continue;
}
filters.push(createPrimaryFilter(key, _t(name)));
}
return filters;
}, [primaryFilter, updateRoomsFromStore, secondaryFilter]);
return { primaryFilters, rooms, activateSecondaryFilter, activeSecondaryFilter };
}

View File

@@ -9,12 +9,13 @@ 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.
*/
import React from "react";
import React, { type ReactNode } from "react";
import { Link } from "@vector-im/compound-web";
import SdkConfig from "../../../SdkConfig";
import Modal from "../../../Modal";
import { _t } from "../../../languageHandler";
import sendBugReport, { downloadBugReport } from "../../../rageshake/submit-rageshake";
import sendBugReport, { downloadBugReport, RageshakeError } from "../../../rageshake/submit-rageshake";
import AccessibleButton from "../elements/AccessibleButton";
import QuestionDialog from "./QuestionDialog";
import BaseDialog from "./BaseDialog";
@@ -26,7 +27,7 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { getBrowserSupport } from "../../../SupportedBrowser";
interface IProps {
export interface BugReportDialogProps {
onFinished: (success: boolean) => void;
initialText?: string;
label?: string;
@@ -36,7 +37,7 @@ interface IProps {
interface IState {
sendLogs: boolean;
busy: boolean;
err: string | null;
err: ReactNode | null;
issueUrl: string;
text: string;
progress: string | null;
@@ -44,11 +45,11 @@ interface IState {
downloadProgress: string | null;
}
export default class BugReportDialog extends React.Component<IProps, IState> {
export default class BugReportDialog extends React.Component<BugReportDialogProps, IState> {
private unmounted: boolean;
private issueRef: React.RefObject<Field>;
public constructor(props: IProps) {
public constructor(props: BugReportDialogProps) {
super(props);
this.state = {
@@ -89,6 +90,42 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
this.props.onFinished(false);
};
private getErrorText(error: Error | RageshakeError): ReactNode {
if (error instanceof RageshakeError) {
let errorText;
switch (error.errorcode) {
case "DISALLOWED_APP":
errorText = _t("bug_reporting|failed_send_logs_causes|disallowed_app");
break;
case "REJECTED_BAD_VERSION":
errorText = _t("bug_reporting|failed_send_logs_causes|rejected_version");
break;
case "REJECTED_UNEXPECTED_RECOVERY_KEY":
errorText = _t("bug_reporting|failed_send_logs_causes|rejected_recovery_key");
break;
default:
if (error.errorcode?.startsWith("REJECTED")) {
errorText = _t("bug_reporting|failed_send_logs_causes|rejected_generic");
} else {
errorText = _t("bug_reporting|failed_send_logs_causes|server_unknown_error");
}
break;
}
return (
<>
<p>{errorText}</p>
{error.policyURL && (
<Link size="medium" target="_blank" href={error.policyURL}>
{_t("action|learn_more")}
</Link>
)}
</>
);
} else {
return <p>{_t("bug_reporting|failed_send_logs_causes|unknown_error")}</p>;
}
}
private onSubmit = (): void => {
if ((!this.state.text || !this.state.text.trim()) && (!this.state.issueUrl || !this.state.issueUrl.trim())) {
this.setState({
@@ -126,7 +163,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
this.setState({
busy: false,
progress: null,
err: _t("bug_reporting|failed_send_logs") + `${err.message}`,
err: this.getErrorText(err),
});
}
},
@@ -155,7 +192,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
this.setState({
downloadBusy: false,
downloadProgress:
_t("bug_reporting|failed_send_logs") + `${err instanceof Error ? err.message : ""}`,
_t("bug_reporting|failed_download_logs") + `${err instanceof Error ? err.message : ""}`,
});
}
}

View File

@@ -9,10 +9,8 @@ Please see LICENSE files in the repository root for full details.
import classNames from "classnames";
import { EventType } from "matrix-js-sdk/src/matrix";
import React, { useContext, useRef, useState, type MouseEvent, type ReactNode } from "react";
import { Tooltip } from "@vector-im/compound-web";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { useTimeout } from "../../../hooks/useTimeout";
import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds";
import AccessibleButton from "./AccessibleButton";
import Spinner from "./Spinner";
@@ -42,15 +40,6 @@ const MiniAvatarUploader: React.FC<IProps> = ({
}) => {
const cli = useContext(MatrixClientContext);
const [busy, setBusy] = useState(false);
const [hover, setHover] = useState(false);
const [show, setShow] = useState(false);
useTimeout(() => {
setShow(true);
}, 3000); // show after 3 seconds
useTimeout(() => {
setShow(false);
}, 13000); // hide after being shown for 10 seconds
const uploadRef = useRef<HTMLInputElement>(null);
@@ -61,7 +50,6 @@ const MiniAvatarUploader: React.FC<IProps> = ({
isUserAvatar || room?.currentState?.maySendStateEvent(EventType.RoomAvatar, cli.getSafeUserId());
if (!canSetAvatar) return <React.Fragment>{children}</React.Fragment>;
const visible = !!label && (hover || show);
return (
<React.Fragment>
<input
@@ -84,24 +72,23 @@ const MiniAvatarUploader: React.FC<IProps> = ({
accept="image/*"
/>
<Tooltip label={label!} open={visible} onOpenChange={setHover}>
<AccessibleButton
className={classNames("mx_MiniAvatarUploader", {
mx_MiniAvatarUploader_busy: busy,
mx_MiniAvatarUploader_hasAvatar: hasAvatar,
})}
disabled={busy}
onClick={() => {
uploadRef.current?.click();
}}
>
{children}
<AccessibleButton
className={classNames("mx_MiniAvatarUploader", {
mx_MiniAvatarUploader_busy: busy,
mx_MiniAvatarUploader_hasAvatar: hasAvatar,
})}
disabled={busy}
onClick={() => {
uploadRef.current?.click();
}}
aria-label={label}
>
{children}
<div className="mx_MiniAvatarUploader_indicator">
{busy ? <Spinner w={20} h={20} /> : <div className="mx_MiniAvatarUploader_cameraIcon" />}
</div>
</AccessibleButton>
</Tooltip>
<div className="mx_MiniAvatarUploader_indicator">
{busy ? <Spinner w={20} h={20} /> : <div className="mx_MiniAvatarUploader_cameraIcon" />}
</div>
</AccessibleButton>
</React.Fragment>
);
};

View File

@@ -126,10 +126,6 @@ const ActiveLoadedCallEvent = forwardRef<any, ActiveLoadedCallEventProps>(({ mxE
return [_t("action|leave"), "danger", disconnect];
case ConnectionState.Disconnecting:
return [_t("action|leave"), "danger", null];
case ConnectionState.Connecting:
case ConnectionState.Lobby:
case ConnectionState.WidgetLoading:
return [_t("action|join"), "primary", null];
}
}, [connectionState, connect, disconnect]);

View File

@@ -441,6 +441,7 @@ const RoomSummaryCard: React.FC<IProps> = ({
<Separator />
<MenuItem
className="mx_RoomSummaryCard_leave"
Icon={LeaveIcon}
kind="critical"
label={_t("action|leave_room")}

View File

@@ -142,7 +142,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
{showCreateRooms && (
<IconizedContextMenuOption
label={_t("action|start_new_chat")}
iconClassName="mx_RoomList_iconStartChat"
iconClassName="mx_LegacyRoomList_iconStartChat"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -158,7 +158,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
{showInviteUsers && (
<IconizedContextMenuOption
label={_t("action|invite_to_space")}
iconClassName="mx_RoomList_iconInvite"
iconClassName="mx_LegacyRoomList_iconInvite"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -230,7 +230,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
<IconizedContextMenuOptionList first>
<IconizedContextMenuOption
label={_t("action|explore_rooms")}
iconClassName="mx_RoomList_iconExplore"
iconClassName="mx_LegacyRoomList_iconExplore"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -247,7 +247,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
<>
<IconizedContextMenuOption
label={_t("action|new_room")}
iconClassName="mx_RoomList_iconNewRoom"
iconClassName="mx_LegacyRoomList_iconNewRoom"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -261,7 +261,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
{videoRoomsEnabled && (
<IconizedContextMenuOption
label={_t("action|new_video_room")}
iconClassName="mx_RoomList_iconNewVideoRoom"
iconClassName="mx_LegacyRoomList_iconNewVideoRoom"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -279,7 +279,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
)}
<IconizedContextMenuOption
label={_t("action|add_existing_room")}
iconClassName="mx_RoomList_iconAddExistingRoom"
iconClassName="mx_LegacyRoomList_iconAddExistingRoom"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -300,7 +300,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
<>
<IconizedContextMenuOption
label={_t("action|new_room")}
iconClassName="mx_RoomList_iconNewRoom"
iconClassName="mx_LegacyRoomList_iconNewRoom"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -312,7 +312,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
{videoRoomsEnabled && (
<IconizedContextMenuOption
label={_t("action|new_video_room")}
iconClassName="mx_RoomList_iconNewVideoRoom"
iconClassName="mx_LegacyRoomList_iconNewVideoRoom"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -333,7 +333,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
{showExploreRooms ? (
<IconizedContextMenuOption
label={_t("action|explore_public_rooms")}
iconClassName="mx_RoomList_iconExplore"
iconClassName="mx_LegacyRoomList_iconExplore"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@@ -678,7 +678,7 @@ export default class LegacyRoomList extends React.PureComponent<IProps, IState>
}
onKeyDownHandler(ev);
}}
className="mx_RoomList"
className="mx_LegacyRoomList"
role="tree"
aria-label={_t("common|rooms")}
ref={this.treeRef}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import React, { useCallback, type JSX } from "react";
import { AutoSizer, List, type ListRowProps } from "react-virtualized";
import { type RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel";
import { _t } from "../../../../languageHandler";
import { RoomListCell } from "./RoomListCell";
interface RoomListProps {
/**
* The view model state for the room list.
*/
vm: RoomListViewState;
}
/**
* A virtualized list of rooms.
*/
export function RoomList({ vm: { rooms, openRoom } }: RoomListProps): JSX.Element {
const roomRendererMemoized = useCallback(
({ key, index, style }: ListRowProps) => (
<RoomListCell room={rooms[index]} key={key} style={style} onClick={() => openRoom(rooms[index].roomId)} />
),
[rooms, openRoom],
);
// The first div is needed to make the virtualized list take all the remaining space and scroll correctly
return (
<div className="mx_RoomList" data-testid="room-list">
<AutoSizer>
{({ height, width }) => (
<List
aria-label={_t("room_list|list_title")}
className="mx_RoomList_List"
rowRenderer={roomRendererMemoized}
rowCount={rooms.length}
rowHeight={48}
height={height}
width={width}
/>
)}
</AutoSizer>
</div>
);
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX } from "react";
import { type Room } from "matrix-js-sdk/src/matrix";
import { _t } from "../../../../languageHandler";
import { Flex } from "../../../utils/Flex";
import DecoratedRoomAvatar from "../../avatars/DecoratedRoomAvatar";
interface RoomListCellProps extends React.HTMLAttributes<HTMLButtonElement> {
/**
* The room to display
*/
room: Room;
}
/**
* A cell in the room list
*/
export function RoomListCell({ room, ...props }: RoomListCellProps): JSX.Element {
return (
<button
className="mx_RoomListCell"
type="button"
aria-label={_t("room_list|room|open_room", { roomName: room.name })}
{...props}
>
{/* We need this extra div between the button and the content in order to add a padding which is not messing with the virtualized list */}
<Flex className="mx_RoomListCell_container" gap="var(--cpd-space-3x)" align="center">
<DecoratedRoomAvatar room={room} size="32px" />
<Flex className="mx_RoomListCell_content" align="center">
{/* We truncate the room name when too long. Title here is to show the full name on hover */}
<span title={room.name}>{room.name}</span>
{/* Future hover menu et notification badges */}
</Flex>
</Flex>
</button>
);
}

View File

@@ -38,8 +38,8 @@ export function RoomListHeaderView(): JSX.Element {
align="center"
data-testid="room-list-header"
>
<Flex align="center" gap="var(--cpd-space-1x)">
<h1>{vm.title}</h1>
<Flex className="mx_RoomListHeaderView_title" align="center" gap="var(--cpd-space-1x)">
<h1 title={vm.title}>{vm.title}</h1>
{vm.displaySpaceMenu && <SpaceMenu vm={vm} />}
</Flex>
{vm.displayComposeMenu && <ComposeMenu vm={vm} />}

View File

@@ -11,8 +11,10 @@ import { shouldShowComponent } from "../../../../customisations/helpers/UICompon
import { UIComponent } from "../../../../settings/UIFeature";
import { RoomListSearch } from "./RoomListSearch";
import { RoomListHeaderView } from "./RoomListHeaderView";
import { RoomListView } from "./RoomListView";
import { Flex } from "../../../utils/Flex";
type RoomListViewProps = {
type RoomListPanelProps = {
/**
* Current active space
* See {@link RoomListSearch}
@@ -21,15 +23,22 @@ type RoomListViewProps = {
};
/**
* A view component for the room list.
* The panel of the room list
*/
export const RoomListView: React.FC<RoomListViewProps> = ({ activeSpace }) => {
export const RoomListPanel: React.FC<RoomListPanelProps> = ({ activeSpace }) => {
const displayRoomSearch = shouldShowComponent(UIComponent.FilterContainer);
return (
<section className="mx_RoomListView" data-testid="room-list-view">
<Flex
as="section"
className="mx_RoomListPanel"
data-testid="room-list-panel"
direction="column"
align="stretch"
>
{displayRoomSearch && <RoomListSearch activeSpace={activeSpace} />}
<RoomListHeaderView />
</section>
<RoomListView />
</Flex>
);
};

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX } from "react";
import { ChatFilter } from "@vector-im/compound-web";
import type { RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel";
import { Flex } from "../../../utils/Flex";
import { _t } from "../../../../languageHandler";
interface RoomListPrimaryFiltersProps {
/**
* The view model for the room list
*/
vm: RoomListViewState;
}
/**
* The primary filters for the room list
*/
export function RoomListPrimaryFilters({ vm }: RoomListPrimaryFiltersProps): JSX.Element {
return (
<Flex
as="ul"
role="listbox"
aria-label={_t("room_list|primary_filters")}
className="mx_RoomListPrimaryFilters"
align="center"
gap="var(--cpd-space-2x)"
wrap="wrap"
>
{vm.primaryFilters.map((filter) => (
<li role="option" aria-selected={filter.active} key={filter.name}>
<ChatFilter selected={filter.active} onClick={filter.toggle}>
{filter.name}
</ChatFilter>
</li>
))}
</Flex>
);
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX } from "react";
import { useRoomListViewModel } from "../../../viewmodels/roomlist/RoomListViewModel";
import { RoomList } from "./RoomList";
import { RoomListPrimaryFilters } from "./RoomListPrimaryFilters";
/**
* Host the room list and the (future) room filters
*/
export function RoomListView(): JSX.Element {
const vm = useRoomListViewModel();
return (
<>
<RoomListPrimaryFilters vm={vm} />
<RoomList vm={vm} />
</>
);
}

View File

@@ -5,4 +5,4 @@
* Please see LICENSE files in the repository root for full details.
*/
export { RoomListView } from "./RoomListView";
export { RoomListPanel } from "./RoomListPanel";

View File

@@ -27,18 +27,6 @@ export const RoomTileCallSummary: FC<Props> = ({ call }) => {
text = _t("common|video");
active = false;
break;
case ConnectionState.WidgetLoading:
text = _t("common|loading");
active = false;
break;
case ConnectionState.Lobby:
text = _t("common|lobby");
active = false;
break;
case ConnectionState.Connecting:
text = _t("room|joining");
active = true;
break;
case ConnectionState.Connected:
case ConnectionState.Disconnecting:
text = _t("common|joined");

View File

@@ -95,16 +95,9 @@ export function attachMentions(
const userMentions = new Set<string>();
let roomMention = false;
// If there's a reply, initialize the mentioned users as the sender of that
// event + any mentioned users in that event.
// If there's a reply, initialize the mentioned users as the sender of that event.
if (replyToEvent) {
userMentions.add(replyToEvent.sender!.userId);
// TODO What do we do if the reply event *doeesn't* have this property?
// Try to fish out replies from the contents?
const userIds = replyToEvent.getContent()["m.mentions"]?.user_ids;
if (Array.isArray(userIds)) {
userIds.forEach((userId) => userMentions.add(userId));
}
}
// If user provided content is available, check to see if any users are mentioned.

View File

@@ -22,7 +22,7 @@ interface EditorProps {
}
export const Editor = memo(
forwardRef<HTMLDivElement, EditorProps>(function Editor(
forwardRef<HTMLDivElement | null, EditorProps>(function Editor(
{ disabled, placeholder, leftComponent, rightComponent }: EditorProps,
ref,
) {

View File

@@ -10,7 +10,7 @@ import { type RefObject, useEffect } from "react";
import { setCursorPositionAtTheEnd } from "./utils";
export function useSetCursorPosition(disabled: boolean, ref: RefObject<HTMLElement>): void {
export function useSetCursorPosition(disabled: boolean, ref: RefObject<HTMLDivElement | null>): void {
useEffect(() => {
if (ref.current && !disabled) {
setCursorPositionAtTheEnd(ref.current);

View File

@@ -9,13 +9,13 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { type EmptyObject } from "matrix-js-sdk/src/matrix";
import { Root, InlineField, Label, ToggleInput } from "@vector-im/compound-web";
import { _t } from "../../../languageHandler";
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
import { type IntegrationManagerInstance } from "../../../integrations/IntegrationManagerInstance";
import SettingsStore from "../../../settings/SettingsStore";
import { SettingLevel } from "../../../settings/SettingLevel";
import ToggleSwitch from "../elements/ToggleSwitch";
import Heading from "../typography/Heading";
import { SettingsSubsectionText } from "./shared/SettingsSubsection";
import { UIFeature } from "../../../settings/UIFeature";
@@ -66,26 +66,33 @@ export default class SetIntegrationManager extends React.Component<EmptyObject,
if (!SettingsStore.getValue(UIFeature.Widgets)) return null;
return (
<label
className="mx_SetIntegrationManager"
data-testid="mx_SetIntegrationManager"
htmlFor="toggle_integration"
>
<div className="mx_SetIntegrationManager" data-testid="mx_SetIntegrationManager">
<div className="mx_SettingsFlag">
<div className="mx_SetIntegrationManager_heading_manager">
<Heading size="3">{_t("integration_manager|manage_title")}</Heading>
<Heading size="4">{managerName}</Heading>
</div>
<ToggleSwitch
id="toggle_integration"
checked={this.state.provisioningEnabled}
disabled={false}
onChange={this.onProvisioningToggled}
/>
</div>
<SettingsSubsectionText>{bodyText}</SettingsSubsectionText>
<SettingsSubsectionText>{_t("integration_manager|explainer")}</SettingsSubsectionText>
</label>
<Root>
<InlineField
name="enable_im"
control={
<ToggleInput
role="switch"
id="mx_SetIntegrationManager_Toggle"
checked={this.state.provisioningEnabled}
onChange={this.onProvisioningToggled}
/>
}
>
<Label htmlFor="mx_SetIntegrationManager_Toggle">
{_t("integration_manager|toggle_label")}
</Label>
</InlineField>
</Root>
</div>
);
}
}

View File

@@ -5,11 +5,11 @@
* Please see LICENSE files in the repository root for full details.
*/
import { Breadcrumb, Button, VisualList, VisualListItem } from "@vector-im/compound-web";
import { Breadcrumb, Button, InlineSpinner, VisualList, VisualListItem } from "@vector-im/compound-web";
import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
import InfoIcon from "@vector-im/compound-design-tokens/assets/web/icons/info";
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
import React, { type MouseEventHandler } from "react";
import React, { useState, type MouseEventHandler } from "react";
import { _t } from "../../../../languageHandler";
import { EncryptionCard } from "./EncryptionCard";
@@ -44,6 +44,10 @@ interface ResetIdentityPanelProps {
export function ResetIdentityPanel({ onCancelClick, onFinish, variant }: ResetIdentityPanelProps): JSX.Element {
const matrixClient = useMatrixClientContext();
// After the user clicks "Continue", we disable the button so it can't be
// clicked again, and warn the user not to close the window.
const [inProgress, setInProgress] = useState(false);
return (
<>
<Breadcrumb
@@ -78,18 +82,34 @@ export function ResetIdentityPanel({ onCancelClick, onFinish, variant }: ResetId
<EncryptionCardButtons>
<Button
destructive={true}
disabled={inProgress}
onClick={async (evt) => {
setInProgress(true);
await matrixClient
.getCrypto()
?.resetEncryption((makeRequest) => uiAuthCallback(matrixClient, makeRequest));
onFinish(evt);
}}
>
{_t("action|continue")}
</Button>
<Button kind="tertiary" onClick={onCancelClick}>
{_t("action|cancel")}
{inProgress ? (
<>
<InlineSpinner /> {_t("settings|encryption|advanced|reset_in_progress")}
</>
) : (
_t("action|continue")
)}
</Button>
{inProgress ? (
<EncryptionCardEmphasisedContent>
<span className="mx_ResetIdentityPanel_warning">
{_t("settings|encryption|advanced|do_not_close_warning")}
</span>
</EncryptionCardEmphasisedContent>
) : (
<Button kind="tertiary" onClick={onCancelClick}>
{_t("action|cancel")}
</Button>
)}
</EncryptionCardButtons>
</EncryptionCard>
</>

View File

@@ -51,6 +51,8 @@ const QuickSettingsButton: React.FC<{
wrapperClassName={classNames("mx_QuickSettingsButton_ContextMenuWrapper", {
mx_QuickSettingsButton_ContextMenuWrapper_new_room_list: newRoomListEnabled,
})}
// Eventually replace with a properly aria-labelled menu
data-testid="quick-settings-menu"
onFinished={closeMenu}
managed={false}
focusLock={true}

View File

@@ -165,12 +165,12 @@ export default class VerificationShowSas extends React.Component<IProps, IState>
} else {
confirm = (
<div className="mx_VerificationShowSas_buttonRow">
<AccessibleButton onClick={this.onDontMatchClick} kind="danger">
{_t("encryption|verification|sas_no_match")}
</AccessibleButton>
<AccessibleButton onClick={this.onMatchClick} kind="primary">
{_t("encryption|verification|sas_match")}
</AccessibleButton>
<AccessibleButton onClick={this.onDontMatchClick} kind="secondary">
{_t("encryption|verification|sas_no_match")}
</AccessibleButton>
</div>
);
}

View File

@@ -9,12 +9,13 @@ Please see LICENSE files in the repository root for full details.
import React, { type FC, useContext, useEffect, type AriaRole, useCallback } from "react";
import type { Room } from "matrix-js-sdk/src/matrix";
import { type Call, ConnectionState, ElementCall } from "../../../models/Call";
import { useCall } from "../../../hooks/useCall";
import { type Call, CallEvent } from "../../../models/Call";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import AppTile from "../elements/AppTile";
import { CallStore } from "../../../stores/CallStore";
import { SdkContextClass } from "../../../contexts/SDKContext";
import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
import { useCall } from "../../../hooks/useCall";
interface JoinCallViewProps {
room: Room;
@@ -22,10 +23,12 @@ interface JoinCallViewProps {
call: Call;
skipLobby?: boolean;
role?: AriaRole;
onClose: () => void;
}
const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby, role }) => {
const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby, role, onClose }) => {
const cli = useContext(MatrixClientContext);
useTypedEventEmitter(call, CallEvent.Close, onClose);
useEffect(() => {
// We'll take this opportunity to tidy up our room state
@@ -38,17 +41,6 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
call.widget.data.skipLobby = skipLobby;
}, [call.widget, skipLobby]);
useEffect(() => {
if (call.connectionState === ConnectionState.Disconnected) {
// immediately start the call
// (this will start the lobby view in the widget and connect to all required widget events)
call.start();
}
return (): void => {
// If we are connected the widget is sticky and we do not want to destroy the call.
if (!call.connected) call.destroy();
};
}, [call]);
const disconnectAllOtherCalls: () => Promise<void> = useCallback(async () => {
// The stickyPromise has to resolve before the widget actually becomes sticky.
// We only let the widget become sticky after disconnecting all other active calls.
@@ -57,6 +49,7 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
);
await Promise.all(calls.map(async (call) => await call.disconnect()));
}, []);
return (
<div className="mx_CallView" role={role}>
<AppTile
@@ -76,26 +69,27 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
interface CallViewProps {
room: Room;
resizing: boolean;
/**
* If true, the view will be blank until a call appears. Otherwise, the join
* button will create a call if there isn't already one.
*/
waitForCall: boolean;
skipLobby?: boolean;
role?: AriaRole;
/**
* Callback for when the user closes the call.
*/
onClose: () => void;
}
export const CallView: FC<CallViewProps> = ({ room, resizing, waitForCall, skipLobby, role }) => {
export const CallView: FC<CallViewProps> = ({ room, resizing, skipLobby, role, onClose }) => {
const call = useCall(room.roomId);
useEffect(() => {
if (call === null && !waitForCall) {
ElementCall.create(room, skipLobby);
}
}, [call, room, skipLobby, waitForCall]);
if (call === null) {
return null;
} else {
return <JoinCallView room={room} resizing={resizing} call={call} skipLobby={skipLobby} role={role} />;
}
return (
call && (
<JoinCallView
room={room}
resizing={resizing}
call={call}
skipLobby={skipLobby}
role={role}
onClose={onClose}
/>
)
);
};

View File

@@ -70,7 +70,6 @@ const RoomContext = createContext<
threadId: undefined,
liveTimeline: undefined,
narrow: false,
activeCall: null,
msc3946ProcessDynamicPredecessor: false,
canAskToJoin: false,
promptAskToJoin: false,

View File

@@ -343,7 +343,7 @@ export default async function createRoom(client: MatrixClient, opts: IOpts): Pro
await client.setPowerLevel(roomId, client.getUserId()!, 100);
} else if (opts.roomType === RoomType.UnstableCall) {
// Set up this video room with an Element call
await ElementCall.create(await room);
ElementCall.create(await room);
// Reset our power level back to admin so that the call becomes immutable
await client.setPowerLevel(roomId, client.getUserId()!, 100);

View File

@@ -75,9 +75,5 @@ export const useFull = (call: Call | null): boolean => {
export const useJoinCallButtonDisabledTooltip = (call: Call | null): string | null => {
const isFull = useFull(call);
const state = useConnectionState(call);
if (state === ConnectionState.Connecting) return _t("voip|join_button_tooltip_connecting");
if (isFull) return _t("voip|join_button_tooltip_call_full");
return null;
return isFull ? _t("voip|join_button_tooltip_call_full") : null;
};

View File

@@ -80,12 +80,14 @@
"maximise": "Maximalizovat",
"mention": "Zmínit",
"minimise": "Minimalizovat",
"new_message": "Nová zpráva",
"new_room": "Nová místnost",
"new_video_room": "Nová video místnost",
"next": "Další",
"no": "Ne",
"ok": "OK",
"open": "Otevřít",
"open_menu": "Otevřít nabídku",
"pause": "Pozastavit",
"pin": "Připnout",
"play": "Přehrát",
@@ -405,7 +407,15 @@
"download_logs": "Stáhnout záznamy",
"downloading_logs": "Stahování záznamů",
"error_empty": "Dejte nám vědět, prosím, co se pokazilo nebo vytvořte issue na GitHubu, kde problém popište.",
"failed_send_logs": "Nepodařilo se odeslat záznamy: ",
"failed_download_logs": "Stažení ladících protokolů se nezdařilo: ",
"failed_send_logs_causes": {
"disallowed_app": "Vaše hlášení o chybě bylo zamítnuto. Rageshake server tuto aplikaci nepodporuje.",
"rejected_generic": "Vaše hlášení o chybě bylo zamítnuto. Rageshake server odmítl obsah zprávy kvůli zásadám.",
"rejected_recovery_key": "Vaše hlášení o chybě bylo z bezpečnostních důvodů odmítnuto, protože obsahovalo klíč pro obnovení.",
"rejected_version": "Vaše hlášení o chybě bylo zamítnuto, protože verze, kterou používáte, je příliš stará.",
"server_unknown_error": "Rageshake server narazil na neznámou chybu a nemohl zpracovat zprávu.",
"unknown_error": "Odeslání protokolů se nezdařilo."
},
"github_issue": "issue na GitHubu",
"introduction": "Pokud jste odeslali chybu prostřednictvím GitHubu, ladící protokoly nám mohou pomoci problém vysledovat. ",
"log_request": "Abychom tomu mohli pro příště předejít, <a>pošlete nám prosím záznamy</a>.",
@@ -495,7 +505,6 @@
"legal": "Právní informace",
"light": "Světlý",
"loading": "Načítání…",
"lobby": "Předsálí",
"location": "Poloha",
"low_priority": "Nízká priorita",
"matrix": "Matrix",
@@ -1244,6 +1253,7 @@
"change": "Změnit server identit",
"change_prompt": "Odpojit se ze serveru <current /> a připojit na <new />?",
"change_server_prompt": "Pokud nechcete na hledání existujících kontaktů používat server <server />, zvolte si jiný server.",
"changed": "Váš server identit byl změněn",
"checking": "Kontrolování serveru",
"description_connected": "Pro hledání existujících kontaktů používáte server identit <server></server>. Níže ho můžete změnit.",
"description_disconnected": "Pro hledání existujících kontaktů nepoužíváte žádný server identit <server></server>. Abyste mohli hledat kontakty, nějaký níže nastavte.",
@@ -1286,7 +1296,9 @@
"title": "Nepodporovaný prohlížeč",
"use_desktop_heading": "Místo toho použijte %(brand)s Desktop",
"use_mobile_heading": "Místo toho použijte %(brand)s na mobilu",
"use_mobile_heading_after_desktop": "Nebo použijte naši mobilní aplikaci"
"use_mobile_heading_after_desktop": "Nebo použijte naši mobilní aplikaci",
"windows_64bit": "Windows (64-bit)",
"windows_arm_64bit": "Windows (ARM 64-bit)"
},
"info_tooltip_title": "Informace",
"integration_manager": {
@@ -1295,6 +1307,7 @@
"error_connecting_heading": "Nepovedlo se připojení ke správci integrací",
"explainer": "Správci integrace přijímají konfigurační data a mohou vaším jménem upravovat widgety, odesílat pozvánky do místností a nastavovat úrovně oprávnění.",
"manage_title": "Správa integrací",
"toggle_label": "Povolit správce integrací",
"use_im": "Použít správce integrací na správu botů, widgetů a nálepek.",
"use_im_default": "Použít správce integrací <b>(%(serverName)s)</b> na správu botů, widgetů a nálepek."
},
@@ -2097,11 +2110,16 @@
"one": "Momentálně se připojuje %(count)s místnost",
"other": "Momentálně se připojuje %(count)s místností"
},
"list_title": "Seznam místností",
"notification_options": "Možnosti oznámení",
"open_space_menu": "Otevřít nabídku prostoru",
"redacting_messages_status": {
"one": "Momentálně se odstraňují zprávy v %(count)s místnosti",
"other": "Momentálně se odstraňují zprávy v %(count)s místnostech"
},
"room": {
"open_room": "Otevřít místnost %(roomName)s"
},
"show_less": "Zobrazit méně",
"show_n_more": {
"other": "Zobrazit %(count)s dalších",
@@ -2112,6 +2130,10 @@
"sort_by_activity": "Aktivity",
"sort_by_alphabet": "AZ",
"sort_unread_first": "Zobrazovat místnosti s nepřečtenými zprávami jako první",
"space_menu": {
"home": "Domov prostoru",
"space_settings": "Nastavení prostoru"
},
"space_menu_label": "Nabídka pro %(spaceName)s",
"sublist_options": "Možnosti seznamu",
"suggested_rooms_heading": "Doporučené místnosti"
@@ -2474,12 +2496,14 @@
"breadcrumb_title_forgot": "Zapomněli jste klíč pro obnovení? Budete muset obnovit svou identitu.",
"breadcrumb_warning": "Udělejte to pouze v případě, že se domníváte, že váš účet byl napaden.",
"details_title": "Podrobnosti o šifrování",
"do_not_close_warning": "Nezavírejte toto okno, dokud není resetování dokončeno",
"export_keys": "Exportovat klíče",
"import_keys": "Importovat klíče",
"other_people_device_description": "Ve výchozím nastavení v šifrovaných místnostech nikomu neposílat šifrované zprávy, dokud je neověříte",
"other_people_device_label": "Nikdy neposílejte šifrované zprávy na neověřená zařízení",
"other_people_device_title": "Zařízení ostatních uživatelů",
"reset_identity": "Obnovit kryptografickou identitu",
"reset_in_progress": "Probíhá resetování...",
"session_id": "ID relace:",
"session_key": "Klíč relace:",
"title": "Rozšířené"
@@ -3902,7 +3926,6 @@
"input_devices": "Vstupní zařízení",
"jitsi_call": "Jitsi konference",
"join_button_tooltip_call_full": "Omlouváme se — tento hovor je v současné době plný",
"join_button_tooltip_connecting": "Spojování",
"legacy_call": "Zastaralý způsob hovoru",
"maximise": "Vyplnit obrazovku",
"maximise_call": "Maximalizovat hovor",

View File

@@ -72,12 +72,14 @@
"maximise": "Mwyhau",
"mention": "Crybwyll",
"minimise": "Lleihau",
"new_message": "Neges newydd",
"new_room": "Ystafell newydd",
"new_video_room": "Ystafell fideo newydd",
"next": "Nesaf",
"no": "Na",
"ok": "Iawn",
"open": "Agor",
"open_menu": "Agor dewislen",
"pause": "Oedi",
"pin": "Pinio",
"play": "Chwarae",
@@ -322,11 +324,11 @@
"server_picker_title_default": "Opsiynau Gweinydd",
"server_picker_title_registration": "Cyfrif gwesteiwr ymlaen",
"session_logged_out_description": "Er diogelwch, mae'r sesiwn hon wedi'i hallgofnodi. Mewngofnodwch eto.",
"session_logged_out_title": "Wedi Arwyddo Allan",
"session_logged_out_title": "Wedi Allgofnodi",
"set_email": {
"description": "Bydd hyn yn caniatáu ichi ailosod eich cyfrinair a derbyn hysbysiadau.",
"description": "Bydd hyn yn caniatáu i chi ailosod eich cyfrinair a derbyn hysbysiadau.",
"verification_pending_description": "Gwiriwch eich e-bost a chliciwch ar y ddolen sydd ynddo. Unwaith y gwneir hyn, cliciwch parhau.",
"verification_pending_title": "Dilysu yn yr Arfaeth"
"verification_pending_title": "Yn Aros i Wirio"
},
"set_email_prompt": "Ydych chi am osod cyfeiriad e-bost?",
"sign_in_description": "Defnyddiwch eich cyfrif i barhau.",
@@ -335,7 +337,7 @@
"sign_in_or_register": "Mewngofnodi neu Creu Cyfrif",
"sign_in_or_register_description": "Defnyddiwch eich cyfrif neu crëwch un newydd i barhau.",
"sign_in_prompt": "Oes gennych chi gyfrif? <a>Mewngofnodwch</a>",
"sign_in_with_sso": "Mewngofnodwch gyda mewngofnodi sengl",
"sign_in_with_sso": "Mewngofnodwch gyda mewngofnod sengl",
"signing_in": "Wrthi'n mewngofnodi…",
"soft_logout": {
"clear_data_button": "Clirio'r holl ddata",
@@ -348,9 +350,9 @@
"soft_logout_intro_unsupported_auth": "Ni allwch fewngofnodi i'ch cyfrif. Cysylltwch â gweinyddwr eich gweinydd cartref am ragor o wybodaeth.",
"soft_logout_subheading": "Clirio data personol",
"soft_logout_warning": "Rhybudd: mae eich data personol (gan gynnwys allweddi amgryptio) yn dal i gael ei storio yn y sesiwn hon. Cliriwch ef os ydych wedi gorffen defnyddio'r sesiwn hon, neu eisiau mewngofnodi i gyfrif arall.",
"sso": "Arwyddo Sengl",
"sso": "Mewngofnod Sengl",
"sso_complete_in_browser_dialog_title": "Ewch i'ch porwr i gwblhau Mewngofnodi",
"sso_failed_missing_storage": "Gofynnom i'r porwr gofio pa weinydd cartref rydych chi'n ei ddefnyddio i'ch galluogi i fewngofnodi, ond yn anffodus mae eich porwr wedi anghofio hynny. Ewch i'r dudalen mewngofnodi a cheisiwch eto.",
"sso_failed_missing_storage": "Rydym wedi gofyn i'r porwr gofio pa weinydd cartref rydych chi'n ei ddefnyddio i'ch galluogi i fewngofnodi, ond yn anffodus mae eich porwr wedi anghofio hynny. Ewch i'r dudalen mewngofnodi a cheisiwch eto.",
"sso_or_username_password": "%(ssoButtons)s Neu %(usernamePassword)s",
"sync_footer_subtitle": "Os ydych chi wedi ymuno â llawer o ystafelloedd, gallai hyn gymryd peth amser",
"syncing": "Cydweddu…",
@@ -367,17 +369,17 @@
"msisdn_token_incorrect": "Tocyn yn anghywir",
"msisdn_token_prompt": "Rhowch y cod sydd ynddo:",
"password_prompt": "Cadarnhewch eich hunaniaeth trwy nodi cyfrinair eich cyfrif isod.",
"recaptcha_missing_params": "Allwedd gyhoeddus captcha ar goll yng nghyfluniad y gweinydd cartref. Rhowch wybod i'ch gweinyddwr gweinyddwr am hyn.",
"recaptcha_missing_params": "Mae allwedd gyhoeddus captcha ar goll yn ffurfweddiad y gweinydd cartref. Rhowch wybod i'ch gweinyddwr gweinyddwr am hyn.",
"registration_token_label": "Tocyn cofrestru",
"registration_token_prompt": "Rhowch docyn cofrestru a ddarparwyd gan weinyddwr y gweinydd cartref.",
"sso_body": "Cadarnhewch ychwanegu'r cyfeiriad e-bost hwn trwy ddefnyddio Single Sign On i brofi pwy ydych.",
"sso_failed": "Aeth rhywbeth o'i le wrth gadarnhau pwy ydych chi. Canslo a cheisio eto.",
"sso_failed": "Aeth rhywbeth o'i le wrth gadarnhau pwy ydych chi. Diddymu a cheisio eto.",
"sso_postauth_body": "Cliciwch ar y botwm isod i gadarnhau pwy ydych chi.",
"sso_postauth_title": "Cadarnhau i barhau",
"sso_preauth_body": "I barhau, defnyddiwch Arwyddo Sengl i brofi pwy ydych.",
"sso_title": "Defnyddiwch Arwyddo Sengl i barhau",
"terms": "Adolygwch a derbyniwch bolisïau'r gweinydd cartref hwn:",
"terms_invalid": "Adolygwch a derbyniwch holl bolisïau'r gweinydd cartref"
"sso_preauth_body": "I barhau, defnyddiwch Mewngofnod Sengl i brofi pwy ydych.",
"sso_title": "Defnyddiwch Mewngofnod Sengl i barhau",
"terms": "Darllenwch a derbyniwch bolisïau'r gweinydd cartref hwn:",
"terms_invalid": "Darllenwch a derbyniwch bolisïau'r gweinydd cartref hwn"
},
"unsupported_auth": "Nid yw'r gweinydd cartref hwn yn cynnig unrhyw lifau mewngofnodi a gefnogir gan y cleient hwn.",
"unsupported_auth_email": "Nid yw'r gweinydd cartref hwn yn cefnogi mewngofnodi gan ddefnyddio cyfeiriad e-bost.",
@@ -389,15 +391,14 @@
},
"bug_reporting": {
"additional_context": "Os oes cyd-destun ychwanegol a fyddai'n helpu i ddadansoddi'r mater, megis yr hyn yr oeddech yn ei wneud ar y pryd, IDau ystafelloedd, IDau defnyddiwr, ac ati, cynhwyswch y pethau hynny yma.",
"before_submitting": "Rydym yn argymell <a>creu rhifyn GitHub</a> i sicrhau bod eich adroddiad yn cael ei adolygu.",
"before_submitting": "Rydym yn argymell <a>creu mater GitHub</a> i sicrhau bod eich adroddiad yn cael ei ddarllen.",
"collecting_information": "Casglu gwybodaeth fersiwn ap",
"collecting_logs": "Casglu boncyffion",
"create_new_issue": "Os gwelwch yn dda<newIssueLink> creu rhifyn newydd</newIssueLink> ar GitHub fel y gallwn ymchwilio i'r byg hwn.",
"description": "Mae logiau dadfygio yn cynnwys data defnydd cymhwysiad gan gynnwys eich enw defnyddiwr, IDau neu arallenwau'r ystafelloedd yr ydych wedi ymweld â nhw, pa elfennau UI y gwnaethoch ryngweithio â nhw ddiwethaf, ac enwau defnyddwyr defnyddwyr eraill. Nid ydynt yn cynnwys negeseuon.",
"download_logs": "Lawrlwythwch logiau",
"downloading_logs": "Wrthi'n llwytho i lawr logiau",
"collecting_logs": "Casglu cofnodion",
"create_new_issue": "Os gwelwch yn dda<newIssueLink> crewch fater newydd</newIssueLink> ar GitHub fel y gallwn ymchwilio i'r byg hwn.",
"description": "Mae cofnodion dadfygio yn cynnwys data defnydd cymhwysiad gan gynnwys eich enw defnyddiwr, IDau neu arallenwau'r ystafelloedd yr ydych wedi ymweld â nhw, pa elfennau UI y gwnaethoch ryngweithio â nhw ddiwethaf, ac enwau defnyddwyr defnyddwyr eraill. Nid ydynt yn cynnwys negeseuon.",
"download_logs": "Llwytho logiau i lawr",
"downloading_logs": "Wrthi'n llwytho logiau i lawr",
"error_empty": "Dywedwch wrthym beth aeth o'i le neu, yn well, crëwch fater GitHub sy'n disgrifio'r broblem.",
"failed_send_logs": "Wedi methu ag anfon logiau: ",
"github_issue": "Mater GitHub",
"introduction": "Os ydych chi wedi cyflwyno byg trwy GitHub, gall logiau dadfygio ein helpu i ddod o hyd i'r broblem. ",
"log_request": "Er mwyn ein helpu i atal hyn yn y dyfodol, <a>anfonwch logiau atom</a> .",
@@ -483,7 +484,6 @@
"legal": "Cyfreithiol",
"light": "Golau",
"loading": "Llwytho…",
"lobby": "Cyntedd",
"location": "Lleoliad",
"low_priority": "Blaenoriaeth isel",
"matrix": "Matrics",
@@ -507,7 +507,7 @@
"people": "Pobl",
"preferences": "Dewisiadau",
"presence": "Presenoldeb",
"preview_message": "Hei chi. Ti yw'r gorau!",
"preview_message": "Hei ti. Ti yw'r gorau!",
"privacy": "Preifatrwydd",
"private": "Preifat",
"private_room": "Ystafell breifat",
@@ -531,7 +531,7 @@
"select_all": "Dewis y cyfan",
"server": "Gweinydd",
"settings": "Gosodiadau",
"setup_secure_messages": "Sefydlu Negeseuon Diogel",
"setup_secure_messages": "Gosod Negeseuon Diogel",
"show_more": "Dangos mwy",
"someone": "Rhywun",
"space": "Bwlch",
@@ -543,12 +543,12 @@
"support": "Cymorth",
"system_alerts": "Rhybuddion System",
"theme": "Thema",
"thread": "Edau",
"thread": "Edefyn",
"threads": "Edau",
"timeline": "Llinell Amser",
"unavailable": "ddim ar gael",
"unencrypted": "Heb ei amgryptio",
"unmute": "Dad-ddistewi",
"unmute": "Dad-dewi",
"unnamed_room": "Ystafell Ddienw",
"unnamed_space": "Gofod Dienw",
"unverified": "Heb ei wirio",
@@ -556,7 +556,7 @@
"user": "Defnyddwyr",
"user_avatar": "Llun proffil",
"username": "Enw defnyddiwr",
"verification_cancelled": "Dilysiad wedi'i ganslo",
"verification_cancelled": "Dilysiad wedi'i ddiddymu",
"verified": "Gwiriwyd",
"version": "Fersiwn",
"video": "Fideo",
@@ -567,22 +567,22 @@
"composer": {
"autocomplete": {
"@room_description": "Rhowch wybod i'r ystafell gyfan",
"command_a11y": "Command Autocomplete",
"command_a11y": "Awtogwblhau Gorchymyn",
"command_description": "Gorchmynion",
"emoji_a11y": "Autocomplete Emoji",
"notification_a11y": "Hysbysiad yn Awtolenwi",
"emoji_a11y": "Awtogwblhau Emoji",
"notification_a11y": "Awtogwblhau Hysbysiad",
"notification_description": "Hysbysiad Ystafell",
"room_a11y": "Ystafell Awtogwblhau",
"space_a11y": "Gofod Autocomplete",
"user_a11y": "Defnyddiwr yn Awtolenwi",
"room_a11y": "Awtogwblhau Ystafell",
"space_a11y": "Awtogwblhau Gofod",
"user_a11y": "Awtogwblhau Defnyddiwr",
"user_description": "Defnyddwyr"
},
"close_sticker_picker": "Cuddio sticeri",
"edit_composer_label": "Golygu neges",
"format_bold": "Trwm",
"format_code_block": "Bloc cod",
"format_decrease_indent": "Gostyngiad mewnoliad",
"format_increase_indent": "Cynnydd mewnoliad",
"format_decrease_indent": "Lleihau mewnoliad",
"format_increase_indent": "Cynnyddu mewnoliad",
"format_inline_code": "Cod",
"format_insert_link": "Mewnosod dolen",
"format_italic": "Italig",
@@ -604,7 +604,7 @@
"no_perms_notice": "Nid oes gennych ganiatâd i bostio i'r ystafell hon",
"placeholder": "Anfon neges…",
"placeholder_encrypted": "Anfon neges wedi'i hamgryptio…",
"placeholder_reply": "Anfonwch ateb…",
"placeholder_reply": "Anfon ateb…",
"placeholder_reply_encrypted": "Anfon ateb wedi'i amgryptio…",
"placeholder_thread": "Ateb i edefyn…",
"placeholder_thread_encrypted": "Ymateb i edefyn wedi'i amgryptio…",
@@ -620,17 +620,17 @@
"stop_voice_message": "Stopio recordio",
"voice_message_button": "Neges Llais"
},
"console_dev_note": "Os ydych chi'n gwybod beth rydych chi'n ei wneud, mae Element yn ffynhonnell agored, gwnewch yn siŵr eich bod chi'n edrych ar ein GitHub ( https://github.com/vector-im/element-web/ ) a chyfrannu!",
"console_dev_note": "Os ydych chi'n gwybod beth rydych chi'n ei wneud, mae Element yn raglen cod agored, gwnewch yn siŵr eich bod chi'n edrych ar ein GitHub ( https://github.com/vector-im/element-web/ ) a chyfrannu!",
"console_scam_warning": "Os dywedodd rhywun wrthych am gopïo/gludo rhywbeth yma, mae'n debygol iawn eich bod yn cael eich twyllo!",
"console_wait": "Arhoswch!",
"create_room": {
"action_create_room": "Creu ystafell",
"action_create_video_room": "Creu ystafell fideo",
"encrypted_video_room_warning": "Ni allwch analluogi hyn yn nes ymlaen. Bydd yr ystafell yn cael ei hamgryptio ond ni fydd yr alwad wedi'i mewnosod.",
"encrypted_warning": "Ni allwch analluogi hyn yn nes ymlaen. Ni fydd pontydd a'r mwyafrif o bots yn gweithio eto.",
"encrypted_video_room_warning": "Allwch chi ddim analluogi hyn yn nes ymlaen. Bydd yr ystafell yn cael ei hamgryptio ond ni fydd yr alwad wedi'i mewnosod.",
"encrypted_warning": "Allwch chi ddim analluogi hyn yn nes ymlaen. Ni fydd pontydd a'r mwyafrif o fotiau'n gweithio eto.",
"encryption_forced": "Mae angen amgryptio ar eich gweinydd er mwyn ei alluogi mewn ystafelloedd preifat.",
"encryption_label": "Galluogi amgryptio o'r dechrau i'r diwedd",
"error_title": "Methiant i greu ystafell",
"error_title": "Methu creu ystafell",
"generic_error": "Mae'n bosibl nad yw'r gweinydd ar gael, wedi'i orlwytho, neu eich bod yn taro byg.",
"join_rule_change_notice": "Gallwch newid hwn unrhyw bryd o osodiadau ystafell.",
"join_rule_invite": "Ystafell breifat (gwahoddiad yn unig)",
@@ -647,7 +647,7 @@
"title_video_room": "Creu ystafell fideo",
"topic_label": "Pwnc (dewisol)",
"unfederated": "Rhwystro unrhyw un nad yw'n rhan o %(serverName)s rhag ymuno â'r ystafell hon byth.",
"unfederated_label_default_off": "Efallai y byddwch yn galluogi hyn os mai dim ond ar gyfer cydweithio â thimau mewnol ar eich gweinydd cartref y bydd yr ystafell yn cael ei defnyddio. Ni ellir newid hyn yn ddiweddarach.",
"unfederated_label_default_off": "Efallai y byddwch yn galluogi hyn os mai dim ond ar gyfer cydweithio â thimau mewnol ar eich gweinydd cartref y bydd yr ystafell yn cael ei defnyddio. Nid oes modd newid hyn yn ddiweddarach.",
"unfederated_label_default_on": "Efallai y byddwch yn analluogi hyn os bydd yr ystafell yn cael ei defnyddio ar gyfer cydweithio â thimau allanol sydd â'u gweinydd cartref eu hunain. Ni ellir newid hyn yn ddiweddarach.",
"unsupported_version": "Nid yw'r gweinydd yn cefnogi'r fersiwn ystafell a nodwyd."
},
@@ -662,7 +662,7 @@
"creating_rooms": "Wrthi'n creu ystafelloedd…",
"done_action": "Ewch i'm gofod",
"done_action_first_room": "Ewch i fy ystafell gyntaf",
"explainer": "Mae lleoedd yn ffordd newydd o grwpio ystafelloedd a phobl. Pa fath o Ofod ydych chi am ei greu? Gallwch newid hyn yn nes ymlaen.",
"explainer": "Mae gofodau yn ffordd newydd o grwpio ystafelloedd a phobl. Pa fath o Ofod ydych chi am ei greu? Gallwch newid hyn yn nes ymlaen.",
"failed_create_initial_rooms": "Wedi methu â chreu ystafelloedd gofod cychwynnol",
"failed_invite_users": "Wedi methu â gwahodd y defnyddwyr canlynol i'ch gofod: %(csvUsers)s",
"invite_teammates_by_username": "Gwahoddiad yn ôl enw defnyddiwr",
@@ -680,13 +680,13 @@
"private_space": "Fi a fy nghyd-chwaraewyr",
"private_space_description": "Man preifat i chi a'ch cyd-chwaraewyr",
"public_description": "Mannau agored i unrhyw un, gorau i gymunedau",
"public_heading": "Eich man cyhoeddus",
"search_public_button": "Chwilio am fannau cyhoeddus",
"setup_rooms_community_description": "Gadewch i ni greu ystafell ar gyfer pob un ohonynt.",
"public_heading": "Eich gofod cyhoeddus",
"search_public_button": "Chwilio am gofodau cyhoeddus",
"setup_rooms_community_description": "Gadewch i ni greu ystafell ar gyfer pob un ohonyn nhw.",
"setup_rooms_community_heading": "Beth yw rhai pethau rydych am eu trafod yn %(spaceName)s?",
"setup_rooms_description": "Gallwch ychwanegu mwy yn nes ymlaen hefyd, gan gynnwys y rhai sydd eisoes yn bodoli.",
"setup_rooms_private_description": "Byddwn yn creu ystafelloedd ar gyfer pob un ohonynt.",
"setup_rooms_private_heading": "Pa brosiectau y mae eich tîm yn gweithio arnynt?",
"setup_rooms_private_description": "Byddwn yn creu ystafelloedd ar gyfer pob un ohonyn nhw.",
"setup_rooms_private_heading": "Pa brosiectau y mae eich tîm yn gweithio arnyn nhw?",
"share_description": "Dim ond chi yw e ar hyn o bryd, bydd hyd yn oed yn well gydag eraill.",
"share_heading": "Rhannu %(name)s",
"skip_action": "Hepgor am nawr",
@@ -1199,6 +1199,7 @@
"change": "Newid gweinydd hunaniaeth",
"change_prompt": "Datgysylltwch o'r gweinydd hunaniaeth<current /> a chysylltu â<new /> yn lle hynny?",
"change_server_prompt": "Os nad ydych am ddefnyddio<server /> i ddarganfod a bod yn ddarganfyddadwy gan gysylltiadau presennol yr ydych yn gwybod, rhowch weinydd hunaniaeth arall isod.",
"changed": "Mae eich gweinydd hunaniaeth wedi'i newid",
"checking": "Gwirio gweinydd",
"description_connected": "Rydych chi'n defnyddio ar hyn o bryd<server></server> i ddarganfod a bod yn ddarganfyddadwy gan gysylltiadau presennol rydych chi'n eu hadnabod. Gallwch newid eich gweinydd hunaniaeth isod.",
"description_disconnected": "Nid ydych yn defnyddio gweinydd adnabod ar hyn o bryd. I ddarganfod a chael eich darganfod gan gysylltiadau presennol rydych chi'n eu hadnabod, ychwanegwch un isod.",
@@ -1237,7 +1238,9 @@
"title": "Nid yw %(brand)s yn cefnogi'r porwr hwn",
"use_desktop_heading": "Defnyddiwch %(brand)s Penbwrdd yn lle hynny",
"use_mobile_heading": "Defnyddiwch %(brand)s ar ffôn symudol yn lle hynny",
"use_mobile_heading_after_desktop": "Neu defnyddiwch ein app symudol"
"use_mobile_heading_after_desktop": "Neu defnyddiwch ein app symudol",
"windows_64bit": "Windows (64-did)",
"windows_arm_64bit": "Windows (ARM 64-did)"
},
"info_tooltip_title": "Gwybodaeth",
"integration_manager": {
@@ -1977,12 +1980,17 @@
"home_menu_label": "Opsiynau cartref",
"join_public_room_label": "Ymunwch â'r ystafell gyhoeddus",
"notification_options": "Opsiynau hysbysu",
"open_space_menu": "Agor dewislen gofod",
"show_less": "Dangos llai",
"show_previews": "Dangos rhagolwg o negeseuon",
"sort_by": "Trefnu yn ôl",
"sort_by_activity": "Gweithgaredd",
"sort_by_alphabet": "A-Z",
"sort_unread_first": "Dangos ystafelloedd gyda negeseuon heb eu darllen yn gyntaf",
"space_menu": {
"home": "Cartref gofod",
"space_settings": "Gosodiadau Gofod"
},
"space_menu_label": "Dewislen %(spaceName)s",
"sublist_options": "Rhestrwch opsiynau",
"suggested_rooms_heading": "Ystafelloedd a Awgrymir"
@@ -3536,7 +3544,6 @@
"input_devices": "Dyfeisiau mewnbwn",
"jitsi_call": "Cynhadledd Jitsi",
"join_button_tooltip_call_full": "Mae'n ddrwg gennym - mae'r alwad hon yn llawn ar hyn o bryd",
"join_button_tooltip_connecting": "Yn cysylltu",
"legacy_call": "Galwad Etifeddiaeth",
"maximise": "Llanw'r Sgrin",
"maximise_call": "Mwyhau galwad",

View File

@@ -1,6 +1,6 @@
{
"a11y": {
"emoji_picker": "Emoji-Auswahl",
"emoji_picker": "Emoji-Picker",
"jump_first_invite": "Zur ersten Einladung springen.",
"message_composer": "Nachrichteneingabe-Feld",
"n_unread_messages": {
@@ -80,12 +80,14 @@
"maximise": "Maximieren",
"mention": "Erwähnen",
"minimise": "Minimieren",
"new_message": "Neue Nachricht",
"new_room": "Neuer Raum",
"new_video_room": "Neuer Videoraum",
"next": "Weiter",
"no": "Nein",
"ok": "Ok",
"open": "Öffnen",
"open_menu": "Menu öffnen",
"pause": "Pausieren",
"pin": "Anheften",
"play": "Abspielen",
@@ -405,7 +407,6 @@
"download_logs": "Protokolle herunterladen",
"downloading_logs": "Lade Protokolle herunter",
"error_empty": "Bitte teile uns mit, was schief lief - oder besser, beschreibe das Problem auf GitHub in einem \"Issue\".",
"failed_send_logs": "Senden von Protokolldateien fehlgeschlagen: ",
"github_issue": "GitHub-Problem",
"introduction": "Wenn du uns einen Bug auf GitHub gemeldet hast, können uns Debug-Logs helfen, das Problem zu finden. ",
"log_request": "Um uns zu helfen, dies in Zukunft zu vermeiden, <a>sende uns bitte die Protokolldateien</a>.",
@@ -495,7 +496,6 @@
"legal": "Rechtliches",
"light": "Hell",
"loading": "Lade …",
"lobby": "Lobby",
"location": "Standort",
"low_priority": "Niedrige Priorität",
"matrix": "Matrix",
@@ -1017,8 +1017,8 @@
"sas_description": "Vergleiche eine einmalige Reihe von Emojis, sofern du an keinem Gerät eine Kamera hast",
"sas_emoji_caption_self": "Bestätige, dass die folgenden Emoji auf beiden Geräten in der gleichen Reihenfolge angezeigt werden:",
"sas_emoji_caption_user": "Verifiziere diesen Nutzer, indem du bestätigst, dass folgende Emojis auf dessen Bildschirm erscheinen.",
"sas_match": "Sie passen zueinander",
"sas_no_match": "Sie passen nicht zueinander",
"sas_match": "Sie stimmen überein",
"sas_no_match": "Sie stimmen nicht überein",
"sas_prompt": "Vergleiche einzigartige Emojis",
"scan_qr": "Verifizierung durch Scannen eines QR-Codes",
"scan_qr_explainer": "Bitte %(displayName)s, deinen Code zu scannen:",
@@ -1243,6 +1243,7 @@
"change": "Identitäts-Server wechseln",
"change_prompt": "Vom Identitäts-Server <current /> trennen, und stattdessen mit <new /> verbinden?",
"change_server_prompt": "Wenn du <server /> nicht verwenden willst, um Kontakte zu finden und von anderen gefunden zu werden, trage unten einen anderen Identitäts-Server ein.",
"changed": "Ihr Identitätsserver wurde geändert",
"checking": "Überprüfe Server",
"description_connected": "Zurzeit verwendest du <server></server>, um Kontakte zu finden und von anderen gefunden zu werden. Du kannst deinen Identitäts-Server nachfolgend wechseln.",
"description_disconnected": "Zurzeit benutzt du keinen Identitäts-Server. Trage unten einen Server ein, um Kontakte zu finden und von anderen gefunden zu werden.",
@@ -1285,7 +1286,9 @@
"title": "Nicht unterstützter Browser",
"use_desktop_heading": "Verwende stattdessen %(brand)s Desktop",
"use_mobile_heading": "Stattdessen %(brand)s am Smartphone benutzen",
"use_mobile_heading_after_desktop": "Oder verwende die mobile App"
"use_mobile_heading_after_desktop": "Oder verwende die mobile App",
"windows_64bit": "Windows (64-Bit)",
"windows_arm_64bit": "Windows (ARM 64-Bit)"
},
"info_tooltip_title": "Information",
"integration_manager": {
@@ -2092,6 +2095,7 @@
"other": "Betrete %(count)s Räume"
},
"notification_options": "Benachrichtigungsoptionen",
"open_space_menu": "Menü „Raum öffnen“",
"redacting_messages_status": {
"one": "Entferne Nachrichten in %(count)s Raum",
"other": "Entferne Nachrichten in %(count)s Räumen"
@@ -2106,6 +2110,10 @@
"sort_by_activity": "Aktivität",
"sort_by_alphabet": "AZ",
"sort_unread_first": "Räume mit ungelesenen Nachrichten zuerst zeigen",
"space_menu": {
"home": "Space-Übersicht",
"space_settings": "Raumeinstellungen"
},
"space_menu_label": "%(spaceName)s-Menü",
"sublist_options": "Optionen anzeigen",
"suggested_rooms_heading": "Vorgeschlagene Räume"
@@ -2468,12 +2476,14 @@
"breadcrumb_title_forgot": "Haben Sie Ihren Wiederherstellungsschlüssel vergessen? Sie müssen Ihre Identität zurücksetzen.",
"breadcrumb_warning": "Tun Sie dies nur, wenn Sie glauben, dass Ihr Konto kompromittiert wurde.",
"details_title": "Angaben zur Verschlüsselung",
"do_not_close_warning": "Schließen Sie dieses Fenster nicht, bis der Reset abgeschlossen ist",
"export_keys": "Schlüssel exportieren",
"import_keys": "Schlüssel importieren",
"other_people_device_description": "Senden Sie in verschlüsselten Räumen standardmäßig keine verschlüsselten Nachrichten an Dritte, bis Sie diese verifiziert haben",
"other_people_device_label": "Senden Sie niemals verschlüsselte Nachrichten an nicht verifizierte Geräte",
"other_people_device_title": "Geräte anderer Personen",
"reset_identity": "Kryptografische Identität zurücksetzen",
"reset_in_progress": "Der Reset wird ausgeführt",
"session_id": "Sitzungs-ID:",
"session_key": "Sitzungsschlüssel:",
"title": "Advanced"
@@ -3896,7 +3906,6 @@
"input_devices": "Eingabegeräte",
"jitsi_call": "Jitsi-Konferenz",
"join_button_tooltip_call_full": "Entschuldigung — dieser Anruf ist aktuell besetzt",
"join_button_tooltip_connecting": "Verbinden",
"legacy_call": "Legacy-Anruf",
"maximise": "Bildschirm füllen",
"maximise_call": "Anruf maximieren",

View File

@@ -316,7 +316,6 @@
"download_logs": "Λήψη αρχείων καταγραφής",
"downloading_logs": "Λήψη αρχείων καταγραφής",
"error_empty": "Πείτε μας τι πήγε στραβά ή, καλύτερα, δημιουργήστε ένα ζήτημα στο GitHub που να περιγράφει το πρόβλημα.",
"failed_send_logs": "Αποτυχία αποστολής αρχείων καταγραφής: ",
"github_issue": "Ζήτημα GitHub",
"introduction": "Εάν έχετε υποβάλει ένα σφάλμα μέσω του GitHub, τα αρχεία καταγραφής εντοπισμού σφαλμάτων μπορούν να μας βοηθήσουν να εντοπίσουμε το πρόβλημα. ",
"log_request": "Για να μας βοηθήσετε να το αποτρέψουμε αυτό στο μέλλον, <a>στείλτε μας τα αρχεία καταγραφής</a>.",
@@ -3137,7 +3136,6 @@
"hangup": "Κλείσιμο",
"hide_sidebar_button": "Απόκρυψη πλαϊνής μπάρας",
"input_devices": "Συσκευές εισόδου",
"join_button_tooltip_connecting": "Συνδέεται",
"maximise": "Γέμισμα οθόνης",
"misconfigured_server": "Η κλήση απέτυχε λόγω της λανθασμένης διάρθρωσης του διακομιστή",
"misconfigured_server_description": "Παρακαλείστε να ρωτήσετε τον διαχειριστή του κεντρικού διακομιστή σας (<code>%(homeserverDomain)s</code>) να ρυθμίσουν έναν διακομιστή πρωτοκόλλου TURN ώστε οι κλήσεις να λειτουργούν απρόσκοπτα.",

View File

@@ -407,7 +407,15 @@
"download_logs": "Download logs",
"downloading_logs": "Downloading logs",
"error_empty": "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.",
"failed_send_logs": "Failed to send logs: ",
"failed_download_logs": "Failed to download debug logs: ",
"failed_send_logs_causes": {
"disallowed_app": "Your bug report was rejected. The rageshake server does not support this application.",
"rejected_generic": "Your bug report was rejected. The rageshake server rejected the contents of the report due to a policy.",
"rejected_recovery_key": "Your bug report was rejected for safety reasons, as it contained a recovery key.",
"rejected_version": "Your bug report was rejected as the version you are running is too old.",
"server_unknown_error": "The rageshake server encountered an unknown error and could not handle the report.",
"unknown_error": "Failed to send logs."
},
"github_issue": "GitHub issue",
"introduction": "If you've submitted a bug via GitHub, debug logs can help us track down the problem. ",
"log_request": "To help us prevent this in future, please <a>send us logs</a>.",
@@ -497,7 +505,6 @@
"legal": "Legal",
"light": "Light",
"loading": "Loading…",
"lobby": "Lobby",
"location": "Location",
"low_priority": "Low priority",
"matrix": "Matrix",
@@ -1299,6 +1306,7 @@
"error_connecting_heading": "Cannot connect to integration manager",
"explainer": "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.",
"manage_title": "Manage integrations",
"toggle_label": "Enable the integration manager",
"use_im": "Use an integration manager to manage bots, widgets, and sticker packs.",
"use_im_default": "Use an integration manager <b>(%(serverName)s)</b> to manage bots, widgets, and sticker packs."
},
@@ -2091,18 +2099,29 @@
"failed_add_tag": "Failed to add tag %(tagName)s to room",
"failed_remove_tag": "Failed to remove tag %(tagName)s from room",
"failed_set_dm_tag": "Failed to set direct message tag",
"filters": {
"favourite": "Favourites",
"people": "People",
"rooms": "Rooms",
"unread": "Unread"
},
"home_menu_label": "Home options",
"join_public_room_label": "Join public room",
"joining_rooms_status": {
"one": "Currently joining %(count)s room",
"other": "Currently joining %(count)s rooms"
},
"list_title": "Room list",
"notification_options": "Notification options",
"open_space_menu": "Open space menu",
"primary_filters": "Room list filters",
"redacting_messages_status": {
"one": "Currently removing messages in %(count)s room",
"other": "Currently removing messages in %(count)s rooms"
},
"room": {
"open_room": "Open room %(roomName)s"
},
"show_less": "Show less",
"show_n_more": {
"one": "Show %(count)s more",
@@ -2479,12 +2498,14 @@
"breadcrumb_title_forgot": "Forgot your recovery key? Youll need to reset your identity.",
"breadcrumb_warning": "Only do this if you believe your account has been compromised.",
"details_title": "Encryption details",
"do_not_close_warning": "Do not close this window until the reset is finished",
"export_keys": "Export keys",
"import_keys": "Import keys",
"other_people_device_description": "By default in encrypted rooms, do not send encrypted messages to anyone until youve verified them",
"other_people_device_label": "Never send encrypted messages to unverified devices",
"other_people_device_title": "Other peoples devices",
"reset_identity": "Reset cryptographic identity",
"reset_in_progress": "Reset in progress...",
"session_id": "Session ID:",
"session_key": "Session key:",
"title": "Advanced"
@@ -3907,7 +3928,6 @@
"input_devices": "Input devices",
"jitsi_call": "Jitsi Conference",
"join_button_tooltip_call_full": "Sorry — this call is currently full",
"join_button_tooltip_connecting": "Connecting",
"legacy_call": "Legacy Call",
"maximise": "Fill screen",
"maximise_call": "Maximise call",

View File

@@ -301,7 +301,6 @@
"download_logs": "Elŝuti protokolon",
"downloading_logs": "Elŝutante protokolon",
"error_empty": "Bonvolu diri al ni kio misokazis, aŭ pli bone raporti problemon per GitHub.",
"failed_send_logs": "Malsukcesis sendi protokolon: ",
"github_issue": "Problemo per GitHub",
"log_request": "Por malhelpi tion ose, bonvolu <a>sendi al ni protokolon</a>.",
"logs_sent": "Protokolo sendiĝis",
@@ -2620,7 +2619,6 @@
"expand": "Reveni al voko",
"hangup": "Fini vokon",
"hide_sidebar_button": "Kaŝi flankan breton",
"join_button_tooltip_connecting": "Konektante",
"misconfigured_server": "Voko malsukcesis pro misagordita servilo",
"misconfigured_server_description": "Bonvolu peti la administranton de via hejmservilo (<code>%(homeserverDomain)s</code>) agordi TURN-servilon, por ke vokoj funkciu dependeble.",
"more_button": "Pli",

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