Compare commits

...

54 Commits

Author SHA1 Message Date
Michael Telatynski
20be92ab7e Merge branch 'develop' of ssh://github.com/element-hq/element-web into t3chguy/css-masks-again
# Conflicts:
#	res/css/views/rooms/_EventTile.pcss
2025-12-16 14:47:08 +00:00
Michael Telatynski
1afa202ef5 Add tests
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 14:02:16 +00:00
Michael Telatynski
7e3a6d9c42 Switch to rendering svg icons rather than masking them (#31550)
* Switch to rendering svg icons rather than masking them in SpacePanel

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

* Fix badly rendered icon in JoinRuleDropdown

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

* Fix badly rendered icon in RoomPreviewCard

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

* Fix badly rendered icon in Space menus

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

* Fix badly rendered icon in ThreadPanel

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

* Update snapshots

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

* Update screenshot

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

* Remove unused icon underfill

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

* Update screenshot

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

* Add test

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

* Add missing snapshot

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 13:56:36 +00:00
Michael Telatynski
9d3cba1621 Add missing screenshots
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 13:38:18 +00:00
Michael Telatynski
e6d572c413 Merge branch 't3chguy/a11y-icons-dec15' of ssh://github.com/element-hq/element-web into t3chguy/css-masks-again 2025-12-16 13:31:30 +00:00
Michael Telatynski
ced4456c16 Merge branch 'develop' into t3chguy/a11y-icons-dec15 2025-12-16 13:31:06 +00:00
Michael Telatynski
654a9eeb3a Merge branch 'develop' of ssh://github.com/element-hq/element-web into t3chguy/css-masks-again
# Conflicts:
#	res/css/views/rooms/_EventTile.pcss
2025-12-16 13:03:13 +00:00
Michael Telatynski
dc0763f58a Fix overflow in base card size
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 13:02:38 +00:00
Michael Telatynski
19d8798dc0 Update tests
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 13:02:30 +00:00
Michael Telatynski
d9f6355693 Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:58:50 +00:00
Michael Telatynski
4613c44f47 Update tests
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:31:01 +00:00
Michael Telatynski
773662e018 Amend e2e normal icon from lock-solid to info (#31555)
* Amend e2e normal icon from lock-solid to info

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

* Update screenshot

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:28:01 +00:00
Michael Telatynski
0961e6f6e4 Switch to rendering svg icons rather than masking them in HiddenBody
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:17:12 +00:00
Michael Telatynski
5c2d89a0d7 Switch to rendering svg icons rather than masking them in AccessSecretStorageDialog
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:16:01 +00:00
Michael Telatynski
40120cc226 Merge branch 't3chguy/a11y-icons-dec15' of ssh://github.com/element-hq/element-web into t3chguy/css-masks-again
# Conflicts:
#	res/css/views/rooms/_EventTile.pcss
2025-12-16 12:12:59 +00:00
Michael Telatynski
8fef5c5ec6 Switch to rendering svg icons rather than masking them in DialPad
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:08:35 +00:00
Michael Telatynski
9864294575 Tidy up E2EIcon
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:07:23 +00:00
Michael Telatynski
d1a0875d7f Switch to rendering svg icons rather than masking them in E2ePadlock
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:06:46 +00:00
Michael Telatynski
523fba271f Switch to rendering svg icons rather than masking them in RedactedBody
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:06:27 +00:00
Michael Telatynski
99690f6314 Switch to rendering svg icons rather than masking them in BaseCard
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:06:14 +00:00
Michael Telatynski
58b75e0fe3 Switch to rendering svg icons rather than masking them in MFileBody
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:05:54 +00:00
Michael Telatynski
ed59a53734 Switch to rendering svg icons rather than masking them in ReactionsRow
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:05:46 +00:00
Michael Telatynski
b243c94093 Switch to rendering svg icons rather than masking them in EventTileBubble
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:04:11 +00:00
Michael Telatynski
7cd9ec4fbd Remove unused AccessibleButton kinds
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 12:01:54 +00:00
Michael Telatynski
9fa23d3c19 Add missing snapshot
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 10:45:15 +00:00
Michael Telatynski
05c64bd6bc Add test
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 10:34:04 +00:00
Michael Telatynski
040c348700 Make AccessibleButton contrast control compatible (#31308)
* Make AccessibleButton contrast control compatible

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

* Iterate

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-12-16 09:51:15 +00:00
Michael Telatynski
c4d8c3e85f Update screenshot
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 09:44:26 +00:00
Michael Telatynski
82390d6a23 Remove unused icon underfill
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-16 09:42:54 +00:00
Michael Telatynski
2c2b607d3c Update screenshot
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 18:03:24 +00:00
Michael Telatynski
60e574f37d Update snapshots
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 17:55:37 +00:00
Michael Telatynski
d2ba05a3d8 Fix badly rendered icon in ThreadPanel
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 17:54:27 +00:00
Michael Telatynski
5c28ed6738 Fix badly rendered icon in Space menus
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 17:40:47 +00:00
Michael Telatynski
1808a16ed3 Fix badly rendered icon in RoomPreviewCard
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 17:17:58 +00:00
Michael Telatynski
bff234749f Fix badly rendered icon in JoinRuleDropdown
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 17:09:53 +00:00
Michael Telatynski
0733bebc38 Switch to rendering svg icons rather than masking them in SpacePanel
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 17:09:38 +00:00
Michael Telatynski
9d9782f62b Switch to compound-design-tokens for platform icons (#31543)
* Switch to compound-design-tokens for platform icons

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

* Revert app-store badge usage

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

* Update snapshots

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 16:37:24 +00:00
Michael Telatynski
0cfaeaa3a7 Fix CSS specificity causing icon issues in e2e verification (#31542)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 15:46:03 +00:00
Michael Telatynski
4a3cf3e69d Switch to rendering svg icons rather than masking them (#31531)
* Switch to rendering svg icons rather than masking them in left panel

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

* Remove unused stylesheet

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

* Switch to rendering svg icons rather than masking them for ExternalLink

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

* Switch to rendering svg icons rather than masking them for TabbedView

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

* Switch to rendering svg icons rather than masking them for JoinRuleDropdown

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

* Switch to rendering svg icons rather than masking them in ManageRestrictedJoinRuleDialog

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

* Switch to rendering svg icons rather than masking them in LeaveSpaceDialog

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

* Switch to rendering svg icons rather than masking them in ReplyPreview

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

* Switch to rendering svg icons rather than masking them in SearchBox

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

* Switch to rendering svg icons rather than masking them in RoomStatusBar

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

* Fix advanced.svg

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

* Update snapshots

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

* Update screenshots

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

* Iterate

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

* Fix bad merge conflict resolution

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

* Switch to rendering svg icons rather than masking them in Toasts

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

* Switch to rendering svg icons rather than masking them in RoomInfoLine

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

* Switch to rendering svg icons rather than masking them in UploadBar

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

* Remove unused class

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

* Switch to rendering svg icons rather than masking them in ConfirmSpaceUserActionDialog

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

* Switch to rendering svg icons rather than masking them in FeedbackDialog

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

* Switch to rendering svg icons rather than masking them in KeyBackupFailedDialog

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

* Switch to rendering svg icons rather than masking them in CopyableText

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

* Switch to rendering svg icons rather than masking them in EventTile

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

* Switch to rendering svg icons rather than masking them in InviteReason

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

* Delint

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

* Update tests

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

* Add test

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

* Add test

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 12:00:35 +00:00
Skye Elliot
c7134e8532 Prevent history visible banner from displaying in threads. (#31535)
* fix: Prevent history visible banner from displaying in threads.

* tests: Verify banner is not visible in threads.
2025-12-15 11:43:00 +00:00
Michael Telatynski
1d3421417f Switch to rendering svg icons rather than css masking (#31517)
* Switch to rendering svg icons rather than masking them in left panel

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

* Remove unused stylesheet

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

* Switch to rendering svg icons rather than masking them for ExternalLink

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

* Switch to rendering svg icons rather than masking them for TabbedView

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

* Switch to rendering svg icons rather than masking them for JoinRuleDropdown

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

* Switch to rendering svg icons rather than masking them in ManageRestrictedJoinRuleDialog

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

* Switch to rendering svg icons rather than masking them in LeaveSpaceDialog

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

* Switch to rendering svg icons rather than masking them in ReplyPreview

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

* Switch to rendering svg icons rather than masking them in SearchBox

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

* Switch to rendering svg icons rather than masking them in RoomStatusBar

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

* Fix advanced.svg

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

* Update snapshots

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

* Update screenshots

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

* Iterate

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

* Fix bad merge conflict resolution

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-15 09:52:50 +00:00
ElementRobot
ef63661cb0 [create-pull-request] automated change (#31541)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-12-15 06:32:58 +00:00
ElementRobot
e29da89826 [create-pull-request] automated change (#31537)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-12-13 06:22:12 +00:00
David Baker
d2727754e3 Tidy up token refresh code (#31519)
* Tidy up token refresh code

This was a bit odd where the function to create a refresher sometimes
created a refresher and sometimes just returned null, including if the
init failed, in which case you would just end up with no token refresher.

Pairs with https://github.com/matrix-org/matrix-js-sdk/pull/5106 but
doesn't depend on either way.

* Remove deviceId property in favour of superclass one

* Fix tests

* Fix argument order in super call

redirect URI & device ID were swapped. It appears that gthe OIDS client
only actually sends the redirect URI when refreshing a token, so we will
have been sending a device ID for that when refreshing. I think this is safe
to fix since this is only when refreshing so it already would not have
matched what was passed at login time.

* Pass client ID into createOidcTokenRefresher
2025-12-12 18:23:50 +00:00
Robin
179cf0f8e1 Make the feedback icon be the right color in dark theme (#31527)
Our feedback.svg is not tintable. Rather than mess with it I think it makes sense to use the equivalent Compound icon.
2025-12-12 09:40:38 +00:00
ElementRobot
de74816dd8 [create-pull-request] automated change (#31528)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-12-12 06:31:41 +00:00
Michael Telatynski
7b024f956d Fix e2e icons in CompleteSecurity & SetupEncryptionBody (#31521)
* Fix e2e icons in CompleteSecurity & SetupEncryptionBody

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

* Tests

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

* Prevent screenshot clash between tests

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-11 16:37:49 +00:00
Michael Telatynski
362e34513d Stabilise flaky tests relying on bots (#31520)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-11 16:16:05 +00:00
Florian Duros
5b900ab6e2 Move room list search to shared components (#31502)
* refactor: move room list search to shared components

* refactor: add view model

* refactor: use view and vm in room list search component

* refactor: use room list id instead of class for landmark navigation

* refactor: remove old room list search css

* test: add screenshots test for room list search view

* test: fix e2e test using class as selector...
2025-12-11 15:43:20 +00:00
Florian Duros
23fbe9cef6 UseCreateAutoDisposedViewModel for audio player (#31503)
* refactor: useCreateAutoDisposedViewModel for audio player

* Update src/viewmodels/audio/AudioPlayerViewModel.ts

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

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2025-12-11 15:41:27 +00:00
renovate[bot]
cd71c109d3 Update npm non-major dependencies (#31516)
* Update npm non-major dependencies

* Run prettier

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-12-11 15:37:25 +00:00
Timo
a28eabf73b Auto approve matrix rtc member event (sticky events) (#31452) 2025-12-11 12:20:42 +00:00
renovate[bot]
dbe8ad0529 Update dependency @vector-im/compound-design-tokens to v6.4.2 (#31478)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 11:16:53 +00:00
David Langley
b446506aee Size Autocomplete relative to the RoomView height rather than the viewport height (#31425)
* Size Autocomplete relative to the RoomView height rather than the viewport height

* Add screenshot for the autocomplete in a regression changes the height

* Add cjk fonts to support rendering text emoticons displayed in slash command picker

* Maybe when actually running the tests?

* Try after system dependencies and clear font cache

* Add cjk fonts to support rendering text emoticons displayed in slash command picker

Try after system dependencies and clear font cache

Maybe when actually running the tests?

Revert "Add cjk fonts to support rendering text emoticons displayed in slash command picker"

This reverts commit 46fa014308b6010626174f8cd0d3a978488963ee.

* Render emoji autocoplete instead

* Remove font install that didn't work
2025-12-11 10:55:20 +00:00
256 changed files with 12613 additions and 2925 deletions

View File

@@ -92,7 +92,7 @@
"@matrix-org/spec": "^1.7.0",
"@sentry/browser": "^10.0.0",
"@types/png-chunks-extract": "^1.0.2",
"@vector-im/compound-design-tokens": "6.4.1",
"@vector-im/compound-design-tokens": "6.4.2",
"@vector-im/compound-web": "^8.3.1",
"@vector-im/matrix-wysiwyg": "2.40.0",
"@zxcvbn-ts/core": "^3.0.4",
@@ -137,7 +137,7 @@
"opus-recorder": "^8.0.3",
"pako": "^2.0.3",
"png-chunks-extract": "^1.0.0",
"posthog-js": "1.297.2",
"posthog-js": "1.302.2",
"qrcode": "1.5.4",
"re-resizable": "6.11.2",
"react": "^19.0.0",
@@ -281,7 +281,7 @@
"postcss-preset-env": "^10.0.0",
"postcss-scss": "^4.0.4",
"postcss-simple-vars": "^7.0.1",
"prettier": "3.6.2",
"prettier": "3.7.4",
"process": "^0.11.10",
"raw-loader": "^4.0.2",
"rimraf": "^6.0.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -19,6 +19,7 @@ export * from "./pill-input/Pill";
export * from "./pill-input/PillInput";
export * from "./rich-list/RichItem";
export * from "./rich-list/RichList";
export * from "./room-list/RoomListSearchView";
export * from "./utils/Box";
export * from "./utils/Flex";

View File

@@ -0,0 +1,47 @@
/*
* 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.
*/
.view {
/* From figma, this should be aligned with the room header */
min-height: 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);
}
.search {
/* The search button should take all the remaining space */
flex: 1;
/* !important is needed to override compound button in EW */
font: var(--cpd-font-body-md-regular) !important;
color: var(--cpd-color-text-secondary) !important;
min-width: 0;
svg {
fill: var(--cpd-color-icon-secondary);
}
}
.search_container {
flex: 1;
/* Shrink and truncate the search text */
white-space: nowrap;
overflow: hidden;
kbd {
font-family: inherit;
}
}
.search_text {
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: start;
}

View File

@@ -0,0 +1,74 @@
/*
* 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 { fn } from "storybook/test";
import type { Meta, StoryFn } from "@storybook/react-vite";
import {
RoomListSearchView,
type RoomListSearchViewActions,
type RoomListSearchViewSnapshot,
} from "./RoomListSearchView";
import { useMockedViewModel } from "../../useMockedViewModel";
type RoomListSearchProps = RoomListSearchViewSnapshot & RoomListSearchViewActions;
const RoomListSearchViewWrapper = ({
onSearchClick,
onDialPadClick,
onExploreClick,
...rest
}: RoomListSearchProps): JSX.Element => {
const vm = useMockedViewModel(rest, {
onSearchClick,
onDialPadClick,
onExploreClick,
});
return <RoomListSearchView vm={vm} />;
};
export default {
title: "Room List/RoomListSearchView",
component: RoomListSearchViewWrapper,
tags: ["autodocs"],
args: {
displayExploreButton: true,
displayDialButton: false,
searchShortcut: "⌘ K",
onSearchClick: fn(),
onDialPadClick: fn(),
onExploreClick: fn(),
},
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/design/vlmt46QDdE4dgXDiyBJXqp/ER-33-Left-Panel-2025?node-id=98-1979&t=vafb4zoYMNLRuAbh-4",
},
},
} as Meta<typeof RoomListSearchViewWrapper>;
const Template: StoryFn<typeof RoomListSearchViewWrapper> = (args) => <RoomListSearchViewWrapper {...args} />;
export const Default = Template.bind({});
export const WithDialPad = Template.bind({});
WithDialPad.args = {
displayDialButton: true,
};
export const WithoutExplore = Template.bind({});
WithoutExplore.args = {
displayExploreButton: false,
};
export const AllButtons = Template.bind({});
AllButtons.args = {
displayExploreButton: true,
displayDialButton: true,
searchShortcut: "⌘ K",
};

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import { render, screen } from "jest-matrix-react";
import { composeStories } from "@storybook/react-vite";
import React from "react";
import userEvent from "@testing-library/user-event";
import * as stories from "./RoomListSearchView.stories";
import {
RoomListSearchView,
type RoomListSearchViewActions,
type RoomListSearchViewSnapshot,
} from "./RoomListSearchView";
import { MockViewModel } from "../../viewmodel/MockViewModel";
const { Default, WithDialPad, WithoutExplore, AllButtons } = composeStories(stories);
describe("RoomListSearchView", () => {
afterEach(() => {
jest.clearAllMocks();
});
describe("Storybook snapshots", () => {
it("renders the default state", () => {
const { container } = render(<Default />);
expect(container).toMatchSnapshot();
});
it("renders with dial pad button", () => {
const { container } = render(<WithDialPad />);
expect(container).toMatchSnapshot();
});
it("renders without explore button", () => {
const { container } = render(<WithoutExplore />);
expect(container).toMatchSnapshot();
});
it("renders with all buttons visible", () => {
const { container } = render(<AllButtons />);
expect(container).toMatchSnapshot();
});
});
describe("User interactions", () => {
const onSearchClick = jest.fn();
const onDialPadClick = jest.fn();
const onExploreClick = jest.fn();
class TestViewModel extends MockViewModel<RoomListSearchViewSnapshot> implements RoomListSearchViewActions {
public onSearchClick = onSearchClick;
public onDialPadClick = onDialPadClick;
public onExploreClick = onExploreClick;
}
it("should call onSearchClick when search button is clicked", async () => {
const user = userEvent.setup();
const vm = new TestViewModel({
displayExploreButton: false,
displayDialButton: false,
searchShortcut: "⌘ K",
});
render(<RoomListSearchView vm={vm} />);
await user.click(screen.getByRole("button", { name: "Search ⌘ K" }));
expect(onSearchClick).toHaveBeenCalledTimes(1);
});
it("should call onDialPadClick when dial pad button is clicked", async () => {
const user = userEvent.setup();
const vm = new TestViewModel({
displayExploreButton: false,
displayDialButton: true,
searchShortcut: "⌘ K",
});
render(<RoomListSearchView vm={vm} />);
await user.click(screen.getByRole("button", { name: "Open dial pad" }));
expect(onDialPadClick).toHaveBeenCalledTimes(1);
});
it("should call onExploreClick when explore button is clicked", async () => {
const user = userEvent.setup();
const vm = new TestViewModel({
displayExploreButton: true,
displayDialButton: false,
searchShortcut: "⌘ K",
});
render(<RoomListSearchView vm={vm} />);
await user.click(screen.getByRole("button", { name: "Explore rooms" }));
expect(onExploreClick).toHaveBeenCalledTimes(1);
});
});
});

View File

@@ -0,0 +1,119 @@
/*
* 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, type MouseEventHandler } from "react";
import { Button } from "@vector-im/compound-web";
import ExploreIcon from "@vector-im/compound-design-tokens/assets/web/icons/explore";
import SearchIcon from "@vector-im/compound-design-tokens/assets/web/icons/search";
import DialPadIcon from "@vector-im/compound-design-tokens/assets/web/icons/dial-pad";
import styles from "./RoomListSearchView.module.css";
import { type ViewModel } from "../../viewmodel/ViewModel";
import { useViewModel } from "../../useViewModel";
import { Flex } from "../../utils/Flex";
import { useI18n } from "../../utils/i18nContext";
export interface RoomListSearchViewSnapshot {
/**
* Whether to display the explore button.
*/
displayExploreButton: boolean;
/**
* Whether to display the dial pad button.
*/
displayDialButton: boolean;
/**
* The keyboard shortcut text to display for the search action.
* For example: "⌘ K" on macOS or "Ctrl K" on other platforms.
*/
searchShortcut: string;
}
export interface RoomListSearchViewActions {
/**
* Handles the click event on the search button.
*/
onSearchClick: MouseEventHandler<HTMLButtonElement>;
/**
* Handles the click event on the dial pad button.
*/
onDialPadClick: MouseEventHandler<HTMLButtonElement>;
/**
* Handles the click event on the explore button.
*/
onExploreClick: MouseEventHandler<HTMLButtonElement>;
}
/**
* The view model for the room list search component.
*/
export type RoomListSearchViewModel = ViewModel<RoomListSearchViewSnapshot> & RoomListSearchViewActions;
interface RoomListSearchViewProps {
/**
* The view model for the room list search component.
*/
vm: RoomListSearchViewModel;
}
/**
* A search component to be displayed at the top of the room list.
* The component provides search functionality, optional dial pad access, and optional room exploration.
*
* @example
* ```tsx
* <RoomListSearchView vm={roomListSearchViewModel} />
* ```
*/
export function RoomListSearchView({ vm }: Readonly<RoomListSearchViewProps>): JSX.Element {
const { translate: _t } = useI18n();
const { displayExploreButton, displayDialButton, searchShortcut } = useViewModel(vm);
return (
<Flex
data-testid="room-list-search"
className={styles.view}
role="search"
gap="var(--cpd-space-2x)"
align="center"
>
<Button
id="room-list-search-button"
className={styles.search}
kind="secondary"
size="sm"
Icon={SearchIcon}
onClick={vm.onSearchClick}
>
<Flex className={styles["search_container"]} as="span" justify="space-between">
<span className={styles["search_text"]}>{_t("action|search")}</span>
<kbd>{searchShortcut}</kbd>
</Flex>
</Button>
{displayDialButton && (
<Button
kind="secondary"
size="sm"
Icon={DialPadIcon}
iconOnly={true}
aria-label={_t("left_panel|open_dial_pad")}
onClick={vm.onDialPadClick}
/>
)}
{displayExploreButton && (
<Button
kind="secondary"
size="sm"
Icon={ExploreIcon}
iconOnly={true}
aria-label={_t("action|explore_rooms")}
onClick={vm.onExploreClick}
/>
)}
</Flex>
);
}

View File

@@ -0,0 +1,290 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`RoomListSearchView Storybook snapshots renders the default state 1`] = `
<div>
<div
class="flex view"
data-testid="room-list-search"
role="search"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
>
<button
class="_button_187yx_8 search _has-icon_187yx_57"
data-kind="secondary"
data-size="sm"
id="room-list-search-button"
role="button"
tabindex="0"
>
<svg
aria-hidden="true"
fill="currentColor"
height="20"
viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
/>
</svg>
<span
class="flex search_container"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
>
<span
class="search_text"
>
Search
</span>
<kbd>
⌘ K
</kbd>
</span>
</button>
<button
aria-label="Explore rooms"
class="_button_187yx_8 _has-icon_187yx_57 _icon-only_187yx_50"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
<svg
aria-hidden="true"
fill="currentColor"
height="20"
viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 12q0-.424.287-.713A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 12 13m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20m0 0q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4t5.675 2.325T20 12t-2.325 5.675T12 20m1.675-5.85q.15-.075.275-.2t.2-.275l2.925-6.25q.125-.25-.062-.437-.188-.188-.438-.063l-6.25 2.925q-.15.075-.275.2t-.2.275l-2.925 6.25q-.125.25.063.438.186.186.437.062z"
/>
</svg>
</button>
</div>
</div>
`;
exports[`RoomListSearchView Storybook snapshots renders with all buttons visible 1`] = `
<div>
<div
class="flex view"
data-testid="room-list-search"
role="search"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
>
<button
class="_button_187yx_8 search _has-icon_187yx_57"
data-kind="secondary"
data-size="sm"
id="room-list-search-button"
role="button"
tabindex="0"
>
<svg
aria-hidden="true"
fill="currentColor"
height="20"
viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
/>
</svg>
<span
class="flex search_container"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
>
<span
class="search_text"
>
Search
</span>
<kbd>
⌘ K
</kbd>
</span>
</button>
<button
aria-label="Open dial pad"
class="_button_187yx_8 _has-icon_187yx_57 _icon-only_187yx_50"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
<svg
aria-hidden="true"
fill="currentColor"
height="20"
viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 18.6c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8M6.6 2.4c-.99 0-1.8.81-1.8 1.8S5.61 6 6.6 6s1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0 5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0 5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8M17.4 6c.99 0 1.8-.81 1.8-1.8s-.81-1.8-1.8-1.8-1.8.81-1.8 1.8.81 1.8 1.8 1.8M12 13.2c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m5.4 0c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0-5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m-5.4 0c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0-5.4c-.99 0-1.8.81-1.8 1.8S11.01 6 12 6s1.8-.81 1.8-1.8-.81-1.8-1.8-1.8"
/>
</svg>
</button>
<button
aria-label="Explore rooms"
class="_button_187yx_8 _has-icon_187yx_57 _icon-only_187yx_50"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
<svg
aria-hidden="true"
fill="currentColor"
height="20"
viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 12q0-.424.287-.713A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 12 13m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20m0 0q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4t5.675 2.325T20 12t-2.325 5.675T12 20m1.675-5.85q.15-.075.275-.2t.2-.275l2.925-6.25q.125-.25-.062-.437-.188-.188-.438-.063l-6.25 2.925q-.15.075-.275.2t-.2.275l-2.925 6.25q-.125.25.063.438.186.186.437.062z"
/>
</svg>
</button>
</div>
</div>
`;
exports[`RoomListSearchView Storybook snapshots renders with dial pad button 1`] = `
<div>
<div
class="flex view"
data-testid="room-list-search"
role="search"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
>
<button
class="_button_187yx_8 search _has-icon_187yx_57"
data-kind="secondary"
data-size="sm"
id="room-list-search-button"
role="button"
tabindex="0"
>
<svg
aria-hidden="true"
fill="currentColor"
height="20"
viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
/>
</svg>
<span
class="flex search_container"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
>
<span
class="search_text"
>
Search
</span>
<kbd>
⌘ K
</kbd>
</span>
</button>
<button
aria-label="Open dial pad"
class="_button_187yx_8 _has-icon_187yx_57 _icon-only_187yx_50"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
<svg
aria-hidden="true"
fill="currentColor"
height="20"
viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 18.6c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8M6.6 2.4c-.99 0-1.8.81-1.8 1.8S5.61 6 6.6 6s1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0 5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0 5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8M17.4 6c.99 0 1.8-.81 1.8-1.8s-.81-1.8-1.8-1.8-1.8.81-1.8 1.8.81 1.8 1.8 1.8M12 13.2c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m5.4 0c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0-5.4c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m-5.4 0c-.99 0-1.8.81-1.8 1.8s.81 1.8 1.8 1.8 1.8-.81 1.8-1.8-.81-1.8-1.8-1.8m0-5.4c-.99 0-1.8.81-1.8 1.8S11.01 6 12 6s1.8-.81 1.8-1.8-.81-1.8-1.8-1.8"
/>
</svg>
</button>
<button
aria-label="Explore rooms"
class="_button_187yx_8 _has-icon_187yx_57 _icon-only_187yx_50"
data-kind="secondary"
data-size="sm"
role="button"
tabindex="0"
>
<svg
aria-hidden="true"
fill="currentColor"
height="20"
viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 13a.97.97 0 0 1-.713-.287A.97.97 0 0 1 11 12q0-.424.287-.713A.97.97 0 0 1 12 11q.424 0 .713.287.287.288.287.713 0 .424-.287.713A.97.97 0 0 1 12 13m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4 6.325 6.325 4 12t2.325 5.675T12 20m0 0q-3.35 0-5.675-2.325T4 12t2.325-5.675T12 4t5.675 2.325T20 12t-2.325 5.675T12 20m1.675-5.85q.15-.075.275-.2t.2-.275l2.925-6.25q.125-.25-.062-.437-.188-.188-.438-.063l-6.25 2.925q-.15.075-.275.2t-.2.275l-2.925 6.25q-.125.25.063.438.186.186.437.062z"
/>
</svg>
</button>
</div>
</div>
`;
exports[`RoomListSearchView Storybook snapshots renders without explore button 1`] = `
<div>
<div
class="flex view"
data-testid="room-list-search"
role="search"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
>
<button
class="_button_187yx_8 search _has-icon_187yx_57"
data-kind="secondary"
data-size="sm"
id="room-list-search-button"
role="button"
tabindex="0"
>
<svg
aria-hidden="true"
fill="currentColor"
height="20"
viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
/>
</svg>
<span
class="flex search_container"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: space-between; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
>
<span
class="search_text"
>
Search
</span>
<kbd>
⌘ K
</kbd>
</span>
</button>
</div>
</div>
`;

View File

@@ -0,0 +1,9 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
export type { RoomListSearchViewModel, RoomListSearchViewSnapshot } from "./RoomListSearchView";
export { RoomListSearchView } from "./RoomListSearchView";

View File

@@ -313,9 +313,9 @@
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@element-hq/element-web-module-api@^1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@element-hq/element-web-module-api/-/element-web-module-api-1.8.0.tgz#95aa4ec22609cf0f4a7f24274473af0645a16f2a"
integrity sha512-lMiDA9ubP3mZZupIMT8T3wS0riX30rYZj3pFpdP4cfZhkWZa3FJFStokAy5OnaHyENC7Px1cqkBGqilOWewY/A==
version "1.9.0"
resolved "https://registry.yarnpkg.com/@element-hq/element-web-module-api/-/element-web-module-api-1.9.0.tgz#2e4fcc8809418c8670d4f0576bc4a9a235bc6c50"
integrity sha512-Ao/V9w+wysZK4bh61LlKlznF10n2ZbD6KcUI46/zUMttXbmJn3ahvbzhEpwYcD+Cjy3ag5ycxLIIGkKV/fncXg==
"@element-hq/element-web-playwright-common@^2.0.0":
version "2.1.0"
@@ -2032,9 +2032,9 @@
integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==
"@vector-im/compound-design-tokens@^6.3.0":
version "6.4.0"
resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-6.4.0.tgz#2e51f39f79ebda985a2f6cf80d567b9307aff03a"
integrity sha512-93nYQZMgUt6apjCwwnMhMxN8VYQXN3GYOnwovwJjavImwsCGwI/e853BV/DstrWumYh6k5pZsP9e6AF+nz3SIQ==
version "6.4.2"
resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-6.4.2.tgz#29189d6480c8ccf09ce143cb4618fb13a56a7583"
integrity sha512-LHLGZgnatH3mQXn9TF+m/SUinPS2nKvuCT/r2AQ7HAgEIG/S/Ck6e/iV4IFQLSZnd9gU0RlMsLkP2UQ/AKUEBA==
"@vitest/expect@3.2.4":
version "3.2.4"
@@ -5855,9 +5855,9 @@ prelude-ls@^1.2.1:
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prettier@^3.6.2:
version "3.6.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.2.tgz#ccda02a1003ebbb2bfda6f83a074978f608b9393"
integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==
version "3.7.4"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.7.4.tgz#d2f8335d4b1cec47e1c8098645411b0c9dff9c0f"
integrity sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==
pretty-format@30.2.0, pretty-format@^30.0.0:
version "30.2.0"
@@ -7088,9 +7088,9 @@ vite-plugin-node-polyfills@^0.24.0:
node-stdlib-browser "^1.2.0"
vite@^7.1.9:
version "7.2.4"
resolved "https://registry.yarnpkg.com/vite/-/vite-7.2.4.tgz#a3a09c7e25487612ecc1119c7d412c73da35bd4e"
integrity sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==
version "7.2.7"
resolved "https://registry.yarnpkg.com/vite/-/vite-7.2.7.tgz#0789a4c3206081699f34a9ecca2dda594a07478e"
integrity sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==
dependencies:
esbuild "^0.25.0"
fdir "^6.5.0"

View File

@@ -29,7 +29,7 @@ test.describe("Landmark navigation tests", () => {
// Pressing Control+F6 again will focus room search
await page.keyboard.press("ControlOrMeta+F6");
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
await expect(page.locator("#room-list-search-button")).toBeFocused();
// Pressing Control+F6 again will focus the message composer
await page.keyboard.press("ControlOrMeta+F6");
@@ -44,7 +44,7 @@ test.describe("Landmark navigation tests", () => {
await expect(page.locator(".mx_HomePage")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
await expect(page.locator("#room-list-search-button")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_SpaceButton_active")).toBeFocused();
@@ -75,7 +75,7 @@ test.describe("Landmark navigation tests", () => {
// Pressing Control+F6 again will focus room search
await page.keyboard.press("ControlOrMeta+F6");
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
await expect(page.locator("#room-list-search-button")).toBeFocused();
// Pressing Control+F6 again will focus the room tile in the room list
await page.keyboard.press("ControlOrMeta+F6");
@@ -97,7 +97,7 @@ test.describe("Landmark navigation tests", () => {
await expect(page.locator(".mx_RoomListItemView_selected")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
await expect(page.locator("#room-list-search-button")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_SpaceButton_active")).toBeFocused();
@@ -131,7 +131,7 @@ test.describe("Landmark navigation tests", () => {
// Pressing Control+F6 again will focus room search
await page.keyboard.press("ControlOrMeta+F6");
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
await expect(page.locator("#room-list-search-button")).toBeFocused();
// Pressing Control+F6 again will focus the room tile in the room list
await page.keyboard.press("ControlOrMeta+F6");
@@ -153,7 +153,7 @@ test.describe("Landmark navigation tests", () => {
await expect(page.locator(".mx_RoomListItemView")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_RoomListSearch_search")).toBeFocused();
await expect(page.locator("#room-list-search-button")).toBeFocused();
await page.keyboard.press("ControlOrMeta+Shift+F6");
await expect(page.locator(".mx_SpaceButton_active")).toBeFocused();

View File

@@ -351,7 +351,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
const composer = thread.locator(".mx_MessageComposer--compact");
// Assert that the reply preview contains audio ReplyTile the file info button
await expect(
composer.locator(".mx_ReplyPreview .mx_ReplyTile_audio .mx_MFileBody_info[role='button']"),
composer.locator(".mx_ReplyPreview .mx_ReplyTile .mx_MFileBody_info[role='button']"),
).toBeVisible();
// Select :smile: emoji and send it
@@ -360,6 +360,6 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await composer.getByTestId("basicmessagecomposer").press("Enter");
// Assert that the file name is rendered on the file button
await expect(threadTile.locator(".mx_ReplyTile_audio .mx_MFileBody_info[role='button']")).toBeVisible();
await expect(threadTile.locator(".mx_ReplyTile .mx_MFileBody_info[role='button']")).toBeVisible();
});
});

View File

@@ -168,5 +168,19 @@ test.describe("Composer", () => {
await composer.press("Enter");
await expect(page.locator(".mx_EventTile_body", { hasText: "Bob" })).toBeVisible();
});
test("renders emoji autocomplete", { tag: "@screenshot" }, async ({ page }) => {
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
// Type ":+1" to trigger emoji autocomplete
await composer.pressSequentially(":+1");
// Wait for autocomplete to appear
const autocomplete = page.locator("#mx_Autocomplete");
await expect(autocomplete).toBeVisible();
// Take a screenshot of the autocomplete
await expect(autocomplete).toMatchScreenshot("emoji-autocomplete.png");
});
});
});

View File

@@ -31,15 +31,11 @@ const startDMWithBob = async (page: Page, bob: Bot) => {
const testMessages = async (page: Page, bob: Bot, bobRoomId: string) => {
// check the invite message
await expect(
page.locator(".mx_EventTile", { hasText: "Hey!" }).locator(".mx_EventTile_e2eIcon_warning"),
).not.toBeVisible();
await expect(page.locator(".mx_EventTile", { hasText: "Hey!" }).locator(".mx_EventTile_e2eIcon")).not.toBeVisible();
// Bob sends a response
await bob.sendMessage(bobRoomId, "Hoo!");
await expect(
page.locator(".mx_EventTile", { hasText: "Hoo!" }).locator(".mx_EventTile_e2eIcon_warning"),
).not.toBeVisible();
await expect(page.locator(".mx_EventTile", { hasText: "Hoo!" }).locator(".mx_EventTile_e2eIcon")).not.toBeVisible();
};
const bobJoin = async (page: Page, bob: Bot) => {

View File

@@ -30,69 +30,80 @@ test.describe("Cryptography", function () {
test.describe("decryption failure messages", () => {
test.skip(isDendrite, "Dendrite lacks support for MSC3967 so requires additional auth here");
test("should handle device-relative historical messages", async ({
homeserver,
page,
app,
credentials,
user,
}) => {
test.setTimeout(60000);
test(
"should handle device-relative historical messages",
{ tag: "@screenshot" },
async ({ homeserver, page, app, credentials, user }) => {
test.setTimeout(60000);
// Start with a logged-in session, without key backup, and send a message.
await createRoom(page, "Test room", true);
await sendMessageInCurrentRoom(page, "test test");
// Start with a logged-in session, without key backup, and send a message.
await createRoom(page, "Test room", true);
await sendMessageInCurrentRoom(page, "test test");
// Log out, discarding the key for the sent message.
await logOutOfElement(page, true);
// Log out, discarding the key for the sent message.
await logOutOfElement(page, true);
// Log in again, and see how the message looks.
await logIntoElement(page, credentials);
await app.viewRoomByName("Test room");
const lastTile = page.locator(".mx_EventTile").last();
await expect(lastTile).toContainText("Historical messages are not available on this device");
await expect(lastTile.locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible();
// Log in again, and see how the message looks.
await logIntoElement(page, credentials);
await app.viewRoomByName("Test room");
const lastTile = page.locator(".mx_EventTile").last();
await expect(lastTile).toContainText("Historical messages are not available on this device");
await expect(lastTile.locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
"This message could not be decrypted",
);
await expect(lastTile).toMatchScreenshot("history-not-available.png", {
mask: [page.locator(".mx_MessageTimestamp")],
});
// Now, we set up key backup, and then send another message.
const secretStorageKey = await enableKeyBackup(app);
await app.viewRoomByName("Test room");
await sendMessageInCurrentRoom(page, "test2 test2");
// Now, we set up key backup, and then send another message.
const secretStorageKey = await enableKeyBackup(app);
await app.viewRoomByName("Test room");
await sendMessageInCurrentRoom(page, "test2 test2");
// Workaround for https://github.com/element-hq/element-web/issues/27267. It can take up to 10 seconds for
// the key to be backed up.
await page.waitForTimeout(10000);
// Workaround for https://github.com/element-hq/element-web/issues/27267. It can take up to 10 seconds for
// the key to be backed up.
await page.waitForTimeout(10000);
// Finally, log out again, and back in, skipping verification for now, and see what we see.
await logOutOfElement(page);
await logIntoElement(page, credentials);
await page.locator(".mx_AuthPage").getByRole("button", { name: "Skip verification for now" }).click();
await page.locator(".mx_AuthPage").getByRole("button", { name: "I'll verify later" }).click();
await app.viewRoomByName("Test room");
// Finally, log out again, and back in, skipping verification for now, and see what we see.
await logOutOfElement(page);
await logIntoElement(page, credentials);
await page.locator(".mx_AuthPage").getByRole("button", { name: "Skip verification for now" }).click();
await page.locator(".mx_AuthPage").getByRole("button", { name: "I'll verify later" }).click();
await app.viewRoomByName("Test room");
// In this case, the call to cryptoApi.isEncryptionEnabledInRoom is taking a long time to resolve
await page.waitForTimeout(1000);
// In this case, the call to cryptoApi.isEncryptionEnabledInRoom is taking a long time to resolve
await page.waitForTimeout(1000);
// There should be two historical events in the timeline
const tiles = await page.locator(".mx_EventTile").all();
expect(tiles.length).toBeGreaterThanOrEqual(2);
// look at the last two tiles only
for (const tile of tiles.slice(-2)) {
await expect(tile).toContainText("You need to verify this device for access to historical messages");
await expect(tile.locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible();
}
// There should be two historical events in the timeline
const tiles = await page.locator(".mx_EventTile").all();
expect(tiles.length).toBeGreaterThanOrEqual(2);
// look at the last two tiles only
for (const tile of tiles.slice(-2)) {
await expect(tile).toContainText(
"You need to verify this device for access to historical messages",
);
await expect(tile.locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
"This message could not be decrypted",
);
}
// Now verify our device (setting up key backup), and check what happens
await verifySession(app, secretStorageKey);
const tilesAfterVerify = (await page.locator(".mx_EventTile").all()).slice(-2);
// Now verify our device (setting up key backup), and check what happens
await verifySession(app, secretStorageKey);
const tilesAfterVerify = (await page.locator(".mx_EventTile").all()).slice(-2);
// The first message still cannot be decrypted, because it was never backed up. It's now a regular UTD though.
await expect(tilesAfterVerify[0]).toContainText("Unable to decrypt message");
await expect(tilesAfterVerify[0].locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible();
// The first message still cannot be decrypted, because it was never backed up. It's now a regular UTD though.
await expect(tilesAfterVerify[0]).toContainText("Unable to decrypt message");
await expect(tilesAfterVerify[0].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
"This message could not be decrypted",
);
// The second message should now be decrypted, with a grey shield
await expect(tilesAfterVerify[1]).toContainText("test2 test2");
await expect(tilesAfterVerify[1].locator(".mx_EventTile_e2eIcon_normal")).toBeVisible();
});
// The second message should now be decrypted, with a grey shield
await expect(tilesAfterVerify[1]).toContainText("test2 test2");
await expect(tilesAfterVerify[1].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
"The authenticity of this encrypted message can't be guaranteed on this device.",
);
},
);
test.describe("non-joined historical messages", () => {
test.skip(isDendrite, "does not yet support membership on events");
@@ -186,7 +197,9 @@ test.describe("Cryptography", function () {
// The first message from Bob was sent before Alice was in the room, so should
// be different from the standard UTD message
await expect(tiles[tiles.length - 5]).toContainText("You don't have access to this message");
await expect(tiles[tiles.length - 5].locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible();
await expect(tiles[tiles.length - 5].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
"This message could not be decrypted",
);
// The second message from Bob should be decryptable
await expect(tiles[tiles.length - 2]).toContainText("This should be decryptable");
@@ -196,7 +209,9 @@ test.describe("Cryptography", function () {
// in the room and is expected to be decryptable, so this should have the
// standard UTD message
await expect(tiles[tiles.length - 1]).toContainText("Unable to decrypt message");
await expect(tiles[tiles.length - 1].locator(".mx_EventTile_e2eIcon_decryption_failure")).toBeVisible();
await expect(tiles[tiles.length - 1].locator(".mx_EventTile_e2eIcon")).toHaveAccessibleName(
"This message could not be decrypted",
);
});
test("should be able to jump to a message sent before our last join event", async ({

View File

@@ -68,7 +68,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
await doTwoWaySasVerification(page, verifier);
await infoDialog.getByRole("button", { name: "They match" }).click();
await expect(page.locator(".mx_E2EIcon_verified")).toMatchScreenshot("device-verified-e2eIcon.png");
await expect(page.locator(".mx_E2EIcon")).toMatchScreenshot("device-verified-e2eIcon.png");
await infoDialog.getByRole("button", { name: "Got it" }).click();
// Check that our device is now cross-signed
@@ -130,53 +130,68 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
await page.unrouteAll({ behavior: "ignoreErrors" });
});
test("Verify device with QR code during login", async ({ page, app, credentials, homeserver }) => {
// A mode 0x02 verification: "self-verifying in which the current device does not yet trust the master key"
await logIntoElement(page, credentials);
test(
"Verify device with QR code during login",
{ tag: "@screenshot" },
async ({ page, app, credentials, homeserver }) => {
// A mode 0x02 verification: "self-verifying in which the current device does not yet trust the master key"
await logIntoElement(page, credentials);
// Launch the verification request between alice and the bot
const verificationRequest = await initiateAliceVerificationRequest(page);
// Launch the verification request between alice and the bot
const verificationRequest = await initiateAliceVerificationRequest(page);
const infoDialog = page.locator(".mx_InfoDialog");
// feed the QR code into the verification request.
const qrData = await readQrCode(infoDialog);
const verifier = await verificationRequest.evaluateHandle(
(request, qrData) => request.scanQRCode(new Uint8ClampedArray(qrData)),
[...qrData],
);
const infoDialog = page.locator(".mx_InfoDialog");
// feed the QR code into the verification request.
const qrData = await readQrCode(infoDialog);
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("qr-code.png", {
mask: [infoDialog.locator("img")],
});
const verifier = await verificationRequest.evaluateHandle(
(request, qrData) => request.scanQRCode(new Uint8ClampedArray(qrData)),
[...qrData],
);
// Confirm that the bot user scanned successfully
await expect(infoDialog.getByText("Confirm that you see a green shield on your other device")).toBeVisible();
await infoDialog.getByRole("button", { name: "Yes, I see a green shield" }).click();
await infoDialog.getByRole("button", { name: "Got it" }).click();
// Confirm that the bot user scanned successfully
await expect(
infoDialog.getByText("Confirm that you see a green shield on your other device"),
).toBeVisible();
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("confirm-green-shield.png");
await infoDialog.getByRole("button", { name: "Yes, I see a green shield" }).click();
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("got-it.png");
await infoDialog.getByRole("button", { name: "Got it" }).click();
// wait for the bot to see we have finished
await verifier.evaluate((verifier) => verifier.verify());
// wait for the bot to see we have finished
await verifier.evaluate((verifier) => verifier.verify());
// the bot uploads the signatures asynchronously, so wait for that to happen
await page.waitForTimeout(1000);
// the bot uploads the signatures asynchronously, so wait for that to happen
await page.waitForTimeout(1000);
// our device should trust the bot device
await app.client.evaluate(async (cli, aliceBotCredentials) => {
const deviceStatus = await cli
.getCrypto()!
.getDeviceVerificationStatus(aliceBotCredentials.userId, aliceBotCredentials.deviceId);
if (!deviceStatus.isVerified()) {
throw new Error("Bot device was not verified after QR code verification");
}
}, aliceBotClient.credentials);
// our device should trust the bot device
await app.client.evaluate(async (cli, aliceBotCredentials) => {
const deviceStatus = await cli
.getCrypto()!
.getDeviceVerificationStatus(aliceBotCredentials.userId, aliceBotCredentials.deviceId);
if (!deviceStatus.isVerified()) {
throw new Error("Bot device was not verified after QR code verification");
}
}, aliceBotClient.credentials);
// Check that our device is now cross-signed
await checkDeviceIsCrossSigned(app);
// Check that our device is now cross-signed
await checkDeviceIsCrossSigned(app);
// Check that the current device is connected to key backup
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
});
// Check that the current device is connected to key backup
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
},
);
test("Verify device with Security Phrase during login", async ({ page, app, credentials, homeserver }) => {
await logIntoElement(page, credentials);
await enterRecoveryKeyAndCheckVerified(page, app, "new passphrase");
});
test(
"Verify device with Security Phrase during login",
{ tag: "@screenshot" },
async ({ page, app, credentials, homeserver }) => {
await logIntoElement(page, credentials);
await enterRecoveryKeyAndCheckVerified(page, app, "new passphrase", true);
},
);
test("Verify device with Recovery Key during login", async ({ page, app, credentials, homeserver }) => {
const recoveryKey = (await aliceBotClient.getRecoveryKey()).encodedPrivateKey;
@@ -226,7 +241,12 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
});
/** Helper for the three tests above which verify by recovery key */
async function enterRecoveryKeyAndCheckVerified(page: Page, app: ElementAppPage, recoveryKey: string) {
async function enterRecoveryKeyAndCheckVerified(
page: Page,
app: ElementAppPage,
recoveryKey: string,
screenshot = false,
) {
await page.getByRole("button", { name: "Use recovery key" }).click();
// Enter the recovery key
@@ -234,8 +254,12 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
// We use `pressSequentially` here to make sure that the FocusLock isn't causing us any problems
// (cf https://github.com/element-hq/element-web/issues/30089)
await dialog.getByTitle("Recovery key").pressSequentially(recoveryKey);
if (screenshot) {
await expect(page.locator(".mx_Dialog").filter({ hasText: "Enter your recovery key" })).toMatchScreenshot(
"recovery-key.png",
);
}
await dialog.getByRole("button", { name: "Continue", disabled: false }).click();
await page.getByRole("button", { name: "Done" }).click();
// Check that our device is now cross-signed

View File

@@ -77,11 +77,8 @@ test.describe("Cryptography", function () {
const last = page.locator(".mx_EventTile_last");
await expect(last).toContainText("Unable to decrypt message");
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_decryption_failure/);
await lastE2eIcon.focus();
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
"This message could not be decrypted",
);
await expect(lastE2eIcon).toHaveAccessibleName("This message could not be decrypted");
await expect(lastE2eIcon).toMatchScreenshot("event-shield-utd.png");
/* Should show a red padlock for an unencrypted message in an e2e room */
await bob.evaluate(
@@ -99,10 +96,8 @@ test.describe("Cryptography", function () {
);
await expect(last).toContainText("test unencrypted");
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
await expect(lastE2eIcon).toHaveAccessibleName("Not encrypted");
await expect(lastE2eIcon).toMatchScreenshot("event-shield-warning.png");
await lastE2eIcon.focus();
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText("Not encrypted");
/* Should show no padlock for an unverified user */
// bob sends a valid event
@@ -133,11 +128,8 @@ test.describe("Cryptography", function () {
/* should show red padlock for a message from an unverified device */
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified");
await expect(lastTile).toContainText("test encrypted from unverified");
await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
await lastTileE2eIcon.focus();
await expect(await app.getTooltipForElement(lastTileE2eIcon)).toContainText(
"Encrypted by a device not verified by its owner.",
);
await expect(lastTileE2eIcon).toHaveAccessibleName("Encrypted by a device not verified by its owner.");
await expect(lastE2eIcon).toMatchScreenshot("event-shield-not-verified.png");
/* Should show a red padlock for a message from an unverified device.
* Rust crypto remembers the verification state of the sending device, so it will know that the device was
@@ -153,64 +145,58 @@ test.describe("Cryptography", function () {
await app.viewRoomByName("TestRoom");
await expect(last).toContainText("test encrypted from unverified");
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
await lastE2eIcon.focus();
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
"Encrypted by a device not verified by its owner.",
);
await expect(lastE2eIcon).toHaveAccessibleName("Encrypted by a device not verified by its owner.");
await expect(lastE2eIcon).toMatchScreenshot("event-shield-not-verified.png");
},
);
test("Should show a grey padlock for a key restored from backup", async ({
page,
app,
bot: bob,
homeserver,
user: aliceCredentials,
}) => {
test.slow();
const securityKey = await enableKeyBackup(app);
test(
"Should show a grey padlock for a key restored from backup",
{ tag: "@screenshot" },
async ({ page, app, bot: bob, homeserver, user: aliceCredentials }) => {
test.slow();
const securityKey = await enableKeyBackup(app);
// bob sends a valid event
await bob.sendMessage(testRoomId, "test encrypted 1");
// bob sends a valid event
await bob.sendMessage(testRoomId, "test encrypted 1");
const lastTile = page.locator(".mx_EventTile_last");
const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon");
await expect(lastTile).toContainText("test encrypted 1");
// no e2e icon
await expect(lastTileE2eIcon).not.toBeVisible();
const lastTile = page.locator(".mx_EventTile_last");
const lastTileE2eIcon = lastTile.locator(".mx_EventTile_e2eIcon");
await expect(lastTile).toContainText("test encrypted 1");
// no e2e icon
await expect(lastTileE2eIcon).not.toBeVisible();
// Workaround for https://github.com/element-hq/element-web/issues/27267. It can take up to 10 seconds for
// the key to be backed up.
await page.waitForTimeout(10000);
// Workaround for https://github.com/element-hq/element-web/issues/27267. It can take up to 10 seconds for
// the key to be backed up.
await page.waitForTimeout(10000);
/* log out, and back in */
await logOutOfElement(page);
// Reload to work around a Rust crypto bug where it can hold onto the indexeddb even after logout
// https://github.com/element-hq/element-web/issues/25779
await page.addInitScript(() => {
// When we reload, the initScript created by the `user`/`pageWithCredentials` fixtures
// will re-inject the original credentials into localStorage, which we don't want.
// To work around, we add a second initScript which will clear localStorage again.
window.localStorage.clear();
});
await page.reload();
await logIntoElementAndVerify(page, aliceCredentials, securityKey);
/* log out, and back in */
await logOutOfElement(page);
// Reload to work around a Rust crypto bug where it can hold onto the indexeddb even after logout
// https://github.com/element-hq/element-web/issues/25779
await page.addInitScript(() => {
// When we reload, the initScript created by the `user`/`pageWithCredentials` fixtures
// will re-inject the original credentials into localStorage, which we don't want.
// To work around, we add a second initScript which will clear localStorage again.
window.localStorage.clear();
});
await page.reload();
await logIntoElementAndVerify(page, aliceCredentials, securityKey);
/* go back to the test room and find Bob's message again */
await app.viewRoomById(testRoomId);
await expect(lastTile).toContainText("test encrypted 1");
// The gray shield would be a mx_EventTile_e2eIcon_normal. The red shield would be a mx_EventTile_e2eIcon_warning.
// No shield would have no div mx_EventTile_e2eIcon at all.
await expect(lastTileE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_normal/);
await lastTileE2eIcon.hover();
// The key is coming from backup, so it is not anymore possible to establish if the claimed device
// creator of this key is authentic. The tooltip should be "The authenticity of this encrypted message can't be guaranteed on this device."
// It is not "Encrypted by an unknown or deleted device." even if the claimed device is actually deleted.
await expect(await app.getTooltipForElement(lastTileE2eIcon)).toContainText(
"The authenticity of this encrypted message can't be guaranteed on this device.",
);
});
/* go back to the test room and find Bob's message again */
await app.viewRoomById(testRoomId);
await expect(lastTile).toContainText("test encrypted 1");
// The gray shield would be a Compound info icon. The red shield would be a Compound error solid icon.
// No shield would have no div mx_EventTile_e2eIcon at all.
// The key is coming from backup, so it is not anymore possible to establish if the claimed device
// creator of this key is authentic. The tooltip should be "The authenticity of this encrypted message can't be guaranteed on this device."
// It is not "Encrypted by an unknown or deleted device." even if the claimed device is actually deleted.
await expect(lastTileE2eIcon).toHaveAccessibleName(
"The authenticity of this encrypted message can't be guaranteed on this device.",
);
await expect(lastTileE2eIcon).toMatchScreenshot("event-shield-authenticity.png");
},
);
test("should show the correct shield on edited e2e events", async ({ page, app, bot: bob, homeserver }) => {
// bob has a second, not cross-signed, device
@@ -224,7 +210,7 @@ test.describe("Cryptography", function () {
// the message should appear, decrypted, with no warning
await expect(
page.locator(".mx_EventTile", { hasText: "Hoo!" }).locator(".mx_EventTile_e2eIcon_warning"),
page.locator(".mx_EventTile", { hasText: "Hoo!" }).locator(".mx_EventTile_e2eIcon"),
).not.toBeVisible();
// bob sends an edit to the first message with his unverified device
@@ -241,7 +227,7 @@ test.describe("Cryptography", function () {
// the edit should have a warning
await expect(
page.locator(".mx_EventTile", { hasText: "Haa!" }).locator(".mx_EventTile_e2eIcon_warning"),
page.locator(".mx_EventTile", { hasText: "Haa!" }).locator(".mx_EventTile_e2eIcon"),
).toBeVisible();
// a second edit from the verified device should be ok
@@ -257,77 +243,69 @@ test.describe("Cryptography", function () {
});
await expect(
page.locator(".mx_EventTile", { hasText: "Hee!" }).locator(".mx_EventTile_e2eIcon_warning"),
page.locator(".mx_EventTile", { hasText: "Hee!" }).locator(".mx_EventTile_e2eIcon"),
).not.toBeVisible();
});
test("should show correct shields on events sent by devices which have since been deleted", async ({
page,
app,
bot: bob,
homeserver,
}) => {
// Workaround for https://github.com/element-hq/element-web/issues/28640:
// make sure that Alice has seen Bob's identity before she goes offline. We do this by opening
// his user info.
await waitForDevices(app, bob.credentials.userId, 1);
test(
"should show correct shields on events sent by devices which have since been deleted",
{ tag: "@screenshot" },
async ({ page, app, bot: bob, homeserver }) => {
// Workaround for https://github.com/element-hq/element-web/issues/28640:
// make sure that Alice has seen Bob's identity before she goes offline. We do this by opening
// his user info.
await waitForDevices(app, bob.credentials.userId, 1);
// Our app is blocked from syncing while Bob sends his messages.
await app.client.network.goOffline();
// Our app is blocked from syncing while Bob sends his messages.
await app.client.network.goOffline();
// Bob sends a message from his verified device
await bob.sendMessage(testRoomId, "test encrypted from verified");
// Bob sends a message from his verified device
await bob.sendMessage(testRoomId, "test encrypted from verified");
// And one from a second, not cross-signed, device
const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
await bobSecondDevice.waitForNextSync(); // make sure the client knows the room is encrypted
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified");
// And one from a second, not cross-signed, device
const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
await bobSecondDevice.waitForNextSync(); // make sure the client knows the room is encrypted
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from unverified");
// ... and then logs out both devices.
await bob.evaluate((cli) => cli.logout(true));
await bobSecondDevice.evaluate((cli) => cli.logout(true));
// ... and then logs out both devices.
await bob.evaluate((cli) => cli.logout(true));
await bobSecondDevice.evaluate((cli) => cli.logout(true));
// Let our app start syncing again
await app.client.network.goOnline();
// Let our app start syncing again
await app.client.network.goOnline();
// Wait for the messages to arrive. It can take quite a while for the sync to wake up.
const last = page.locator(".mx_EventTile_last");
await expect(last).toContainText("test encrypted from unverified", { timeout: 20000 });
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
await lastE2eIcon.focus();
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
"Encrypted by a device not verified by its owner.",
);
// Wait for the messages to arrive. It can take quite a while for the sync to wake up.
const last = page.locator(".mx_EventTile_last");
await expect(last).toContainText("test encrypted from unverified", { timeout: 20000 });
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
await expect(lastE2eIcon).toHaveAccessibleName("Encrypted by a device not verified by its owner.");
await expect(lastE2eIcon).toMatchScreenshot("event-shield-not-verified.png");
const penultimate = page.locator(".mx_EventTile").filter({ hasText: "test encrypted from verified" });
await assertNoE2EIcon(penultimate, app);
});
const penultimate = page.locator(".mx_EventTile").filter({ hasText: "test encrypted from verified" });
await assertNoE2EIcon(penultimate, app);
},
);
test("should show correct shields on events sent by users with changed identity", async ({
page,
app,
bot: bob,
homeserver,
}) => {
// Verify Bob
await verify(app, bob);
test(
"should show correct shields on events sent by users with changed identity",
{ tag: "@screenshot" },
async ({ page, app, bot: bob, homeserver }) => {
// Verify Bob
await verify(app, bob);
// Bob logs in a new device and resets cross-signing
const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
await bootstrapCrossSigningForClient(await bobSecondDevice.prepareClient(), bob.credentials, true);
// Bob logs in a new device and resets cross-signing
const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
await bootstrapCrossSigningForClient(await bobSecondDevice.prepareClient(), bob.credentials, true);
/* should show an error for a message from a previously verified device */
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from user that was previously verified");
const last = page.locator(".mx_EventTile_last");
await expect(last).toContainText("test encrypted from user that was previously verified");
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
await lastE2eIcon.focus();
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
"Sender's verified identity was reset",
);
});
/* should show an error for a message from a previously verified device */
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from user that was previously verified");
const last = page.locator(".mx_EventTile_last");
await expect(last).toContainText("test encrypted from user that was previously verified");
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
await expect(lastE2eIcon).toHaveAccessibleName("Sender's verified identity was reset");
await expect(lastE2eIcon).toMatchScreenshot("event-shield-identity-reset.png");
},
);
});
});
@@ -343,8 +321,6 @@ async function assertNoE2EIcon(messageLocator: Locator, app: ElementAppPage) {
const e2eIcon = messageLocator.locator(".mx_EventTile_e2eIcon");
if ((await e2eIcon.count()) > 0) {
// uh-oh, there is an e2e icon. Let's find out what it's about so that we can throw a helpful error.
await e2eIcon.focus();
const tooltip = await app.getTooltipForElement(e2eIcon);
throw new Error(`Found an unexpected e2eIcon with tooltip '${await tooltip.textContent()}'`);
await expect(e2eIcon).toHaveAccessibleName("None");
}
}

View File

@@ -29,6 +29,7 @@ export const test = base.extend<{
room1Name: "Room 1",
room1: async ({ room1Name: name, app, user, bot }, use) => {
const roomId = await app.client.createRoom({ name, invite: [bot.credentials.userId] });
await bot.awaitRoomMembership(roomId);
await use({ name, roomId });
},

View File

@@ -36,11 +36,13 @@ export const test = base.extend<{
roomAlphaName: "Room Alpha",
roomAlpha: async ({ roomAlphaName: name, app, user, bot }, use) => {
const roomId = await app.client.createRoom({ name, invite: [bot.credentials.userId] });
await bot.awaitRoomMembership(roomId);
await use({ name, roomId });
},
roomBetaName: "Room Beta",
roomBeta: async ({ roomBetaName: name, app, user, bot }, use) => {
const roomId = await app.client.createRoom({ name, invite: [bot.credentials.userId] });
await bot.awaitRoomMembership(roomId);
await use({ name, roomId });
},
msg: async ({ page, app, util }, use) => {

View File

@@ -13,72 +13,30 @@ import { test } from ".";
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("Message ordering", () => {
test.describe("in the main timeline", () => {
test.fixme(
"A receipt for the last event in sync order (even with wrong ts) marks a room as read",
() => {},
);
test.fixme(
"A receipt for a non-last event in sync order (even when ts makes it last) leaves room unread",
() => {},
);
test.fixme("A receipt for the last event in sync order (even with wrong ts) marks a room as read", () => {});
test.fixme("A receipt for a non-last event in sync order (even when ts makes it last) leaves room unread", () => {});
});
test.describe("in threads", () => {
// These don't pass yet - we need MSC4033 - we don't even know the Sync order yet
test.fixme(
"A receipt for the last event in sync order (even with wrong ts) marks a thread as read",
() => {},
);
test.fixme(
"A receipt for a non-last event in sync order (even when ts makes it last) leaves thread unread",
() => {},
);
test.fixme("A receipt for the last event in sync order (even with wrong ts) marks a thread as read", () => {});
test.fixme("A receipt for a non-last event in sync order (even when ts makes it last) leaves thread unread", () => {});
// These pass now and should not later - we should use order from MSC4033 instead of ts
// These are broken out
test.fixme(
"A receipt for last threaded event in ts order (even when it was received non-last) marks a thread as read",
() => {},
);
test.fixme(
"A receipt for non-last threaded event in ts order (even when it was received last) leaves thread unread",
() => {},
);
test.fixme(
"A receipt for last threaded edit in ts order (even when it was received non-last) marks a thread as read",
() => {},
);
test.fixme(
"A receipt for non-last threaded edit in ts order (even when it was received last) leaves thread unread",
() => {},
);
test.fixme(
"A receipt for last threaded reaction in ts order (even when it was received non-last) marks a thread as read",
() => {},
);
test.fixme(
"A receipt for non-last threaded reaction in ts order (even when it was received last) leaves thread unread",
() => {},
);
test.fixme("A receipt for last threaded event in ts order (even when it was received non-last) marks a thread as read", () => {});
test.fixme("A receipt for non-last threaded event in ts order (even when it was received last) leaves thread unread", () => {});
test.fixme("A receipt for last threaded edit in ts order (even when it was received non-last) marks a thread as read", () => {});
test.fixme("A receipt for non-last threaded edit in ts order (even when it was received last) leaves thread unread", () => {});
test.fixme("A receipt for last threaded reaction in ts order (even when it was received non-last) marks a thread as read", () => {});
test.fixme("A receipt for non-last threaded reaction in ts order (even when it was received last) leaves thread unread", () => {});
});
test.describe("thread roots", () => {
test.fixme(
"A receipt for last reaction to thread root in sync order (even when ts makes it last) marks room as read",
() => {},
);
test.fixme(
"A receipt for non-last reaction to thread root in sync order (even when ts makes it last) leaves room unread",
() => {},
);
test.fixme(
"A receipt for last edit to thread root in sync order (even when ts makes it last) marks room as read",
() => {},
);
test.fixme(
"A receipt for non-last edit to thread root in sync order (even when ts makes it last) leaves room unread",
() => {},
);
test.fixme("A receipt for last reaction to thread root in sync order (even when ts makes it last) marks room as read", () => {});
test.fixme("A receipt for non-last reaction to thread root in sync order (even when ts makes it last) leaves room unread", () => {});
test.fixme("A receipt for last edit to thread root in sync order (even when ts makes it last) marks room as read", () => {});
test.fixme("A receipt for non-last edit to thread root in sync order (even when ts makes it last) leaves room unread", () => {});
});
});
});

View File

@@ -12,18 +12,20 @@ import { test } from ".";
test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("messages with missing referents", () => {
test.fixme(
"A message in an unknown thread is not visible and the room is read",
async ({ roomAlpha: room1, roomBeta: room2, util, msg }) => {
// Given a thread existed and the room is read
await util.goTo(room1);
await util.receiveMessages(room2, ["Root1", msg.threadedOff("Root1", "T1a")]);
test.fixme("A message in an unknown thread is not visible and the room is read", async ({
roomAlpha: room1,
roomBeta: room2,
util,
msg,
}) => {
// Given a thread existed and the room is read
await util.goTo(room1);
await util.receiveMessages(room2, ["Root1", msg.threadedOff("Root1", "T1a")]);
// When I restart, forgetting the thread root
// And I receive a message on that thread
// Then the message is invisible and the room remains read
},
);
// When I restart, forgetting the thread root
// And I receive a message on that thread
// Then the message is invisible and the room remains read
});
test.fixme("When a message's thread root appears later the thread appears and the room is unread", () => {});
test.fixme("An edit of an unknown message is not visible and the room is read", () => {});
test.fixme("When an edit's message appears later the edited version appears and the room is unread", () => {});

View File

@@ -14,14 +14,8 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("Notifications", () => {
test.describe("in the main timeline", () => {
test.fixme("A new message that mentions me shows a notification", () => {});
test.fixme(
"Reading a notifying message reduces the notification count in the room list, space and tab",
() => {},
);
test.fixme(
"Reading the last notifying message removes the notification marker from room list, space and tab",
() => {},
);
test.fixme("Reading a notifying message reduces the notification count in the room list, space and tab", () => {});
test.fixme("Reading the last notifying message removes the notification marker from room list, space and tab", () => {});
test.fixme("Editing a message to mentions me shows a notification", () => {});
test.fixme("Reading the last notifying edited message removes the notification marker", () => {});
test.fixme("Redacting a notifying message removes the notification marker", () => {});
@@ -30,18 +24,9 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => {
test.describe("in threads", () => {
test.fixme("A new threaded message that mentions me shows a notification", () => {});
test.fixme("Reading a notifying threaded message removes the notification count", () => {});
test.fixme(
"Notification count remains steady when reading threads that contain seen notifications",
() => {},
);
test.fixme(
"Notification count remains steady when paging up thread view even when threads contain seen notifications",
() => {},
);
test.fixme(
"Notification count remains steady when paging up thread view after mark as unread even if older threads contain notifications",
() => {},
);
test.fixme("Notification count remains steady when reading threads that contain seen notifications", () => {});
test.fixme("Notification count remains steady when paging up thread view even when threads contain seen notifications", () => {});
test.fixme("Notification count remains steady when paging up thread view after mark as unread even if older threads contain notifications", () => {});
test.fixme("Redacting a notifying threaded message removes the notification marker", () => {});
});
});

View File

@@ -207,7 +207,7 @@ test.describe("RightPanel", () => {
// \d represents the number of the space members
await page
.locator(".mx_RoomInfoLine_private")
.locator(".mx_RoomInfoLine")
.getByRole("button", { name: /\d member/ })
.click();
await expect(page.locator(".mx_MemberListView")).toBeVisible();

View File

@@ -264,6 +264,7 @@ test.describe("Element Call", () => {
preset: "trusted_private_chat" as Preset.TrustedPrivateChat,
invite: [bot.credentials.userId],
});
await bot.awaitRoomMembership(roomId);
await app.client.setAccountData("m.direct" as EventType.Direct, {
[bot.credentials.userId]: [roomId],
});

View File

@@ -24,7 +24,7 @@ test.describe("PSTN", () => {
await toasts.rejectToast("Notifications");
await toasts.assertNoToasts();
await expect(page.locator(".mx_RoomListSearch")).toMatchScreenshot("dialpad-trigger.png");
await expect(page.getByTestId("room-list-search")).toMatchScreenshot("dialpad-trigger.png");
await page.getByLabel("Open dial pad").click();
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("dialpad.png");
});

View File

@@ -244,24 +244,6 @@ export class ElementAppPage {
await this.page.getByRole("dialog").getByRole("button", { name: "Invite" }).click();
}
/**
* Get a locator for the tooltip associated with an element
* @param e The element with the tooltip
* @returns Locator to the tooltip
*/
public async getTooltipForElement(e: Locator): Promise<Locator> {
const [labelledById, describedById] = await Promise.all([
e.getAttribute("aria-labelledby"),
e.getAttribute("aria-describedby"),
]);
if (!labelledById && !describedById) {
throw new Error(
"Element has no aria-labelledby or aria-describedy attributes! The tooltip should have added either one of these.",
);
}
return this.page.locator(`id=${labelledById ?? describedById}`);
}
/**
* Close the notification toast
*/

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

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

View File

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

View File

@@ -79,6 +79,7 @@
@import "./structures/_SearchBox.pcss";
@import "./structures/_SpaceHierarchy.pcss";
@import "./structures/_SpacePanel.pcss";
@import "./structures/_SpacePillButton.pcss";
@import "./structures/_SpaceRoomView.pcss";
@import "./structures/_SplashPage.pcss";
@import "./structures/_TabbedView.pcss";
@@ -273,7 +274,6 @@
@import "./views/rooms/RoomListPanel/_RoomListItemView.pcss";
@import "./views/rooms/RoomListPanel/_RoomListPanel.pcss";
@import "./views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss";
@import "./views/rooms/RoomListPanel/_RoomListSearch.pcss";
@import "./views/rooms/RoomListPanel/_RoomListSecondaryFilters.pcss";
@import "./views/rooms/RoomListPanel/_RoomListSkeleton.pcss";
@import "./views/rooms/_AppsDrawer.pcss";
@@ -281,7 +281,6 @@
@import "./views/rooms/_AuxPanel.pcss";
@import "./views/rooms/_BasicMessageComposer.pcss";
@import "./views/rooms/_CallGuestLinkButton.pcss";
@import "./views/rooms/_DecryptionFailureBar.pcss";
@import "./views/rooms/_E2EIcon.pcss";
@import "./views/rooms/_E2EIconView.pcss";
@import "./views/rooms/_EditMessageComposer.pcss";
@@ -383,7 +382,6 @@
@import "./views/spaces/_SpaceBasicSettings.pcss";
@import "./views/spaces/_SpaceChildrenPicker.pcss";
@import "./views/spaces/_SpaceCreateMenu.pcss";
@import "./views/spaces/_SpacePublicShare.pcss";
@import "./views/terms/_InlineTermsAgreement.pcss";
@import "./views/toasts/_AnalyticsToast.pcss";
@import "./views/toasts/_IncomingCallToast.pcss";

View File

@@ -114,67 +114,30 @@ Please see LICENSE files in the repository root for full details.
margin-top: 12px;
}
.mx_LeftPanel_dialPadButton {
width: 32px;
height: 32px;
.mx_LeftPanel_dialPadButton,
.mx_LeftPanel_exploreButton {
width: 20px;
height: 20px;
padding: var(--cpd-space-1-5x);
border-radius: 8px;
background-color: $panel-actions;
position: relative;
margin-left: 8px;
&::before {
content: "";
position: absolute;
top: 6px;
left: 6px;
width: 20px;
height: 20px;
mask-image: url("@vector-im/compound-design-tokens/icons/dial-pad.svg");
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;
background-color: $secondary-content;
}
}
.mx_LeftPanel_exploreButton,
.mx_LeftPanel_recentsButton {
width: 32px;
height: 32px;
border-radius: 8px;
background-color: $panel-actions;
position: relative;
margin-left: 8px;
&::before {
content: "";
position: absolute;
top: 8px;
left: 8px;
width: 16px;
height: 16px;
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;
background-color: $secondary-content;
svg {
width: inherit;
height: inherit;
display: block;
color: $secondary-content;
}
&:hover {
background-color: $tertiary-content;
&::before {
background-color: $background;
svg {
color: $background;
}
}
}
.mx_LeftPanel_exploreButton::before {
mask-image: url("@vector-im/compound-design-tokens/icons/explore.svg");
}
.mx_LeftPanel_recentsButton::before {
mask-image: url("@vector-im/compound-design-tokens/icons/time.svg");
}
}
.mx_LegacyRoomListHeader:first-child {
@@ -228,8 +191,7 @@ Please see LICENSE files in the repository root for full details.
background-color: transparent;
}
.mx_LeftPanel_exploreButton,
.mx_LeftPanel_recentsButton {
.mx_LeftPanel_exploreButton {
margin-left: 0;
margin-top: 8px;
}

View File

@@ -99,34 +99,17 @@ Please see LICENSE files in the repository root for full details.
position: relative;
user-select: none;
&:nth-child(2) {
& + .mx_AccessibleButton {
border-left: 1px solid $resend-button-divider-color;
}
&::before {
content: "";
position: absolute;
svg {
left: 10px; /* inset for regular button padding */
background-color: $muted-fg-color;
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
width: 18px;
height: 18px;
top: 50%; /* text sizes are dynamic */
transform: translateY(-50%);
}
&.mx_RoomStatusBar_unsentCancelAllBtn::before {
mask-image: url("@vector-im/compound-design-tokens/icons/delete.svg");
}
&.mx_RoomStatusBar_unsentRetry {
padding-left: 34px; /* 28px from above, but +6px to account for the wider icon */
&::before {
mask-image: url("@vector-im/compound-design-tokens/icons/restart.svg");
}
vertical-align: middle;
color: $muted-fg-color;
}
}

View File

@@ -76,6 +76,8 @@ Please see LICENSE files in the repository root for full details.
flex-direction: column;
flex: 1;
min-width: 0;
container-type: size;
container-name: roomview;
.mx_RoomView_messagePanel,
.mx_RoomView_messagePanelSpinner,

View File

@@ -17,13 +17,14 @@ Please see LICENSE files in the repository root for full details.
.mx_SearchBox_closeButton {
cursor: pointer;
mask-image: url("@vector-im/compound-design-tokens/icons/close.svg");
mask-repeat: no-repeat;
mask-position: center;
mask-size: 16px;
width: 16px;
height: 16px;
width: 16px;
padding: 9px;
background-color: var(--cpd-color-icon-secondary);
svg {
height: inherit;
width: inherit;
color: var(--cpd-color-icon-secondary);
}
}
}

View File

@@ -44,29 +44,23 @@ Please see LICENSE files in the repository root for full details.
top: 19px; /* v-align with avatar */
right: -8px;
&::before {
content: "";
position: absolute;
width: inherit;
svg {
height: inherit;
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;
background-color: $background;
mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
transform: rotate(270deg);
width: inherit;
display: inline-block;
color: $background;
/* Slight alignment tweak to center the asset */
margin-left: 1px;
}
&:not(.expanded) {
opacity: 0;
&::before {
mask-position: center 1px;
}
}
&.expanded::before {
transform: rotate(90deg);
&.expanded svg {
transform: rotate(180deg);
/* Slight alignment tweak to center the asset */
margin-left: -1px;
}
}
@@ -103,7 +97,6 @@ Please see LICENSE files in the repository root for full details.
& > .mx_SpaceButton > .mx_SpaceButton_toggleCollapse {
padding: 0 10px;
margin: 0 -10px;
transform: rotate(-90deg);
}
& > .mx_SpaceTreeLevel {
@@ -166,109 +159,67 @@ Please see LICENSE files in the repository root for full details.
}
.mx_SpaceButton_toggleCollapse {
width: var(--gutterSize);
padding: 10px 0;
min-width: var(--gutterSize);
height: 20px;
mask-position: center;
mask-size: 20px;
mask-repeat: no-repeat;
background-color: $tertiary-content;
mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
width: var(--gutterSize);
flex-shrink: 0;
padding: 10px 0;
svg {
width: 20px;
height: inherit;
display: inline-block;
color: $tertiary-content;
/* Re-align with parent */
margin-left: -3px;
}
}
.mx_SpaceButton_icon {
width: var(--height-topLevel);
min-width: var(--height-topLevel);
height: var(--height-topLevel);
/* Calculate height excluding padding to allow svg to inherit */
width: calc(var(--height-topLevel) - 14px);
height: calc(var(--height-topLevel) - 14px);
flex-shrink: 0;
border-radius: 8px;
position: relative;
&::before {
position: absolute;
content: "";
width: var(--height-topLevel);
height: var(--height-topLevel);
top: 0;
left: 0;
mask-position: center;
mask-repeat: no-repeat;
mask-size: 18px;
}
}
&.mx_SpaceButton_home,
&.mx_SpaceButton_favourites,
&.mx_SpaceButton_people,
&.mx_SpaceButton_orphans,
&.mx_SpaceButton_videoRooms {
.mx_SpaceButton_icon {
background-color: $panel-actions;
&::before {
background-color: $secondary-content;
}
}
}
&.mx_SpaceButton_withIcon .mx_SpaceButton_icon {
padding: 7px;
background-color: $panel-actions;
}
&.mx_SpaceButton_home .mx_SpaceButton_icon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/home-solid.svg");
}
&.mx_SpaceButton_favourites .mx_SpaceButton_icon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/favourite-solid.svg");
}
&.mx_SpaceButton_people .mx_SpaceButton_icon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/user-profile-solid.svg");
}
&.mx_SpaceButton_orphans .mx_SpaceButton_icon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/room.svg");
}
&.mx_SpaceButton_videoRooms .mx_SpaceButton_icon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/video-call-solid.svg");
svg {
width: inherit;
height: inherit;
display: block;
color: $secondary-content;
}
}
&.mx_SpaceButton_new .mx_SpaceButton_icon {
&::before {
background-color: $primary-content;
mask-image: url("@vector-im/compound-design-tokens/icons/plus.svg");
background-color: unset;
svg {
color: $primary-content;
transition: all 0.2s ease-in-out; /* TODO transition */
}
}
&.mx_SpaceButton_newCancel .mx_SpaceButton_icon::before {
&.mx_SpaceButton_newCancel .mx_SpaceButton_icon svg {
transform: rotate(45deg);
}
.mx_SpaceButton_menuButton {
width: 20px;
min-width: 20px; /* yay flex */
height: 20px;
width: 16px;
height: 16px;
padding: var(--cpd-space-0-5x);
flex-shrink: 0;
margin-top: auto;
margin-bottom: auto;
display: none;
position: absolute;
right: 4px;
&::before {
top: 3px;
left: 2px;
content: "";
width: 16px;
height: 16px;
position: absolute;
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;
mask-image: url("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg");
background: $primary-content;
svg {
width: inherit;
height: inherit;
display: block;
color: $primary-content;
}
}
}
@@ -341,18 +292,6 @@ Please see LICENSE files in the repository root for full details.
padding: 0 0 16px 0;
scrollbar-gutter: stable;
& > .mx_SpaceButton {
height: var(--height-topLevel);
&.mx_SpaceButton_active::before {
height: var(--height-topLevel);
}
}
& > ul {
padding-left: 0;
}
&.mx_IndicatorScrollbar_topOverflow {
mask-image: linear-gradient(to bottom, transparent, black 16px);
}

View File

@@ -0,0 +1,48 @@
/*
Copyright 2025 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
.mx_SpacePillButton {
position: relative;
padding: 16px 32px 16px 72px;
width: 432px;
box-sizing: border-box;
border-radius: 8px;
border: 1px solid $input-border-color;
font-size: $font-17px;
font-weight: var(--cpd-font-weight-semibold);
margin: 20px 0;
> div {
margin-top: 4px;
font-weight: normal;
font-size: $font-15px;
color: $secondary-content;
}
svg {
position: absolute;
content: "";
width: 28px;
height: 28px;
top: 50%;
transform: translateY(-50%);
left: 22px;
color: $tertiary-content;
}
&:hover {
border-color: var(--cpd-color-bg-action-primary-rest);
svg {
color: var(--cpd-color-icon-primary);
}
> span {
color: $primary-content;
}
}
}

View File

@@ -6,51 +6,6 @@ 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.
*/
@define-mixin SpacePillButton {
position: relative;
padding: 16px 32px 16px 72px;
width: 432px;
box-sizing: border-box;
border-radius: 8px;
border: 1px solid $input-border-color;
font-size: $font-17px;
font-weight: var(--cpd-font-weight-semibold);
margin: 20px 0;
> div {
margin-top: 4px;
font-weight: normal;
font-size: $font-15px;
color: $secondary-content;
}
&::before {
position: absolute;
content: "";
width: 28px;
height: 28px;
top: 50%;
transform: translateY(-50%);
left: 22px;
mask-position: center;
mask-repeat: no-repeat;
mask-size: 28px;
background-color: $tertiary-content;
}
&:hover {
border-color: var(--cpd-color-bg-action-primary-rest);
&::before {
background-color: var(--cpd-color-icon-primary);
}
> span {
color: $primary-content;
}
}
}
.mx_SpaceRoomView {
--innerWidth: 428px;
@@ -242,20 +197,6 @@ Please see LICENSE files in the repository root for full details.
}
}
.mx_SpaceRoomView_privateScope {
> .mx_AccessibleButton {
@mixin SpacePillButton;
}
.mx_SpaceRoomView_privateScope_justMeButton::before {
mask-image: url("@vector-im/compound-design-tokens/icons/user-profile-solid.svg");
}
.mx_SpaceRoomView_privateScope_meAndMyTeammatesButton::before {
mask-image: url("@vector-im/compound-design-tokens/icons/group.svg");
}
}
.mx_SpaceRoomView_inviteTeammates {
.mx_SpaceRoomView_inviteTeammates_buttons {
color: $secondary-content;

View File

@@ -32,7 +32,7 @@ Please see LICENSE files in the repository root for full details.
/* mask to dither resulting combined gradient */
url("$(res)/img/noise.png"),
/* gradient to apply different amounts of dithering to different parts of the gradient */
linear-gradient(
linear-gradient(
to bottom,
/* 10% dithering at the top */ rgb(0, 0, 0, 0.9) 20%,
/* 80% dithering at the bottom */ rgb(0, 0, 0, 0.2) 100%

View File

@@ -41,42 +41,10 @@ Please see LICENSE files in the repository root for full details.
padding: var(--cpd-space-3x);
&.mx_Toast_hasIcon {
&::before,
&::after {
content: "";
svg {
width: 22px;
height: 22px;
grid-column: 1;
grid-row: 1;
mask-size: 100%;
mask-position: center;
mask-repeat: no-repeat;
background-size: 100%;
background-repeat: no-repeat;
}
&.mx_Toast_icon_verification::after {
mask-image: url("@vector-im/compound-design-tokens/icons/lock-solid.svg");
background-color: $primary-content;
}
&.mx_Toast_icon_verification_warning {
/* white infill for the hollow svg mask */
&::before {
background-color: #ffffff;
mask-image: url("@vector-im/compound-design-tokens/icons/lock-solid.svg");
mask-size: 80%;
}
&::after {
mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
background-color: $e2e-warning-color;
}
}
&.mx_Toast_icon_key_storage::after {
mask-image: url("@vector-im/compound-design-tokens/icons/settings-solid.svg");
background-color: $primary-content;
}
.mx_Toast_title,

View File

@@ -7,12 +7,44 @@ Please see LICENSE files in the repository root for full details.
*/
.mx_UploadBar {
padding-left: 65px; /* line up with the shield area in the composer */
padding-top: 5px;
/* line up with the shield area in the composer */
padding: 5px 21px 0;
position: relative;
display: grid;
grid-template:
"icon filename cancel" auto
"progress progress progress" auto
/ min-content auto min-content;
gap: var(--cpd-space-1-5x);
& > svg {
grid-area: icon;
height: 18px;
width: 18px;
color: $muted-fg-color;
align-self: center;
}
.mx_UploadBar_filename {
grid-area: filename;
color: $muted-fg-color;
position: relative;
font-size: $font-15px;
vertical-align: middle;
}
.mx_UploadBar_cancel {
grid-area: cancel;
height: 16px;
width: 16px;
color: $muted-fg-color;
align-self: center;
}
.mx_ProgressBar {
width: calc(100% - 40px); /* cheating at a right margin */
grid-area: progress;
width: 100%;
}
}
@@ -21,39 +53,3 @@ Please see LICENSE files in the repository root for full details.
padding-left: 0;
}
}
.mx_UploadBar_filename {
color: $muted-fg-color;
position: relative;
padding-right: 38px; /* 32px for cancel icon, 6px for padding */
padding-left: 22px; /* 18px for icon, 4px for padding */
font-size: $font-15px;
vertical-align: middle;
&::before {
content: "";
height: 18px;
width: 18px;
position: absolute;
top: 0;
left: 0;
mask-repeat: no-repeat;
mask-position: center;
background-color: $muted-fg-color;
mask-image: url("@vector-im/compound-design-tokens/icons/share.svg");
}
}
.mx_UploadBar_cancel {
position: absolute;
top: 0;
right: 0;
height: 16px;
width: 16px;
margin-right: 16px; /* align over rightmost button in composer */
margin-top: 5px;
mask-repeat: no-repeat;
mask-position: center;
background-color: $muted-fg-color;
mask-image: url("@vector-im/compound-design-tokens/icons/close.svg");
}

View File

@@ -119,8 +119,4 @@ Please see LICENSE files in the repository root for full details.
.mx_IconizedContextMenu_icon svg {
color: $icon-button-color;
}
.mx_UserMenu_iconMessage::before {
mask-image: url("$(res)/img/element-icons/feedback.svg");
}
}

View File

@@ -11,17 +11,16 @@ Please see LICENSE files in the repository root for full details.
align-items: center;
}
.mx_CompleteSecurity_headerIcon {
.mx_E2EIcon.mx_CompleteSecurity_headerIcon {
width: 24px;
height: 24px;
margin-right: 4px;
position: relative;
display: inline-block;
}
.mx_CompleteSecurity_heroIcon {
.mx_E2EIcon.mx_CompleteSecurity_heroIcon {
width: 128px;
height: 128px;
position: relative;
margin: 0 auto;
}

View File

@@ -32,27 +32,20 @@ Please see LICENSE files in the repository root for full details.
}
.mx_ConfirmSpaceUserActionDialog_warning {
position: relative;
border-radius: 8px;
padding: 12px 8px 12px 42px;
padding: 12px 8px;
background-color: $header-panel-bg-color;
font-size: $font-12px;
line-height: $font-15px;
color: $secondary-content;
&::before {
content: "";
position: absolute;
left: 10px;
top: calc(50% - 8px); /* vertical centering */
svg {
height: 16px;
width: 16px;
background-color: $secondary-content;
mask-repeat: no-repeat;
mask-size: contain;
mask-image: url("@vector-im/compound-design-tokens/icons/info-solid.svg");
mask-position: center;
vertical-align: -4px;
margin-right: var(--cpd-space-1-5x);
color: $secondary-content;
}
}
}

View File

@@ -55,33 +55,17 @@ Please see LICENSE files in the repository root for full details.
text-decoration: underline;
}
&::before,
&::after {
content: "";
& > svg {
position: absolute;
width: 40px;
height: 40px;
left: 16px;
top: 12px;
}
&::before {
padding: var(--cpd-space-2x);
width: 24px;
height: 24px;
background-color: $icon-button-color;
color: $avatar-initial-color;
border-radius: 8px;
}
&::after {
background: $avatar-initial-color; /* TODO */
mask-position: center;
mask-size: 24px;
mask-repeat: no-repeat;
}
}
.mx_FeedbackDialog_reportBug {
&::after {
mask-image: url("$(res)/img/feather-customised/bug.svg");
}
}
.mx_FeedbackDialog_rateApp {
@@ -125,9 +109,5 @@ Please see LICENSE files in the repository root for full details.
font-size: 24px;
border-color: var(--cpd-color-bg-action-primary-rest);
}
&::after {
mask-image: url("$(res)/img/element-icons/feedback.svg");
}
}
}

View File

@@ -40,8 +40,7 @@ Please see LICENSE files in the repository root for full details.
/* that our preview is unencrypted, which doesn't actually matter */
/* We also hide download links to not encourage users to try interacting */
.mx_EventTile_msgOption,
.mx_EventTile_e2eIcon_unencrypted,
.mx_EventTile_e2eIcon_warning,
.mx_EventTile_e2eIcon,
.mx_MFileBody_download {
display: none;
}

View File

@@ -28,9 +28,9 @@ Please see LICENSE files in the repository root for full details.
}
.mx_InviteDialog_goButton {
min-width: 48px;
min-width: 86px;
margin-inline-start: 10px;
height: 25px;
height: 41px;
line-height: $font-25px;
}
}
@@ -223,14 +223,6 @@ Please see LICENSE files in the repository root for full details.
margin-inline-start: auto;
}
.mx_InviteDialog_userDirectoryIcon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/user-profile-solid.svg");
}
.mx_InviteDialog_dialPadIcon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/dial-pad.svg");
}
.mx_InviteDialog_tile {
cursor: pointer;
display: grid;

View File

@@ -18,19 +18,14 @@ Please see LICENSE files in the repository root for full details.
min-height: 32px;
> div {
padding-left: 30px;
position: relative;
padding-left: var(--cpd-space-1-5x);
&::before {
content: "";
position: absolute;
height: 16px;
svg {
width: 16px;
left: 6px;
top: 8px;
mask-repeat: no-repeat;
mask-position: center;
background-color: $secondary-content;
height: 16px;
color: $secondary-content;
vertical-align: -2px;
margin-right: var(--cpd-space-1-5x);
}
&.mx_JoinRuleDropdown_knock::before {
@@ -39,22 +34,6 @@ Please see LICENSE files in the repository root for full details.
}
}
.mx_JoinRuleDropdown_invite::before {
box-sizing: border-box;
mask-image: url("@vector-im/compound-design-tokens/icons/lock-solid.svg");
mask-size: contain;
padding: 1px;
}
.mx_JoinRuleDropdown_public::before {
mask-image: url("@vector-im/compound-design-tokens/icons/public.svg");
}
.mx_JoinRuleDropdown_restricted::before {
mask-image: url("@vector-im/compound-design-tokens/icons/group.svg");
mask-size: contain;
}
.mx_JoinRuleDropdown_icon {
color: $secondary-content;
position: absolute;

View File

@@ -25,28 +25,21 @@ Please see LICENSE files in the repository root for full details.
overflow-y: auto;
.mx_LeaveSpaceDialog_section_warning {
position: relative;
border-radius: 8px;
margin: 12px 0 0;
padding: 12px 8px 12px 42px;
padding: 12px 8px;
background-color: $header-panel-bg-color;
font-size: $font-12px;
line-height: $font-15px;
color: $secondary-content;
&::before {
content: "";
position: absolute;
left: 10px;
top: calc(50% - 8px); /* vertical centering */
svg {
height: 16px;
width: 16px;
background-color: $secondary-content;
mask-repeat: no-repeat;
mask-size: contain;
mask-image: url("@vector-im/compound-design-tokens/icons/info-solid.svg");
mask-position: center;
color: $secondary-content;
vertical-align: middle;
margin: 0 var(--cpd-space-1x);
}
}

View File

@@ -87,25 +87,19 @@ Please see LICENSE files in the repository root for full details.
position: relative;
border-radius: 8px;
margin: 12px 0;
padding: 8px 8px 8px 42px;
padding: 8px;
background-color: $header-panel-bg-color;
font-size: $font-12px;
line-height: $font-15px;
color: $secondary-content;
&::before {
content: "";
position: absolute;
left: 10px;
top: calc(50% - 8px); /* vertical centering */
svg {
height: 16px;
width: 16px;
background-color: $secondary-content;
mask-repeat: no-repeat;
mask-size: contain;
mask-image: url("@vector-im/compound-design-tokens/icons/info-solid.svg");
mask-position: center;
color: $secondary-content;
vertical-align: middle;
margin: 0 var(--cpd-space-1x);
}
}

View File

@@ -5,46 +5,6 @@ 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.
*/
/* ICONS */
/* ========================================================== */
.mx_RoomSettingsDialog_settingsIcon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/settings-solid.svg");
}
.mx_RoomSettingsDialog_voiceIcon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/voice-call-solid.svg");
}
.mx_RoomSettingsDialog_securityIcon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/lock-solid.svg");
}
.mx_RoomSettingsDialog_rolesIcon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/admin.svg");
}
.mx_RoomSettingsDialog_notificationsIcon::before {
mask-image: url("$(res)/img/element-icons/notifications.svg");
}
.mx_RoomSettingsDialog_bridgesIcon::before {
/* This icon is pants, please improve :) */
mask-image: url("$(res)/img/feather-customised/bridge.svg");
}
.mx_RoomSettingsDialog_pollsIcon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/polls.svg");
}
.mx_RoomSettingsDialog_warningIcon::before {
mask-image: url("$(res)/img/element-icons/room/settings/advanced.svg");
}
.mx_RoomSettingsDialog_peopleIcon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/group.svg");
}
.mx_RoomSettingsDialog .mx_Dialog_title {
-ms-text-overflow: ellipsis;
text-overflow: ellipsis;

View File

@@ -64,7 +64,7 @@ Please see LICENSE files in the repository root for full details.
}
.mx_AccessibleButton_kind_primary {
width: calc(100% - 64px);
width: calc(100% - 26px);
margin: 0 8px;
padding: 15px 18px;
}

View File

@@ -26,7 +26,3 @@ Please see LICENSE files in the repository root for full details.
}
}
}
.mx_SpacePreferencesDialog_appearanceIcon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/visibility-on.svg");
}

View File

@@ -71,14 +71,4 @@ Please see LICENSE files in the repository root for full details.
}
}
}
.mx_TabbedView_tabLabel {
.mx_SpaceSettingsDialog_generalIcon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/settings-solid.svg");
}
.mx_SpaceSettingsDialog_visibilityIcon::before {
mask-image: url("@vector-im/compound-design-tokens/icons/visibility-on.svg");
}
}
}

View File

@@ -33,14 +33,11 @@ Please see LICENSE files in the repository root for full details.
}
.mx_AccessSecretStorageDialog_recoveryKeyFeedback {
&::before {
content: "";
svg {
display: inline-block;
vertical-align: bottom;
width: 20px;
height: 20px;
mask-repeat: no-repeat;
mask-position: center;
mask-size: 20px;
margin-inline-end: 5px;
}
@@ -48,9 +45,8 @@ Please see LICENSE files in the repository root for full details.
&.mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid {
color: $alert;
&::before {
mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
background-color: $alert;
svg {
color: $alert;
}
}
}

View File

@@ -10,20 +10,12 @@ Please see LICENSE files in the repository root for full details.
}
.mx_KeyBackupFailedDialog_title {
position: relative;
padding-left: 45px;
padding-bottom: 10px;
&::before {
mask-image: url("@vector-im/compound-design-tokens/icons/error.svg");
mask-repeat: no-repeat;
background-color: $primary-content;
content: "";
position: absolute;
top: -6px;
right: 0;
bottom: 0;
left: 0;
svg {
margin-right: var(--cpd-space-2x);
vertical-align: -2px;
color: $primary-content;
}
}

View File

@@ -37,6 +37,7 @@ Please see LICENSE files in the repository root for full details.
font: var(--cpd-font-body-md-semibold);
border: none; /* override default <button /> styles */
word-break: keep-all; /* prevent button text in Chinese/Japanese/Korean (CJK) from being collapsed */
box-sizing: border-box;
&.mx_AccessibleButton_kind_primary_sm,
&.mx_AccessibleButton_kind_danger_sm,
@@ -47,11 +48,13 @@ Please see LICENSE files in the repository root for full details.
&.mx_AccessibleButton_kind_primary_sm {
color: var(--cpd-color-text-on-solid-primary);
background-color: var(--cpd-color-bg-action-primary-rest);
border: 1px solid var(--cpd-color-bg-action-primary-rest);
}
&.mx_AccessibleButton_kind_danger_sm {
color: var(--cpd-color-text-on-solid-primary);
background-color: var(--cpd-color-bg-critical-primary);
border: 1px solid var(--cpd-color-bg-critical-primary);
}
&.mx_AccessibleButton_kind_link_sm {
@@ -60,22 +63,6 @@ Please see LICENSE files in the repository root for full details.
font-weight: var(--cpd-font-weight-semibold);
}
&.mx_AccessibleButton_kind_confirm_sm {
background-color: var(--cpd-color-bg-action-primary-rest);
&::before {
mask-image: url("@vector-im/compound-design-tokens/icons/check.svg");
}
}
&.mx_AccessibleButton_kind_cancel_sm {
background-color: var(--cpd-color-bg-critical-primary);
&::before {
mask-image: url("@vector-im/compound-design-tokens/icons/close.svg");
}
}
&.mx_AccessibleButton_kind_icon,
&.mx_AccessibleButton_kind_icon_primary,
&.mx_AccessibleButton_kind_icon_primary_outline {
@@ -111,10 +98,6 @@ Please see LICENSE files in the repository root for full details.
text-decoration: underline;
}
&.mx_AccessibleButton_kind_secondary_content {
color: $secondary-content;
}
&.mx_AccessibleButton_kind_danger {
color: var(--cpd-color-text-on-solid-primary);
background-color: var(--cpd-color-bg-critical-primary);
@@ -172,25 +155,4 @@ Please see LICENSE files in the repository root for full details.
&.mx_AccessibleButton_kind_content_inline {
display: inline;
}
&.mx_AccessibleButton_kind_confirm_sm,
&.mx_AccessibleButton_kind_cancel_sm {
padding: 0px;
width: 16px;
height: 16px;
border-radius: 100%;
position: relative;
display: block;
&::before {
content: "";
display: block;
position: absolute;
inset: 0;
background-color: #ffffff;
mask-repeat: no-repeat;
mask-position: center;
mask-size: 80%;
}
}
}

View File

@@ -39,17 +39,11 @@ Please see LICENSE files in the repository root for full details.
top: 0.15em;
background-color: $background;
&::before {
content: "";
mask-image: url($copy-button-url);
mask-position: center center;
mask-repeat: no-repeat;
mask-size: contain;
background-color: $message-action-bar-fg-color;
svg {
width: 1em;
height: 1em;
color: $message-action-bar-fg-color;
display: block;
background-repeat: no-repeat;
}
}
}

View File

@@ -12,10 +12,6 @@ Please see LICENSE files in the repository root for full details.
.mx_ExternalLink_icon {
display: inline-block;
mask-image: url("@vector-im/compound-design-tokens/icons/pop-out.svg");
background-color: currentColor;
mask-repeat: no-repeat;
mask-size: contain;
width: $font-11px;
height: $font-11px;
margin-left: 0.3rem;

View File

@@ -23,14 +23,12 @@ Please see LICENSE files in the repository root for full details.
cursor: pointer;
color: $secondary-content;
&::before {
content: "";
margin-right: 8px;
background-color: $secondary-content;
mask-image: url("@vector-im/compound-design-tokens/icons/visibility-on.svg");
svg {
width: 16px;
height: 16px;
color: $secondary-content;
display: inline-block;
width: 18px;
height: 14px;
margin-right: 8px;
}
}
}

View File

@@ -9,8 +9,7 @@ Please see LICENSE files in the repository root for full details.
.mx_EventTileBubble.mx_CreateEvent {
margin: var(--EventTileBubble_margin-block) auto;
&::before {
background-color: $header-panel-text-primary-color;
mask-image: url("@vector-im/compound-design-tokens/icons/chat-solid.svg");
svg {
color: $header-panel-text-primary-color;
}
}

View File

@@ -18,8 +18,7 @@ Please see LICENSE files in the repository root for full details.
display: grid;
grid-template-columns: 24px minmax(0, 1fr) min-content min-content;
&::before,
&::after {
svg {
position: relative;
grid-column: 1;
grid-row: 1 / 3;

View File

@@ -11,21 +11,12 @@ Please see LICENSE files in the repository root for full details.
color: $muted-fg-color;
vertical-align: middle;
padding-left: 20px;
position: relative;
&::before {
svg {
height: 14px;
width: 14px;
background-color: $muted-fg-color;
mask-image: url("@vector-im/compound-design-tokens/icons/visibility-off.svg");
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
content: "";
position: absolute;
top: 1px;
left: 0;
display: inline-block;
margin-right: var(--cpd-space-1-5x);
color: $muted-fg-color;
vertical-align: -2px;
}
}

View File

@@ -34,25 +34,17 @@ Please see LICENSE files in the repository root for full details.
background-color: $system;
border-radius: 20px;
display: inline-block;
width: 32px;
height: 32px;
position: relative;
width: 16px;
height: 16px;
padding: var(--cpd-space-2x);
vertical-align: middle;
margin-right: 12px;
&::before {
content: "";
mask-repeat: no-repeat;
mask-position: center;
mask-size: cover;
mask-image: url("@vector-im/compound-design-tokens/icons/attachment.svg");
background-color: $secondary-content;
width: 16px;
height: 16px;
position: absolute;
top: 8px;
left: 8px;
svg {
color: $secondary-content;
width: inherit;
height: inherit;
display: block;
}
}

View File

@@ -7,8 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
.mx_EventTileBubble.mx_MJitsiWidgetEvent {
&::before {
background-color: $header-panel-text-primary-color; /* XXX: Variable abuse */
mask-image: url("@vector-im/compound-design-tokens/icons/video-call-solid.svg");
svg {
color: $header-panel-text-primary-color; /* XXX: Variable abuse */
}
}

View File

@@ -9,25 +9,19 @@ Please see LICENSE files in the repository root for full details.
color: var(--cpd-color-text-primary);
.mx_ReactionsRow_addReactionButton {
position: relative;
display: inline-block;
visibility: hidden; /* show on hover of the .mx_EventTile */
width: 24px;
height: 24px;
width: 16px;
height: 16px;
padding: var(--cpd-space-1x);
vertical-align: middle;
margin-left: 4px;
margin-right: 4px;
&::before {
content: "";
position: absolute;
height: 100%;
width: 100%;
mask-size: 16px;
mask-repeat: no-repeat;
mask-position: center;
background-color: $tertiary-content;
mask-image: url("@vector-im/compound-design-tokens/icons/reaction-add.svg");
svg {
height: inherit;
width: inherit;
color: $tertiary-content;
}
&.mx_ReactionsRow_addReactionButton_active {
@@ -36,8 +30,8 @@ Please see LICENSE files in the repository root for full details.
&:hover,
&.mx_ReactionsRow_addReactionButton_active {
&::before {
background-color: $primary-content;
svg {
color: $primary-content;
}
}
}

View File

@@ -11,20 +11,11 @@ Please see LICENSE files in the repository root for full details.
color: $secondary-content;
vertical-align: middle;
padding-left: 20px;
position: relative;
&::before {
svg {
margin-right: 6px;
height: 14px;
width: 14px;
background-color: $icon-button-color;
mask-image: url("@vector-im/compound-design-tokens/icons/delete.svg");
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
content: "";
position: absolute;
top: 1px;
left: 0;
color: $icon-button-color;
vertical-align: -2px;
}
}

View File

@@ -9,28 +9,8 @@ Please see LICENSE files in the repository root for full details.
.mx_EventTileBubble.mx_cryptoEvent {
margin: var(--EventTileBubble_margin-block) auto;
/* white infill for the transparency */
&.mx_cryptoEvent_icon::before {
background-color: #ffffff;
mask-image: url("@vector-im/compound-design-tokens/icons/lock-solid.svg");
mask-repeat: no-repeat;
mask-position: center;
mask-size: 80%;
}
&.mx_cryptoEvent_icon::after {
mask-image: url("@vector-im/compound-design-tokens/icons/lock-solid.svg");
background-color: $header-panel-text-primary-color;
}
&.mx_cryptoEvent_icon_verified::after {
mask-image: url("@vector-im/compound-design-tokens/icons/shield.svg");
background-color: $accent;
}
&.mx_cryptoEvent_icon_warning::after {
mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
background-color: $e2e-warning-color;
&.mx_cryptoEvent_icon svg {
color: $header-panel-text-primary-color;
}
.mx_cryptoEvent_state,

View File

@@ -61,24 +61,19 @@ Please see LICENSE files in the repository root for full details.
.mx_BaseCard_header_title_button--option {
position: relative;
width: var(--BaseCard_header-button-size);
height: var(--BaseCard_header-button-size);
width: calc(var(--BaseCard_header-button-size) - 4px);
height: calc(var(--BaseCard_header-button-size) - 4px);
padding: 2px;
&::after {
content: "";
position: absolute;
inset-block-start: 0;
inset-inline-start: 0;
height: 100%;
width: 100%;
mask-repeat: no-repeat;
mask-position: center;
mask-image: url("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg");
background-color: $secondary-content;
svg {
width: inherit;
height: inherit;
display: block;
color: $secondary-content;
}
&:hover::after {
background-color: $primary-content;
&:hover svg {
color: $primary-content;
}
}
}

View File

@@ -47,16 +47,12 @@ Please see LICENSE files in the repository root for full details.
background: $quinary-content;
}
&::before {
svg {
margin-left: 2px;
content: "";
width: 20px;
height: 20px;
background: currentColor;
mask-image: url("@vector-im/compound-design-tokens/icons/chevron-down.svg");
mask-size: 100%;
mask-repeat: no-repeat;
float: right;
width: 20px;
display: inline-block;
vertical-align: bottom;
}
}
}
@@ -181,25 +177,24 @@ Please see LICENSE files in the repository root for full details.
}
&[aria-checked="true"] {
:first-child {
span:first-child {
margin-left: -20px;
}
:first-child::before {
content: "";
svg {
width: 12px;
height: 12px;
margin-right: 8px;
mask-image: url("@vector-im/compound-design-tokens/icons/check.svg");
mask-size: 100%;
mask-repeat: no-repeat;
background-color: $primary-content;
color: $primary-content;
display: inline-block;
vertical-align: middle;
position: absolute;
top: 14px;
left: 10px;
}
}
:last-child {
span:last-child {
color: $secondary-content;
}
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
.mx_RoomListSearch {
/* From figma, this should be aligned with the room header */
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);
.mx_RoomListSearch_search {
/* The search button should take all the remaining space */
flex: 1;
font: var(--cpd-font-body-md-regular);
color: var(--cpd-color-text-secondary);
min-width: 0;
svg {
fill: var(--cpd-color-icon-secondary);
}
span {
flex: 1;
kbd {
font-family: inherit;
}
/* Shrink and truncate the search text */
white-space: nowrap;
overflow: hidden;
.mx_RoomListSearch_search_text {
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: start;
}
}
}
}

View File

@@ -65,7 +65,12 @@
margin: 12px;
height: 100%;
overflow-y: scroll;
max-height: 35vh;
}
@container roomview (height >= 0px) {
.mx_Autocomplete_Completion_container_pill {
max-height: 40cqh;
}
}
.mx_Autocomplete_Completion_container_truncate {

View File

@@ -1,79 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
.mx_DecryptionFailureBar {
--gap-row: $spacing-8;
--gap-column: $spacing-12;
--gap: var(--gap-row) var(--gap-column);
--size-icon: 24px;
background-color: $system;
padding: $spacing-12;
margin-inline: $spacing-16;
border-radius: 4px;
&.mx_DecryptionFailureBar--withEnd {
display: flex;
flex-flow: wrap;
align-items: flex-start;
justify-content: space-between;
row-gap: calc(var(--gap-row) + $spacing-4); /* Increase spacing between the message and the buttons */
.mx_DecryptionFailureBar_end {
display: flex;
flex-wrap: wrap; /* Let the buttons wrapped on a narrow column */
gap: var(--buttons-dialog-gap-row) var(--buttons-dialog-gap-column);
margin-inline-start: calc(var(--size-icon) + var(--gap-column)); /* Align the button(s) and the message */
}
}
.mx_DecryptionFailureBar_start {
display: grid;
gap: var(--gap);
grid-template-areas:
"status headline"
". message";
grid-template-columns: var(--size-icon) auto;
.mx_DecryptionFailureBar_start_status {
grid-area: status;
display: flex;
align-items: center;
gap: var(--gap);
.mx_Spinner {
height: unset; /* Unset height: 100% */
}
.mx_DecryptionFailureBar_start_status_icon {
min-width: var(--size-icon);
height: var(--size-icon);
mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
background-color: $e2e-warning-color;
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
}
}
.mx_DecryptionFailureBar_start_headline {
grid-area: headline;
font-weight: var(--cpd-font-weight-semibold);
font-size: $font-16px;
align-self: center;
}
.mx_DecryptionFailureBar_start_message {
grid-area: message;
color: $secondary-content;
}
}
}

View File

@@ -21,22 +21,3 @@ Please see LICENSE files in the repository root for full details.
.mx_E2EIcon.mx_E2EIcon_inline {
display: inline-block;
}
.mx_E2EIcon_warning {
color: $e2e-warning-color;
}
.mx_E2EIcon_normal {
color: var(--cpd-color-icon-tertiary);
}
.mx_E2EIcon_verified,
.mx_E2EIcon_warning {
.mx_E2EIcon_normal::after {
background-color: white;
}
}
.mx_E2EIcon_verified {
color: $e2e-verified-color;
}

View File

@@ -830,37 +830,11 @@ $left-gutter: 64px;
width: 14px;
height: 14px;
display: block;
background-repeat: no-repeat;
background-size: contain;
&::before,
&::after {
content: "";
svg {
height: inherit;
width: inherit;
display: block;
position: absolute;
inset: 0;
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
}
&::before {
mask-size: 80%;
}
&.mx_EventTile_e2eIcon_warning::after {
mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
background-color: $e2e-warning-color; /* red */
}
&.mx_EventTile_e2eIcon_normal::after {
mask-image: url("@vector-im/compound-design-tokens/icons/lock-solid.svg");
background-color: var(--cpd-color-icon-tertiary); /* grey */
}
&.mx_EventTile_e2eIcon_decryption_failure::after {
mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg");
background-color: var(--cpd-color-icon-tertiary);
}
}
@@ -918,30 +892,21 @@ $left-gutter: 64px;
position: absolute;
top: $spacing-8;
right: $spacing-8;
width: 19px;
height: 19px;
width: 16px;
height: 16px;
visibility: hidden;
background-color: $message-action-bar-fg-color;
&.mx_EventTile_buttonBottom {
top: 33px;
}
&.mx_EventTile_collapseButton,
&.mx_EventTile_expandButton {
mask-size: 75%;
svg {
width: inherit;
height: inherit;
display: block;
color: $message-action-bar-fg-color;
}
}
.mx_EventTile_copyButton {
height: 17px;
mask-image: url($copy-button-url);
mask-position: center center;
mask-repeat: no-repeat;
mask-size: contain;
right: 9px;
width: 17px;
}
}
}
@@ -962,20 +927,6 @@ $left-gutter: 64px;
cursor: pointer;
}
.mx_EventTile_collapseButton,
.mx_EventTile_expandButton {
mask-position: center;
mask-repeat: no-repeat;
}
.mx_EventTile_collapseButton {
mask-image: url("@vector-im/compound-design-tokens/icons/collapse.svg");
}
.mx_EventTile_expandButton {
mask-image: url("@vector-im/compound-design-tokens/icons/expand.svg");
}
.mx_EventTile_tileError {
color: red;
text-align: center;

View File

@@ -9,8 +9,7 @@ Please see LICENSE files in the repository root for full details.
.mx_EventTileBubble.mx_HistoryTile {
margin: var(--EventTileBubble_margin-block) auto;
&::before {
background-color: $header-panel-text-primary-color;
mask-image: url("@vector-im/compound-design-tokens/icons/visibility-off.svg");
svg {
color: $header-panel-text-primary-color;
}
}

View File

@@ -29,16 +29,11 @@ Please see LICENSE files in the repository root for full details.
font-weight: 400;
opacity: 0.4;
.mx_ReplyPreview_header_cancel {
background-color: $primary-content;
mask-image: url("@vector-im/compound-design-tokens/icons/close.svg");
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
.mx_ReplyPreview_header_cancel svg {
color: $primary-content;
width: 20px;
height: 20px;
min-width: 20px;
min-height: 20px;
flex-shrink: 0;
}
}
}

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