Collapsed URL previews in timeline (#34165)
* Fetches link previews for all links in message (instead of just the first one) * message url component to display preview for multiple urls * Revert visible behaviour back to only showing one URL preview While fetching all link previews in the text message. * Moved URL preview VM to MessageComposer * Added com.beeper.linkpreviews to messages sent with the markdown compositor * MSC 4452 implemented for the older (markdown) composer * claude told me to use logger instead of console * Previews generated with snapshot instead of vm * moved attachPreviews to a separate file * added attach URL previews to rich text editor * don't let attachUrlPreviews block clearning the composer * fixed linter errors * moved url preview behind labs feature gate * passed linters * moved lab feature checking to where attach preview is used * claude wrote some unit tests for url previewing * fixed linter errors * added feature to labs.md * fixed linter errors * fixed oxfmt error * set previews to none if all previews failed * resolved PR reviews besides the ones that requires a larger code change * moved url preview VM creation to message composer * removed delay when clearing composer url preview on message send * Collpased URL previews in timeline * CSS for collapsed URL previews * fixed typo thing? * update class for this component * remove dead code from css * minor changes to stop using globals everywhere * moved debouncing responsibility to urlpreviewVM * minor lint fixes * added comment * remove composer content from the state of urlpreview, moved it to the vm * urlpreviewwrapper depends on the vm only * added comments * edited the comment * one slow preview no longer blocks up the current batch of previews from loading * show failed and loading url previews * collapsed urlpreview styles to match the expanded preview * css formtting * composer url preview summary bar * prefer site icon * Revert "Merge branch 'url-preview-loading-indicator' into collapsed-url-previews" This reverts commit b7a1ccb0e74661458e16c128814ba1939bdf400e, reversing changes made to 1340b216407cf9af95741174819f97a144c13bb3. * updated shared-components Punit test snapshots * updated snapshots * wait for tall image to load before capturing snapshot (claude wrote this) * try remove requestanimation frame * use semantic tokens instead of specifying hard values in some places * Update packages/shared-components/src/room/timeline/event-tile/UrlPreviewGroupView/LinkPreview/LinkPreview.tsx Co-authored-by: Florian Duros <florian.duros@ormaz.fr> * changed css so previews looks less horrendous on different front sizes, particularly very small font sizes * updated unit test snapshots * regenerated playwright story screenshots --------- Co-authored-by: Florian Duros <florian.duros@ormaz.fr>
@@ -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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A Storybook `play` helper that waits for every CSS `background-image` inside the
|
||||
* rendered story to finish decoding before the visual-regression snapshot is taken.
|
||||
*
|
||||
* Components such as `LinkPreview` render their thumbnails as CSS `background-image`s
|
||||
* rather than `<img>` elements, so there is no load event for the snapshot machinery
|
||||
* to await. A larger image (e.g. the tall test image) can therefore still be decoding
|
||||
* when the screenshot is captured, producing a non-deterministic placeholder frame.
|
||||
* Decoding the images up-front populates the browser cache so the background paints
|
||||
* synchronously on the next frame.
|
||||
*/
|
||||
export async function waitForBackgroundImages(root: HTMLElement): Promise<void> {
|
||||
const urls = new Set<string>();
|
||||
for (const el of root.querySelectorAll<HTMLElement>("*")) {
|
||||
const match = /url\(["']?(.+?)["']?\)/.exec(getComputedStyle(el).backgroundImage);
|
||||
if (match) urls.add(match[1]);
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
[...urls].map(async (src) => {
|
||||
const img = new Image();
|
||||
img.src = src;
|
||||
try {
|
||||
await img.decode();
|
||||
} catch {
|
||||
// Ignore images that fail to decode; the snapshot captures whatever renders.
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 43 KiB |
@@ -9,40 +9,60 @@ button.preview {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.preview {
|
||||
display: flex;
|
||||
position: relative;
|
||||
.containerCollapsed {
|
||||
max-width: 100%;
|
||||
width: 478px;
|
||||
height: 200px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
border: 1px solid var(--cpd-color-border-interactive-secondary);
|
||||
border-radius: 12px;
|
||||
/* Get radius from cpd */
|
||||
flex-direction: row;
|
||||
color: var(--cpd-color-text-secondary);
|
||||
overflow: clip;
|
||||
gap: 8px;
|
||||
padding: var(--cpd-space-3x) var(--cpd-space-4x);
|
||||
|
||||
.playButton[data-kind="primary"] {
|
||||
padding: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin: auto;
|
||||
background: var(--cpd-color-text-on-solid-primary);
|
||||
> svg {
|
||||
margin: auto;
|
||||
border-radius: 50px;
|
||||
color: var(--cpd-color-icon-primary);
|
||||
}
|
||||
background: var(--cpd-color-bg-subtle-secondary);
|
||||
|
||||
.textContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.preview {
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
flex-shrink: 0;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.siteName span {
|
||||
line-height: 135%;
|
||||
}
|
||||
|
||||
.title,
|
||||
.siteName > span {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
line-clamp: 1;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
.containerExpanded {
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
width: 478px;
|
||||
display: flex;
|
||||
border: 1px solid var(--cpd-color-border-interactive-secondary);
|
||||
border-radius: 12px; /* Get radius from cpd */
|
||||
border-radius: 12px;
|
||||
/* Get radius from cpd */
|
||||
flex-direction: column;
|
||||
color: var(--cpd-color-gray-900);
|
||||
color: var(--cpd-color-text-secondary);
|
||||
overflow: clip;
|
||||
|
||||
background: var(--cpd-color-bg-subtle-secondary);
|
||||
@@ -67,9 +87,11 @@ button.preview {
|
||||
|
||||
.textContent {
|
||||
padding: var(--cpd-space-3x) var(--cpd-space-4x);
|
||||
|
||||
&.inline {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--cpd-space-1x);
|
||||
@@ -78,19 +100,58 @@ button.preview {
|
||||
.caption {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
min-width: 0; /* Prevent blowout */
|
||||
min-width: 0;
|
||||
/* Prevent blowout */
|
||||
}
|
||||
|
||||
.caption {
|
||||
flex: 1;
|
||||
overflow: hidden; /* cause it to wrap rather than clip */
|
||||
overflow: hidden;
|
||||
/* cause it to wrap rather than clip */
|
||||
}
|
||||
|
||||
.preview {
|
||||
display: flex;
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
width: 478px;
|
||||
height: 200px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border: none;
|
||||
padding: 0;
|
||||
|
||||
.playButton[data-kind="primary"] {
|
||||
padding: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin: auto;
|
||||
background: var(--cpd-color-text-on-solid-primary);
|
||||
|
||||
> svg {
|
||||
margin: auto;
|
||||
border-radius: 50px;
|
||||
color: var(--cpd-color-icon-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.siteName {
|
||||
margin-top: var(--cpd-space-1x);
|
||||
}
|
||||
|
||||
.title,
|
||||
.description {
|
||||
line-clamp: 2;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.siteName {
|
||||
margin-top: var(--cpd-space-1x);
|
||||
vertical-align: middle;
|
||||
display: flex;
|
||||
gap: var(--cpd-space-1-5x);
|
||||
|
||||
> * {
|
||||
/* Center everything */
|
||||
margin: auto 0;
|
||||
@@ -103,8 +164,6 @@ button.preview {
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
white-space: normal;
|
||||
line-clamp: 2;
|
||||
-webkit-line-clamp: 2;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { fn } from "storybook/test";
|
||||
import type { Meta, StoryFn } from "@storybook/react-vite";
|
||||
import { LinkPreview } from "./LinkPreview";
|
||||
import { LinkedTextContext } from "../../../../../core/utils/LinkedText";
|
||||
import { waitForBackgroundImages } from "../../../../../../.storybook/waitForImages";
|
||||
import imageFile from "../../../../../../static/element.png";
|
||||
import imageFileWide from "../../../../../../static/wideImage.png";
|
||||
import imageFileTall from "../../../../../../static/tallImage.png";
|
||||
@@ -22,6 +23,9 @@ export default {
|
||||
args: {
|
||||
onImageClick: fn(),
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
await waitForBackgroundImages(canvasElement);
|
||||
},
|
||||
argTypes: {
|
||||
siteName: {
|
||||
control: "text",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type MouseEventHandler, type JSX, useCallback } from "react";
|
||||
import React, { type JSX } from "react";
|
||||
import { Tooltip, Text, Avatar, Button } from "@vector-im/compound-web";
|
||||
import PlaySolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/play-solid";
|
||||
import classNames from "classnames";
|
||||
@@ -19,7 +19,7 @@ export interface LinkPreviewActions {
|
||||
onImageClick: () => void;
|
||||
}
|
||||
|
||||
export type LinkPreviewProps = UrlPreview & LinkPreviewActions;
|
||||
export type LinkPreviewProps = UrlPreview & LinkPreviewActions & { collapsed: boolean };
|
||||
|
||||
export function LinkTitle({
|
||||
title,
|
||||
@@ -66,7 +66,7 @@ function LinkPreviewInline({
|
||||
link,
|
||||
}: Omit<LinkPreviewProps, "image" | "description" | "author" | "onImageClick">): JSX.Element {
|
||||
return (
|
||||
<div className={classNames(styles.container, styles.inline)}>
|
||||
<div className={classNames(styles.containerExpanded, styles.inline)}>
|
||||
{siteIcon && (
|
||||
<div className={styles.siteAvatar}>
|
||||
<Avatar type="square" size="48px" name={title} id={title} src={siteIcon} />
|
||||
@@ -84,21 +84,57 @@ function LinkPreviewInline({
|
||||
* LinkPreview renders a single preview component for a single link on an event. It is usually rendered as part of
|
||||
* a `UrlPreviewGroupView`.
|
||||
*/
|
||||
export function LinkPreview({ onImageClick, ...preview }: LinkPreviewProps): JSX.Element {
|
||||
export function LinkPreview(props: LinkPreviewProps): JSX.Element {
|
||||
if (props.collapsed) {
|
||||
return <LinkPreviewCollapsed {...props} />;
|
||||
} else {
|
||||
return <LinkPreviewExpanded {...props} />;
|
||||
}
|
||||
}
|
||||
|
||||
function createImageClickHandler({ onImageClick, ...preview }: LinkPreviewProps): React.MouseEventHandler {
|
||||
return (ev) => {
|
||||
if (ev.button != 0 || ev.metaKey) return;
|
||||
ev.preventDefault();
|
||||
|
||||
if (!preview.image?.imageFull) {
|
||||
return;
|
||||
}
|
||||
onImageClick();
|
||||
};
|
||||
}
|
||||
|
||||
export function LinkPreviewCollapsed(preview: LinkPreviewProps): JSX.Element {
|
||||
const { translate: _t } = useI18n();
|
||||
let img: JSX.Element | undefined;
|
||||
|
||||
const onImageClickHandler = useCallback<MouseEventHandler>(
|
||||
(ev) => {
|
||||
if (ev.button != 0 || ev.metaKey) return;
|
||||
ev.preventDefault();
|
||||
if (preview.image && !preview.image.playable) {
|
||||
img = (
|
||||
<button
|
||||
type="button"
|
||||
style={{
|
||||
backgroundImage: `url('${preview.image.imageThumb}')`,
|
||||
}}
|
||||
className={styles.preview}
|
||||
onClick={createImageClickHandler(preview)}
|
||||
aria-label={_t("timeline|url_preview|view_image")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!preview.image?.imageFull) {
|
||||
return;
|
||||
}
|
||||
onImageClick();
|
||||
},
|
||||
[preview.image?.imageFull, onImageClick],
|
||||
return (
|
||||
<div className={styles.containerCollapsed}>
|
||||
{img}
|
||||
<div className={styles.textContent}>
|
||||
<LinkTitle title={preview.title} showTooltipOnLink={preview.showTooltipOnLink} link={preview.link} />
|
||||
{preview.siteName && <LinkSiteName siteName={preview.siteName} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function LinkPreviewExpanded(preview: LinkPreviewProps): JSX.Element {
|
||||
const { translate: _t } = useI18n();
|
||||
|
||||
if (!preview.image && !preview.author && !preview.description) {
|
||||
return <LinkPreviewInline {...preview} />;
|
||||
@@ -139,7 +175,7 @@ export function LinkPreview({ onImageClick, ...preview }: LinkPreviewProps): JSX
|
||||
backgroundImage: `url('${preview.image.imageThumb}')`,
|
||||
}}
|
||||
className={styles.preview}
|
||||
onClick={onImageClickHandler}
|
||||
onClick={createImageClickHandler(preview)}
|
||||
aria-label={_t("timeline|url_preview|view_image")}
|
||||
/>
|
||||
);
|
||||
@@ -147,7 +183,7 @@ export function LinkPreview({ onImageClick, ...preview }: LinkPreviewProps): JSX
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.containerExpanded}>
|
||||
{img}
|
||||
<div className={styles.textContent}>
|
||||
{preview.author && (
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`LinkPreview > renders a playable preview that can be opened with a click 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="LinkPreview-module_container"
|
||||
class="LinkPreview-module_containerExpanded"
|
||||
>
|
||||
<div
|
||||
class="LinkPreview-module_preview"
|
||||
@@ -66,7 +66,7 @@ exports[`LinkPreview > renders a playable preview that can be opened with a clic
|
||||
exports[`LinkPreview > renders a preview 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="LinkPreview-module_container"
|
||||
class="LinkPreview-module_containerExpanded"
|
||||
>
|
||||
<button
|
||||
aria-label="View image"
|
||||
@@ -106,7 +106,7 @@ exports[`LinkPreview > renders a preview 1`] = `
|
||||
exports[`LinkPreview > renders a preview with just a title 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="LinkPreview-module_container LinkPreview-module_inline"
|
||||
class="LinkPreview-module_containerExpanded LinkPreview-module_inline"
|
||||
>
|
||||
<div
|
||||
class="LinkPreview-module_textContent LinkPreview-module_inline"
|
||||
@@ -136,7 +136,7 @@ exports[`LinkPreview > renders a preview with just a title 1`] = `
|
||||
exports[`LinkPreview > renders a preview with just a title and description 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="LinkPreview-module_container"
|
||||
class="LinkPreview-module_containerExpanded"
|
||||
>
|
||||
<div
|
||||
class="LinkPreview-module_textContent"
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { useMockedViewModel } from "../../../../core/viewmodel";
|
||||
import { LinkedTextContext } from "../../../../core/utils/LinkedText";
|
||||
import { withViewDocs } from "../../../../../.storybook/withViewDocs";
|
||||
import { waitForBackgroundImages } from "../../../../../.storybook/waitForImages";
|
||||
|
||||
type UrlPreviewGroupViewProps = UrlPreviewGroupViewSnapshot & UrlPreviewGroupViewActions;
|
||||
|
||||
@@ -72,6 +73,9 @@ export default {
|
||||
onImageClick: fn(),
|
||||
onTogglePreviewLimit: fn(),
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
await waitForBackgroundImages(canvasElement);
|
||||
},
|
||||
parameters: {
|
||||
design: {
|
||||
type: "figma",
|
||||
|
||||
@@ -99,12 +99,13 @@ export function UrlPreviewGroupView({ vm, className }: UrlPreviewGroupViewProps)
|
||||
<div className={classNames(className, styles.wrapper)} {...eventPresentationAttributes}>
|
||||
<HideButton onHideClick={vm.onHideClick} />
|
||||
<div className={styles.previewGroup}>
|
||||
{previews.map((preview) => (
|
||||
{previews.map((preview, i) => (
|
||||
<LinkPreview
|
||||
key={preview.link}
|
||||
onImageClick={() => vm.onImageClick(preview)}
|
||||
{...preview}
|
||||
image={preview.image}
|
||||
collapsed={i !== 0}
|
||||
/>
|
||||
))}
|
||||
{toggleButton}
|
||||
|
||||
@@ -40,7 +40,7 @@ exports[`UrlPreviewGroupView > renders a single preview 1`] = `
|
||||
class="UrlPreviewGroupView-module_previewGroup"
|
||||
>
|
||||
<div
|
||||
class="LinkPreview-module_container"
|
||||
class="LinkPreview-module_containerExpanded"
|
||||
>
|
||||
<button
|
||||
aria-label="View image"
|
||||
@@ -119,7 +119,7 @@ exports[`UrlPreviewGroupView > renders multiple previews 1`] = `
|
||||
class="UrlPreviewGroupView-module_previewGroup"
|
||||
>
|
||||
<div
|
||||
class="LinkPreview-module_container"
|
||||
class="LinkPreview-module_containerExpanded"
|
||||
>
|
||||
<button
|
||||
aria-label="View image"
|
||||
@@ -154,12 +154,13 @@ exports[`UrlPreviewGroupView > renders multiple previews 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="LinkPreview-module_container"
|
||||
class="LinkPreview-module_containerCollapsed"
|
||||
>
|
||||
<button
|
||||
aria-label="View image"
|
||||
class="LinkPreview-module_preview"
|
||||
style="background-image: url("/static/tallImage.png");"
|
||||
type="button"
|
||||
/>
|
||||
<div
|
||||
class="LinkPreview-module_textContent"
|
||||
@@ -172,11 +173,6 @@ exports[`UrlPreviewGroupView > renders multiple previews 1`] = `
|
||||
>
|
||||
Two
|
||||
</a>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
|
||||
>
|
||||
This one has a taller image which should crop nicely.
|
||||
</p>
|
||||
<div
|
||||
class="LinkPreview-module_siteName"
|
||||
>
|
||||
@@ -189,12 +185,13 @@ exports[`UrlPreviewGroupView > renders multiple previews 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="LinkPreview-module_container"
|
||||
class="LinkPreview-module_containerCollapsed"
|
||||
>
|
||||
<button
|
||||
aria-label="View image"
|
||||
class="LinkPreview-module_preview"
|
||||
style="background-image: url("/static/element.png");"
|
||||
type="button"
|
||||
/>
|
||||
<div
|
||||
class="LinkPreview-module_textContent"
|
||||
@@ -207,11 +204,6 @@ exports[`UrlPreviewGroupView > renders multiple previews 1`] = `
|
||||
>
|
||||
Three
|
||||
</a>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
|
||||
>
|
||||
One more description
|
||||
</p>
|
||||
<div
|
||||
class="LinkPreview-module_siteName"
|
||||
>
|
||||
@@ -277,7 +269,7 @@ exports[`UrlPreviewGroupView > renders multiple previews which are hidden 1`] =
|
||||
class="UrlPreviewGroupView-module_previewGroup"
|
||||
>
|
||||
<div
|
||||
class="LinkPreview-module_container"
|
||||
class="LinkPreview-module_containerExpanded"
|
||||
>
|
||||
<button
|
||||
aria-label="View image"
|
||||
@@ -365,7 +357,7 @@ exports[`UrlPreviewGroupView > renders with compact density 1`] = `
|
||||
class="UrlPreviewGroupView-module_previewGroup"
|
||||
>
|
||||
<div
|
||||
class="LinkPreview-module_container"
|
||||
class="LinkPreview-module_containerExpanded"
|
||||
>
|
||||
<button
|
||||
aria-label="View image"
|
||||
@@ -400,12 +392,13 @@ exports[`UrlPreviewGroupView > renders with compact density 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="LinkPreview-module_container"
|
||||
class="LinkPreview-module_containerCollapsed"
|
||||
>
|
||||
<button
|
||||
aria-label="View image"
|
||||
class="LinkPreview-module_preview"
|
||||
style="background-image: url("/static/tallImage.png");"
|
||||
type="button"
|
||||
/>
|
||||
<div
|
||||
class="LinkPreview-module_textContent"
|
||||
@@ -418,11 +411,6 @@ exports[`UrlPreviewGroupView > renders with compact density 1`] = `
|
||||
>
|
||||
Two
|
||||
</a>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
|
||||
>
|
||||
This one has a taller image which should crop nicely.
|
||||
</p>
|
||||
<div
|
||||
class="LinkPreview-module_siteName"
|
||||
>
|
||||
@@ -435,12 +423,13 @@ exports[`UrlPreviewGroupView > renders with compact density 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="LinkPreview-module_container"
|
||||
class="LinkPreview-module_containerCollapsed"
|
||||
>
|
||||
<button
|
||||
aria-label="View image"
|
||||
class="LinkPreview-module_preview"
|
||||
style="background-image: url("/static/element.png");"
|
||||
type="button"
|
||||
/>
|
||||
<div
|
||||
class="LinkPreview-module_textContent"
|
||||
@@ -453,11 +442,6 @@ exports[`UrlPreviewGroupView > renders with compact density 1`] = `
|
||||
>
|
||||
Three
|
||||
</a>
|
||||
<p
|
||||
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
|
||||
>
|
||||
One more description
|
||||
</p>
|
||||
<div
|
||||
class="LinkPreview-module_siteName"
|
||||
>
|
||||
|
||||