Move EventTileBubble to shared components (#31911)
* Move EventTileBubble to shared components as is * Added documentation and updated stories and unit tests * Move 'global' element web css to _common.pcss * Adding playwright snapshots * Updated comments * Added legacy mx_MessageTimestamp class and updated snapshots * Regenerate snapshots with correct hash * Changes to css and removed timestamp from properties after review. * Update screenshot for room-list and fix flaky CI playwright test. * Blur the play button before matching screenshots * Changed to button focused instead of blur for consistancy * Stabilize play button appearance in CI (disabled due to decoding) * Force play button appearance in CI (disabled due to decoding) * Add comments on playwright test changes. Change from React.RefObject<any> to Ref<HTMLDivElement> in EncryptionEvent.tsx * Update playwright/e2e/composer/CIDER.spec.ts Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> * Update playwright/e2e/composer/CIDER.spec.ts Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> * Update playwright/e2e/crypto/toasts.spec.ts Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --------- Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
@@ -56,7 +56,6 @@ module.exports = {
|
||||
{ from: "res/css/views/rooms/_EditMessageComposer.pcss", type: "css" },
|
||||
{ from: "res/css/views/right_panel/_BaseCard.pcss", type: "css" },
|
||||
{ from: "res/css/views/messages/_MessageTimestamp.pcss", type: "css" },
|
||||
{ from: "res/css/views/messages/_EventTileBubble.pcss", type: "css" },
|
||||
{ from: "res/css/views/messages/_MessageActionBar.pcss", type: "css" },
|
||||
{ from: "res/css/views/voip/LegacyCallView/_LegacyCallViewButtons.pcss", type: "css" },
|
||||
{ from: "res/css/views/elements/_ToggleSwitch.pcss", type: "css" },
|
||||
|
||||
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2026 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.
|
||||
*/
|
||||
|
||||
.container {
|
||||
background-color: var(--cpd-color-bg-subtle-secondary);
|
||||
padding: var(--cpd-space-3x);
|
||||
border-radius: 8px;
|
||||
/* Reserve space for external timestamps, but also cap the width */
|
||||
/* Legacy variable: --MessageTimestamp-width: 46px; /* 8 + 30 (avatar) + 8 */
|
||||
/* max-width: min(calc(100% - 2 * var(--MessageTimestamp-width)), 600px); */
|
||||
max-width: min(calc(100% - 2 * 46px), 600px);
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
grid-template-columns: 24px minmax(0, 1fr) min-content min-content;
|
||||
|
||||
svg {
|
||||
position: relative;
|
||||
grid-column: 1;
|
||||
grid-row: 1 / 3;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
content: "";
|
||||
inset: 0;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
margin-top: var(--cpd-space-1x);
|
||||
}
|
||||
|
||||
.title,
|
||||
.subtitle {
|
||||
grid-column: 2;
|
||||
overflow-wrap: break-word;
|
||||
min-inline-size: 50px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: var(--cpd-font-weight-semibold);
|
||||
font-size: var(--cpd-font-size-body-md);
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: var(--cpd-font-size-body-sm);
|
||||
grid-row: 2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { LockSolidIcon, ErrorSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import type { Meta, StoryFn } from "@storybook/react-vite";
|
||||
import { EventTileBubble } from "./EventTileBubble";
|
||||
|
||||
export default {
|
||||
title: "Event/EventTileBubble",
|
||||
component: EventTileBubble,
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
icon: <ErrorSolidIcon />,
|
||||
title: "Title goes here",
|
||||
subtitle: "Subtitle goes here",
|
||||
className: "custom-class",
|
||||
},
|
||||
} as Meta<typeof EventTileBubble>;
|
||||
|
||||
const Template: StoryFn<typeof EventTileBubble> = (args) => <EventTileBubble {...args} />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
export const HasLockSolidIcon = Template.bind({});
|
||||
HasLockSolidIcon.args = {
|
||||
className: undefined,
|
||||
icon: <LockSolidIcon />,
|
||||
children: undefined,
|
||||
};
|
||||
|
||||
export const HasChildren = Template.bind({});
|
||||
HasChildren.args = {
|
||||
className: undefined,
|
||||
children: <div>children</div>,
|
||||
};
|
||||
|
||||
export const IsCryptoEventBubble = Template.bind({});
|
||||
IsCryptoEventBubble.args = {
|
||||
className: undefined,
|
||||
icon: <LockSolidIcon />,
|
||||
title: "Encryption enabled",
|
||||
subtitle: "Messages here are end-to-end encrypted. Verify XYZ in their profile - tap on their profile picture.",
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { render } from "@test-utils";
|
||||
import { composeStories } from "@storybook/react-vite";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import React from "react";
|
||||
|
||||
import * as stories from "./EventTileBubble.stories.tsx";
|
||||
|
||||
const { Default, HasLockSolidIcon, HasChildren, IsCryptoEventBubble } = composeStories(stories);
|
||||
|
||||
describe("EventTileBubble", () => {
|
||||
it("renders the event tile bubble", () => {
|
||||
const { container } = render(<Default />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the event tile bubble with icon", () => {
|
||||
const { container } = render(<HasLockSolidIcon />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the event tile bubble with children", () => {
|
||||
const { container } = render(<HasChildren />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the event tile bubble as crypto event bubble", () => {
|
||||
const { container } = render(<IsCryptoEventBubble />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX, type ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import styles from "./EventTileBubble.module.css";
|
||||
|
||||
export interface EventTileBubbleProps {
|
||||
/**
|
||||
* Icon rendered at the start of the bubble.
|
||||
*/
|
||||
icon: JSX.Element;
|
||||
/**
|
||||
* Main title text for the bubble.
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* Optional subtitle rendered beneath the title.
|
||||
*/
|
||||
subtitle?: ReactNode;
|
||||
/**
|
||||
* Optional extra class name for the container.
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* Optional children rendered between subtitle and timestamp.
|
||||
*/
|
||||
children?: JSX.Element;
|
||||
/**
|
||||
* Forwarded ref for the container element.
|
||||
*/
|
||||
ref?: React.RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
/**
|
||||
* EventTileBubble renders a compact event tile with an icon, title, and optional subtitle/content.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <EventTileBubble icon={<Icon />} title="Room created" />
|
||||
* ```
|
||||
*/
|
||||
export function EventTileBubble({
|
||||
icon,
|
||||
title,
|
||||
subtitle,
|
||||
className,
|
||||
children,
|
||||
ref,
|
||||
}: EventTileBubbleProps): JSX.Element {
|
||||
return (
|
||||
<div className={classNames(styles.container, className)} ref={ref}>
|
||||
{icon}
|
||||
<div className={styles.title}>{title}</div>
|
||||
{subtitle && <div className={styles.subtitle}>{subtitle}</div>}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`EventTileBubble > renders the event tile bubble 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="container custom-class"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 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 22"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="title"
|
||||
>
|
||||
Title goes here
|
||||
</div>
|
||||
<div
|
||||
class="subtitle"
|
||||
>
|
||||
Subtitle goes here
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`EventTileBubble > renders the event tile bubble as crypto event bubble 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="container"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 22q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 20V10q0-.825.588-1.412A1.93 1.93 0 0 1 6 8h1V6q0-2.075 1.463-3.537Q9.926 1 12 1q2.075 0 3.537 1.463Q17 3.925 17 6v2h1q.824 0 1.413.588Q20 9.175 20 10v10q0 .824-.587 1.413A1.93 1.93 0 0 1 18 22zM9 8h6V6q0-1.25-.875-2.125A2.9 2.9 0 0 0 12 3q-1.25 0-2.125.875A2.9 2.9 0 0 0 9 6z"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="title"
|
||||
>
|
||||
Encryption enabled
|
||||
</div>
|
||||
<div
|
||||
class="subtitle"
|
||||
>
|
||||
Messages here are end-to-end encrypted. Verify XYZ in their profile - tap on their profile picture.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`EventTileBubble > renders the event tile bubble with children 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="container"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 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 22"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="title"
|
||||
>
|
||||
Title goes here
|
||||
</div>
|
||||
<div
|
||||
class="subtitle"
|
||||
>
|
||||
Subtitle goes here
|
||||
</div>
|
||||
<div>
|
||||
children
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`EventTileBubble > renders the event tile bubble with icon 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="container"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 22q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 20V10q0-.825.588-1.412A1.93 1.93 0 0 1 6 8h1V6q0-2.075 1.463-3.537Q9.926 1 12 1q2.075 0 3.537 1.463Q17 3.925 17 6v2h1q.824 0 1.413.588Q20 9.175 20 10v10q0 .824-.587 1.413A1.93 1.93 0 0 1 18 22zM9 8h6V6q0-1.25-.875-2.125A2.9 2.9 0 0 0 12 3q-1.25 0-2.125.875A2.9 2.9 0 0 0 9 6z"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="title"
|
||||
>
|
||||
Title goes here
|
||||
</div>
|
||||
<div
|
||||
class="subtitle"
|
||||
>
|
||||
Subtitle goes here
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright 2026 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.
|
||||
*/
|
||||
|
||||
export { EventTileBubble, type EventTileBubbleProps } from "./EventTileBubble";
|
||||
@@ -13,6 +13,7 @@ export * from "./audio/SeekBar";
|
||||
export * from "./avatar/AvatarWithDetails";
|
||||
export * from "./composer/Banner";
|
||||
export * from "./crypto/SasEmoji";
|
||||
export * from "./event-tiles/EventTileBubble";
|
||||
export * from "./event-tiles/TextualEventView";
|
||||
export * from "./message-body/MediaBody";
|
||||
export * from "./message-body/DecryptionFailureBodyView";
|
||||
|
||||
@@ -23,6 +23,7 @@ const clickButtonReply = async (tile: Locator) => {
|
||||
};
|
||||
|
||||
test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
test.slow();
|
||||
test.use({
|
||||
displayName: "Hanako",
|
||||
});
|
||||
@@ -100,35 +101,39 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => {
|
||||
.mx_MessageActionBar {
|
||||
display: none !important;
|
||||
}
|
||||
/* Stabilize play button appearance in CI (disabled due to decoding) */
|
||||
button[aria-label="Play"] {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
button[aria-label="Play"] svg,
|
||||
button[aria-label="Play"] path {
|
||||
fill: magenta !important;
|
||||
stroke: magenta !important;
|
||||
}
|
||||
`,
|
||||
mask: [page.getByTestId("audio-player-seek")],
|
||||
clip: undefined,
|
||||
};
|
||||
|
||||
// Take a snapshot of mx_EventTile_last on IRC layout
|
||||
await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot(
|
||||
`${detail.replaceAll(" ", "-")}-irc-layout.png`,
|
||||
screenshotOptions,
|
||||
);
|
||||
screenshotOptions.clip = await page.locator(".mx_EventTile_last").boundingBox();
|
||||
await expect(page).toMatchScreenshot(`${detail.replaceAll(" ", "-")}-irc-layout.png`, screenshotOptions);
|
||||
|
||||
// Take a snapshot on modern/group layout
|
||||
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Group);
|
||||
const groupTile = page.locator(".mx_EventTile_last[data-layout='group']");
|
||||
await groupTile.locator(".mx_MessageTimestamp").click();
|
||||
await checkPlayerVisibility(groupTile);
|
||||
await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot(
|
||||
`${detail.replaceAll(" ", "-")}-group-layout.png`,
|
||||
screenshotOptions,
|
||||
);
|
||||
screenshotOptions.clip = await page.locator(".mx_EventTile_last").boundingBox();
|
||||
await expect(page).toMatchScreenshot(`${detail.replaceAll(" ", "-")}-group-layout.png`, screenshotOptions);
|
||||
|
||||
// Take a snapshot on bubble layout
|
||||
await app.settings.setValue("layout", null, SettingLevel.DEVICE, Layout.Bubble);
|
||||
const bubbleTile = page.locator(".mx_EventTile_last[data-layout='bubble']");
|
||||
await bubbleTile.locator(".mx_MessageTimestamp").click();
|
||||
await checkPlayerVisibility(bubbleTile);
|
||||
await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot(
|
||||
`${detail.replaceAll(" ", "-")}-bubble-layout.png`,
|
||||
screenshotOptions,
|
||||
);
|
||||
screenshotOptions.clip = await page.locator(".mx_EventTile_last").boundingBox();
|
||||
await expect(page).toMatchScreenshot(`${detail.replaceAll(" ", "-")}-bubble-layout.png`, screenshotOptions);
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
|
||||
@@ -80,7 +80,15 @@ test.describe("Composer", () => {
|
||||
test.use({ viewport: { width: 1280, height: 720 } });
|
||||
test("render emoji picker", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||
await app.getComposer(false).getByRole("button", { name: "Emoji" }).click();
|
||||
await expect(page.getByTestId("mx_EmojiPicker")).toMatchScreenshot("emoji-picker.png");
|
||||
// Mask the background of the screenshot to avoid failing the test just because some
|
||||
// other component have changed its rendering.
|
||||
await expect(page.getByTestId("mx_EmojiPicker")).toMatchScreenshot("emoji-picker.png", {
|
||||
css: `
|
||||
.mx_ContextualMenu_background {
|
||||
background-color: magenta !important;
|
||||
}
|
||||
`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,7 +96,15 @@ test.describe("Composer", () => {
|
||||
test.use({ viewport: { width: 1280, height: 360 } });
|
||||
test("render emoji picker", { tag: "@screenshot" }, async ({ page, app }) => {
|
||||
await app.getComposer(false).getByRole("button", { name: "Emoji" }).click();
|
||||
await expect(page.getByTestId("mx_EmojiPicker")).toMatchScreenshot("emoji-picker-small.png");
|
||||
// Mask the background of the screenshot to avoid failing the test just because some
|
||||
// other component have changed its rendering.
|
||||
await expect(page.getByTestId("mx_EmojiPicker")).toMatchScreenshot("emoji-picker-small.png", {
|
||||
css: `
|
||||
.mx_ContextualMenu_background {
|
||||
background-color: magenta !important;
|
||||
}
|
||||
`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -37,7 +37,15 @@ test.describe("Key storage out of sync toast", () => {
|
||||
// playwright only evaluates the 'first()' call initially, not subsequent times it checks, so
|
||||
// it would always be checking the same toast, even if another one is now the first.
|
||||
await expect(page.getByRole("alert")).toHaveCount(2);
|
||||
await expect(page.getByRole("alert").first()).toMatchScreenshot("key-storage-out-of-sync-toast.png");
|
||||
// Mask the background of the screenshot to avoid failing the test just because some
|
||||
// other component have changed its rendering.
|
||||
await expect(page.getByRole("alert").first()).toMatchScreenshot("key-storage-out-of-sync-toast.png", {
|
||||
css: `
|
||||
.mx_ToastContainer {
|
||||
background-color: magenta !important;
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
await page.getByRole("button", { name: "Enter recovery key" }).click();
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ test.describe("Room list", () => {
|
||||
});
|
||||
|
||||
test.describe("Room list", () => {
|
||||
test.slow();
|
||||
|
||||
test.beforeEach(async ({ page, app, user }) => {
|
||||
for (let i = 0; i < 30; i++) {
|
||||
await app.client.createRoom({ name: `room${i}` });
|
||||
|
||||
@@ -949,6 +949,10 @@ test.describe("Timeline", () => {
|
||||
await page.getByRole("textbox", { name: "Edit message" }).press("Enter");
|
||||
|
||||
const newTile = page.locator(".mx_EventTile");
|
||||
const codeBlock = newTile.locator(".mx_EventTile_pre_container");
|
||||
await expect(codeBlock).toBeVisible();
|
||||
await codeBlock.hover();
|
||||
await expect(newTile.locator(".mx_EventTile_copyButton")).toBeVisible();
|
||||
await expect(newTile).toMatchScreenshot("edited-code-block.png", {
|
||||
css: `
|
||||
.mx_MessageTimestamp {
|
||||
|
||||
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 166 KiB |
@@ -22,6 +22,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
--buttons-dialog-gap-row: $spacing-8;
|
||||
--buttons-dialog-gap-column: $spacing-8;
|
||||
--MBody-border-radius: 8px;
|
||||
--EventTileBubble_margin-block: 10px;
|
||||
|
||||
/* Expected z-indexes for dialogs:
|
||||
4000 - Default wrapper index
|
||||
|
||||
@@ -221,7 +221,6 @@
|
||||
@import "./views/messages/_CreateEvent.pcss";
|
||||
@import "./views/messages/_DateSeparator.pcss";
|
||||
@import "./views/messages/_DisambiguatedProfile.pcss";
|
||||
@import "./views/messages/_EventTileBubble.pcss";
|
||||
@import "./views/messages/_HiddenBody.pcss";
|
||||
@import "./views/messages/_HiddenMediaPlaceholder.pcss";
|
||||
@import "./views/messages/_JumpToDatePicker.pcss";
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2019, 2020 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_EventTileBubble {
|
||||
--EventTileBubble_margin-block: 10px;
|
||||
|
||||
background-color: $dark-panel-bg-color;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
/* Reserve space for external timestamps, but also cap the width */
|
||||
max-width: min(calc(100% - 2 * var(--MessageTimestamp-width)), 600px);
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
grid-template-columns: 24px minmax(0, 1fr) min-content min-content;
|
||||
|
||||
svg {
|
||||
position: relative;
|
||||
grid-column: 1;
|
||||
grid-row: 1 / 3;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
content: "";
|
||||
inset: 0;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
margin-top: $spacing-4;
|
||||
}
|
||||
|
||||
.mx_EventTileBubble_title,
|
||||
.mx_EventTileBubble_subtitle {
|
||||
grid-column: 2;
|
||||
overflow-wrap: break-word;
|
||||
min-inline-size: 50px;
|
||||
}
|
||||
|
||||
.mx_EventTileBubble_title {
|
||||
font-weight: var(--cpd-font-weight-semibold);
|
||||
font-size: $font-15px;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.mx_EventTileBubble_subtitle {
|
||||
font-size: $font-12px;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.mx_MessageTimestamp {
|
||||
grid-column: 4;
|
||||
grid-row: 1 / 3;
|
||||
align-self: center;
|
||||
margin-left: $spacing-16;
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,12 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React, { type RefObject } from "react";
|
||||
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import { EventTileBubble } from "@element-hq/web-shared-components";
|
||||
|
||||
import type ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import ErrorBoundary from "../views/elements/ErrorBoundary";
|
||||
import RoomHeader from "../views/rooms/RoomHeader/RoomHeader.tsx";
|
||||
import ScrollPanel from "./ScrollPanel";
|
||||
import EventTileBubble from "../views/messages/EventTileBubble";
|
||||
import NewRoomIntro from "../views/rooms/NewRoomIntro";
|
||||
import { UnwrappedEventTile } from "../views/rooms/EventTile";
|
||||
import { _t } from "../../languageHandler";
|
||||
@@ -45,7 +45,7 @@ export const WaitingForThirdPartyRoomView: React.FC<Props> = ({ roomView, resize
|
||||
<ScrollPanel className="mx_RoomView_messagePanel">
|
||||
<EventTileBubble
|
||||
icon={<LockSolidIcon />}
|
||||
className="mx_cryptoEvent mx_cryptoEvent_icon"
|
||||
className="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon"
|
||||
title={_t("room|waiting_for_join_title", { brand })}
|
||||
subtitle={_t("room|waiting_for_join_subtitle", { brand })}
|
||||
/>
|
||||
|
||||
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX, type Ref, type ReactNode } from "react";
|
||||
import React, { type JSX, type ReactNode } from "react";
|
||||
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { ErrorSolidIcon, LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import { EventTileBubble } from "@element-hq/web-shared-components";
|
||||
|
||||
import type { RoomEncryptionEventContent } from "matrix-js-sdk/src/types";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import EventTileBubble from "./EventTileBubble";
|
||||
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import { objectHasDiff } from "../../../utils/objects";
|
||||
@@ -23,7 +23,7 @@ import { useIsEncrypted } from "../../../hooks/useIsEncrypted.ts";
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
timestamp?: JSX.Element;
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
ref?: React.RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
|
||||
@@ -60,11 +60,12 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<LockSolidIcon />}
|
||||
className="mx_cryptoEvent mx_cryptoEvent_icon"
|
||||
className="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon"
|
||||
title={stateEncrypted ? _t("common|state_encryption_enabled") : _t("common|encryption_enabled")}
|
||||
subtitle={subtitle}
|
||||
timestamp={timestamp}
|
||||
/>
|
||||
>
|
||||
{timestamp}
|
||||
</EventTileBubble>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,23 +73,25 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<LockSolidIcon />}
|
||||
className="mx_cryptoEvent mx_cryptoEvent_icon"
|
||||
className="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon"
|
||||
title={_t("common|encryption_enabled")}
|
||||
subtitle={_t("timeline|m.room.encryption|disable_attempt")}
|
||||
timestamp={timestamp}
|
||||
/>
|
||||
>
|
||||
{timestamp}
|
||||
</EventTileBubble>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />}
|
||||
className="mx_cryptoEvent"
|
||||
className="mx_EventTileBubble mx_cryptoEvent"
|
||||
title={_t("timeline|m.room.encryption|disabled")}
|
||||
subtitle={_t("timeline|m.room.encryption|unsupported")}
|
||||
ref={ref}
|
||||
timestamp={timestamp}
|
||||
/>
|
||||
>
|
||||
{timestamp}
|
||||
</EventTileBubble>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX, type ReactNode, type Ref } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
interface IProps {
|
||||
className: string;
|
||||
icon: JSX.Element;
|
||||
title: string;
|
||||
timestamp?: JSX.Element;
|
||||
subtitle?: ReactNode;
|
||||
children?: JSX.Element;
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const EventTileBubble = ({ className, icon, title, timestamp, subtitle, children, ref }: IProps): JSX.Element => {
|
||||
return (
|
||||
<div className={classNames("mx_EventTileBubble", className)} ref={ref}>
|
||||
{icon}
|
||||
<div className="mx_EventTileBubble_title">{title}</div>
|
||||
{subtitle && <div className="mx_EventTileBubble_subtitle">{subtitle}</div>}
|
||||
{children}
|
||||
{timestamp}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventTileBubble;
|
||||
@@ -9,10 +9,10 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React, { type JSX } from "react";
|
||||
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { VideoCallSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import { EventTileBubble } from "@element-hq/web-shared-components";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import WidgetStore from "../../../stores/WidgetStore";
|
||||
import EventTileBubble from "./EventTileBubble";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
||||
|
||||
@@ -43,32 +43,35 @@ export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<VideoCallSolidIcon />}
|
||||
className="mx_MJitsiWidgetEvent"
|
||||
className="mx_EventTileBubble mx_MJitsiWidgetEvent"
|
||||
title={_t("timeline|m.widget|jitsi_ended", { senderName })}
|
||||
timestamp={this.props.timestamp}
|
||||
/>
|
||||
>
|
||||
{this.props.timestamp}
|
||||
</EventTileBubble>
|
||||
);
|
||||
} else if (prevUrl) {
|
||||
// modified
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<VideoCallSolidIcon />}
|
||||
className="mx_MJitsiWidgetEvent"
|
||||
className="mx_EventTileBubble mx_MJitsiWidgetEvent"
|
||||
title={_t("timeline|m.widget|jitsi_updated", { senderName })}
|
||||
subtitle={joinCopy}
|
||||
timestamp={this.props.timestamp}
|
||||
/>
|
||||
>
|
||||
{this.props.timestamp}
|
||||
</EventTileBubble>
|
||||
);
|
||||
} else {
|
||||
// assume added
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<VideoCallSolidIcon />}
|
||||
className="mx_MJitsiWidgetEvent"
|
||||
className="mx_EventTileBubble mx_MJitsiWidgetEvent"
|
||||
title={_t("timeline|m.widget|jitsi_started", { senderName })}
|
||||
subtitle={joinCopy}
|
||||
timestamp={this.props.timestamp}
|
||||
/>
|
||||
>
|
||||
{this.props.timestamp}
|
||||
</EventTileBubble>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React, { type JSX } from "react";
|
||||
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import { EventTileBubble } from "@element-hq/web-shared-components";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { getNameForEventRoom, userLabelForEventRoom } from "../../../utils/KeyVerificationStateObserver";
|
||||
import EventTileBubble from "./EventTileBubble";
|
||||
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
|
||||
|
||||
interface Props {
|
||||
@@ -75,12 +75,11 @@ const MKeyVerificationRequest: React.FC<Props> = ({ mxEvent, timestamp }) => {
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<LockSolidIcon />}
|
||||
className="mx_cryptoEvent mx_cryptoEvent_icon"
|
||||
className="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon"
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
timestamp={timestamp}
|
||||
>
|
||||
<></>
|
||||
{timestamp}
|
||||
</EventTileBubble>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,13 +11,13 @@ import React, { type JSX, useCallback } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { type MatrixEvent, type Room, type RoomState } from "matrix-js-sdk/src/matrix";
|
||||
import { ChatSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import { EventTileBubble } from "@element-hq/web-shared-components";
|
||||
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import EventTileBubble from "./EventTileBubble";
|
||||
import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { useRoomState } from "../../../hooks/useRoomState";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
@@ -91,26 +91,28 @@ export const RoomPredecessorTile: React.FC<IProps> = ({ mxEvent, timestamp }) =>
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<ChatSolidIcon />}
|
||||
className="mx_CreateEvent"
|
||||
className="mx_EventTileBubble mx_CreateEvent"
|
||||
title={_t("timeline|m.room.create|continuation")}
|
||||
timestamp={timestamp}
|
||||
>
|
||||
<div className="mx_EventTile_body">
|
||||
<span className="mx_EventTile_tileError">
|
||||
{!!guessedLink ? (
|
||||
<>
|
||||
{_t("timeline|m.room.create|unknown_predecessor_guess_server", {
|
||||
<>
|
||||
<div className="mx_EventTile_body">
|
||||
<span className="mx_EventTile_tileError">
|
||||
{!!guessedLink ? (
|
||||
<>
|
||||
{_t("timeline|m.room.create|unknown_predecessor_guess_server", {
|
||||
roomId: predecessor.roomId,
|
||||
})}
|
||||
<a href={guessedLink}>{guessedLink}</a>
|
||||
</>
|
||||
) : (
|
||||
_t("timeline|m.room.create|unknown_predecessor", {
|
||||
roomId: predecessor.roomId,
|
||||
})}
|
||||
<a href={guessedLink}>{guessedLink}</a>
|
||||
</>
|
||||
) : (
|
||||
_t("timeline|m.room.create|unknown_predecessor", {
|
||||
roomId: predecessor.roomId,
|
||||
})
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
})
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{timestamp}
|
||||
</>
|
||||
</EventTileBubble>
|
||||
);
|
||||
}
|
||||
@@ -131,11 +133,12 @@ export const RoomPredecessorTile: React.FC<IProps> = ({ mxEvent, timestamp }) =>
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<ChatSolidIcon />}
|
||||
className="mx_CreateEvent"
|
||||
className="mx_EventTileBubble mx_CreateEvent"
|
||||
title={_t("timeline|m.room.create|continuation")}
|
||||
subtitle={link}
|
||||
timestamp={timestamp}
|
||||
/>
|
||||
>
|
||||
{timestamp}
|
||||
</EventTileBubble>
|
||||
);
|
||||
|
||||
function createLinkWithRoom(room: Room, roomId: string, eventId?: string): string {
|
||||
|
||||
@@ -117,7 +117,7 @@ export interface IEventTileOps {
|
||||
unhideWidget(): void;
|
||||
}
|
||||
|
||||
export interface IEventTileType extends React.Component {
|
||||
export interface IEventTileType extends React.Component<HTMLDivElement> {
|
||||
getEventTileOps?(): IEventTileOps;
|
||||
getMediaHelper(): MediaEventHelper | undefined;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
||||
import React from "react";
|
||||
import { EventTimeline } from "matrix-js-sdk/src/matrix";
|
||||
import { VisibilityOffIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import { EventTileBubble } from "@element-hq/web-shared-components";
|
||||
|
||||
import EventTileBubble from "../messages/EventTileBubble";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
|
||||
|
||||
@@ -30,7 +30,7 @@ const HistoryTile: React.FC = () => {
|
||||
return (
|
||||
<EventTileBubble
|
||||
icon={<VisibilityOffIcon />}
|
||||
className="mx_HistoryTile"
|
||||
className="mx_EventTileBubble mx_HistoryTile"
|
||||
title={_t("timeline|historical_messages_unavailable")}
|
||||
subtitle={subtitle}
|
||||
/>
|
||||
|
||||
@@ -10,6 +10,7 @@ import React, { type JSX, useContext } from "react";
|
||||
import { EventType, type Room, type User, type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { ErrorSolidIcon, UserAddIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
import { EventTileBubble } from "@element-hq/web-shared-components";
|
||||
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
@@ -22,7 +23,6 @@ import { type ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPaylo
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import SpaceStore from "../../../stores/spaces/SpaceStore";
|
||||
import { showSpaceInvite } from "../../../utils/space";
|
||||
import EventTileBubble from "../messages/EventTileBubble";
|
||||
import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
|
||||
@@ -296,7 +296,7 @@ const NewRoomIntro: React.FC = () => {
|
||||
{!hasExpectedEncryptionSettings(cli, room) && (
|
||||
<EventTileBubble
|
||||
icon={<ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />}
|
||||
className="mx_cryptoEvent"
|
||||
className="mx_EventTileBubble mx_cryptoEvent"
|
||||
title={_t("room|intro|unencrypted_warning")}
|
||||
subtitle={subtitle}
|
||||
/>
|
||||
|
||||
@@ -21,7 +21,7 @@ import { TextualEventView } from "@element-hq/web-shared-components";
|
||||
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import type LegacyCallEventGrouper from "../components/structures/LegacyCallEventGrouper";
|
||||
import { type EventTileProps } from "../components/views/rooms/EventTile";
|
||||
import { type IEventTileType, type EventTileProps } from "../components/views/rooms/EventTile";
|
||||
import { TimelineRenderingType } from "../contexts/RoomContext";
|
||||
import MessageEvent from "../components/views/messages/MessageEvent";
|
||||
import LegacyCallEvent from "../components/views/messages/LegacyCallEvent";
|
||||
@@ -61,7 +61,7 @@ export interface EventTileTypeProps extends Pick<
|
||||
| "isSeeingThroughMessageHiddenForModeration"
|
||||
| "inhibitInteraction"
|
||||
> {
|
||||
ref?: React.RefObject<any>; // `any` because it's effectively impossible to convince TS of a reasonable type
|
||||
ref?: React.RefObject<IEventTileType | null>;
|
||||
maxImageHeight?: number; // pixels
|
||||
overrideBodyTypes?: Record<string, React.ComponentType<IBodyProps>>;
|
||||
overrideEventTypes?: Record<string, React.ComponentType<IBodyProps>>;
|
||||
|
||||
@@ -61,7 +61,7 @@ exports[`MessagePanel should handle lots of membership events quickly 1`] = `
|
||||
</div>
|
||||
</li>
|
||||
<div
|
||||
class="mx_EventTileBubble mx_HistoryTile"
|
||||
class="_container_sq5fu_8 mx_EventTileBubble mx_HistoryTile"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
@@ -75,7 +75,7 @@ exports[`MessagePanel should handle lots of membership events quickly 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="mx_EventTileBubble_title"
|
||||
class="_title_sq5fu_34"
|
||||
>
|
||||
You can't see earlier messages
|
||||
</div>
|
||||
|
||||
@@ -152,7 +152,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
|
||||
class="mx_NewRoomIntro"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTileBubble mx_cryptoEvent"
|
||||
class="_container_sq5fu_8 mx_EventTileBubble mx_cryptoEvent"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-critical-primary)"
|
||||
@@ -167,12 +167,12 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="mx_EventTileBubble_title"
|
||||
class="_title_sq5fu_34"
|
||||
>
|
||||
End-to-end encryption isn't enabled
|
||||
</div>
|
||||
<div
|
||||
class="mx_EventTileBubble_subtitle"
|
||||
class="_subtitle_sq5fu_35"
|
||||
>
|
||||
<span>
|
||||
|
||||
@@ -342,7 +342,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
|
||||
class="mx_NewRoomIntro"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTileBubble mx_cryptoEvent"
|
||||
class="_container_sq5fu_8 mx_EventTileBubble mx_cryptoEvent"
|
||||
>
|
||||
<svg
|
||||
color="var(--cpd-color-icon-critical-primary)"
|
||||
@@ -357,12 +357,12 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="mx_EventTileBubble_title"
|
||||
class="_title_sq5fu_34"
|
||||
>
|
||||
End-to-end encryption isn't enabled
|
||||
</div>
|
||||
<div
|
||||
class="mx_EventTileBubble_subtitle"
|
||||
class="_subtitle_sq5fu_35"
|
||||
>
|
||||
<span>
|
||||
|
||||
@@ -717,7 +717,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
|
||||
style="height: 400px;"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon"
|
||||
class="_container_sq5fu_8 mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
@@ -731,12 +731,12 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="mx_EventTileBubble_title"
|
||||
class="_title_sq5fu_34"
|
||||
>
|
||||
Encryption enabled
|
||||
</div>
|
||||
<div
|
||||
class="mx_EventTileBubble_subtitle"
|
||||
class="_subtitle_sq5fu_35"
|
||||
>
|
||||
Messages in this chat will be end-to-end encrypted.
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`<RoomPredecessorTile /> Renders as expected 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_EventTileBubble mx_CreateEvent"
|
||||
class="_container_sq5fu_8 mx_EventTileBubble mx_CreateEvent"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
@@ -17,12 +17,12 @@ exports[`<RoomPredecessorTile /> Renders as expected 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="mx_EventTileBubble_title"
|
||||
class="_title_sq5fu_34"
|
||||
>
|
||||
This room is a continuation of another conversation.
|
||||
</div>
|
||||
<div
|
||||
class="mx_EventTileBubble_subtitle"
|
||||
class="_subtitle_sq5fu_35"
|
||||
>
|
||||
<a
|
||||
href="https://matrix.to/#/old_room_id/$tombstone_event_id"
|
||||
|
||||