Compare commits
54 Commits
hs/safety-
...
t3chguy/cs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20be92ab7e | ||
|
|
1afa202ef5 | ||
|
|
7e3a6d9c42 | ||
|
|
9d3cba1621 | ||
|
|
e6d572c413 | ||
|
|
ced4456c16 | ||
|
|
654a9eeb3a | ||
|
|
dc0763f58a | ||
|
|
19d8798dc0 | ||
|
|
d9f6355693 | ||
|
|
4613c44f47 | ||
|
|
773662e018 | ||
|
|
0961e6f6e4 | ||
|
|
5c2d89a0d7 | ||
|
|
40120cc226 | ||
|
|
8fef5c5ec6 | ||
|
|
9864294575 | ||
|
|
d1a0875d7f | ||
|
|
523fba271f | ||
|
|
99690f6314 | ||
|
|
58b75e0fe3 | ||
|
|
ed59a53734 | ||
|
|
b243c94093 | ||
|
|
7cd9ec4fbd | ||
|
|
9fa23d3c19 | ||
|
|
05c64bd6bc | ||
|
|
040c348700 | ||
|
|
c4d8c3e85f | ||
|
|
82390d6a23 | ||
|
|
2c2b607d3c | ||
|
|
60e574f37d | ||
|
|
d2ba05a3d8 | ||
|
|
5c28ed6738 | ||
|
|
1808a16ed3 | ||
|
|
bff234749f | ||
|
|
0733bebc38 | ||
|
|
9d9782f62b | ||
|
|
0cfaeaa3a7 | ||
|
|
4a3cf3e69d | ||
|
|
c7134e8532 | ||
|
|
1d3421417f | ||
|
|
ef63661cb0 | ||
|
|
e29da89826 | ||
|
|
d2727754e3 | ||
|
|
179cf0f8e1 | ||
|
|
de74816dd8 | ||
|
|
7b024f956d | ||
|
|
362e34513d | ||
|
|
5b900ab6e2 | ||
|
|
23fbe9cef6 | ||
|
|
cd71c109d3 | ||
|
|
a28eabf73b | ||
|
|
dbe8ad0529 | ||
|
|
b446506aee |
@@ -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",
|
||||
|
||||
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
@@ -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";
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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",
|
||||
};
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -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";
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 ({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
},
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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", () => {});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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", () => {});
|
||||
|
||||
@@ -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", () => {});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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],
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 441 B |
|
After Width: | Height: | Size: 393 B |
|
After Width: | Height: | Size: 393 B |
|
After Width: | Height: | Size: 372 B |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
48
res/css/structures/_SpacePillButton.pcss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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%
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||