Sending URL Preview Bundles (MSC 4095) (#34150)
* 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 * 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 * updated snapshots * claude fixed tests * enable feature for tests that requires MSC4095 * updated snapshot
This commit is contained in:
+44
-33
@@ -52,46 +52,55 @@ const Template: StoryFn<typeof MessageComposerUrlPreviewViewWrapper> = (args) =>
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
preview: {
|
||||
title: "A simple title",
|
||||
description: "A simple description",
|
||||
link: "https://matrix.org",
|
||||
siteName: "matrix.org",
|
||||
showTooltipOnLink: false,
|
||||
},
|
||||
previews: [
|
||||
{
|
||||
title: "A simple title",
|
||||
description: "A simple description",
|
||||
link: "https://matrix.org",
|
||||
siteName: "matrix.org",
|
||||
showTooltipOnLink: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const WithImage = Template.bind({});
|
||||
WithImage.args = {
|
||||
preview: {
|
||||
...Default.args.preview!,
|
||||
image: {
|
||||
imageThumb: imagePreviewFile,
|
||||
imageFull: imagePreviewFile,
|
||||
alt: "The element logo",
|
||||
playable: false,
|
||||
previews: [
|
||||
{
|
||||
...Default.args.previews![0]!,
|
||||
image: {
|
||||
imageThumb: imagePreviewFile,
|
||||
imageFull: imagePreviewFile,
|
||||
alt: "The element logo",
|
||||
playable: false,
|
||||
mxcImageFull: "mxc://server/file",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
export const WithImageAndSiteIcon = Template.bind({});
|
||||
WithImageAndSiteIcon.args = {
|
||||
preview: {
|
||||
...Default.args.preview!,
|
||||
siteIcon: siteIconFile,
|
||||
image: {
|
||||
imageThumb: imagePreviewFile,
|
||||
imageFull: imagePreviewFile,
|
||||
alt: "The element logo",
|
||||
playable: false,
|
||||
previews: [
|
||||
{
|
||||
...Default.args.previews![0]!,
|
||||
siteIcon: siteIconFile,
|
||||
image: {
|
||||
imageThumb: imagePreviewFile,
|
||||
imageFull: imagePreviewFile,
|
||||
alt: "The element logo",
|
||||
playable: false,
|
||||
mxcImageFull: "mxc://server/file",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const WithImageAndLoadsOfText = Template.bind({});
|
||||
WithImageAndLoadsOfText.args = {
|
||||
preview: {
|
||||
...Default.args.preview!,
|
||||
description: `Molestiae aliquam quos possimus molestiae id sit nulla rerum. Sunt cumque illum alias. Illo ipsa ut iure quia nulla magnam repellat.
|
||||
previews: [
|
||||
{
|
||||
...Default.args.previews![0]!,
|
||||
description: `Molestiae aliquam quos possimus molestiae id sit nulla rerum. Sunt cumque illum alias. Illo ipsa ut iure quia nulla magnam repellat.
|
||||
|
||||
Esse velit corporis sapiente temporibus quia ipsam. Pariatur est rem veritatis. Inventore sit consequatur odio ipsa error non assumenda. Est eum ex dignissimos voluptatibus voluptatem delectus modi. Nisi quia eius ea quibusdam. Aut eveniet maxime non.
|
||||
|
||||
@@ -100,11 +109,13 @@ WithImageAndLoadsOfText.args = {
|
||||
Incidunt ut ea quae nobis. Reiciendis inventore quas qui eum voluptatem ex et qui. Adipisci quibusdam dolores hic inventore et suscipit cupiditate consequuntur.
|
||||
|
||||
Temporibus similique sint quo. Omnis tempora quidem explicabo in quidem magnam quia. Aut sunt accusantium ut et ut laborum debitis in. Enim nihil sit consectetur facilis quidem voluptatem. Quod impedit odit veritatis est laudantium tempore sit labore. Atque minima aliquam nostrum et.`,
|
||||
image: {
|
||||
imageThumb: imagePreviewFile,
|
||||
imageFull: imagePreviewFile,
|
||||
alt: "The element logo",
|
||||
playable: false,
|
||||
image: {
|
||||
imageThumb: imagePreviewFile,
|
||||
imageFull: imagePreviewFile,
|
||||
alt: "The element logo",
|
||||
playable: false,
|
||||
mxcImageFull: "mxc://server/file",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
+14
-7
@@ -17,7 +17,9 @@ import { useViewModel, type ViewModel } from "../../../core/viewmodel";
|
||||
/** Snapshot data for rendering a URL preview attached to the composer. */
|
||||
export interface MessageComposerUrlPreviewSnapshot {
|
||||
/** URL preview to render. */
|
||||
preview: UrlPreview | null;
|
||||
previews: UrlPreview[];
|
||||
/** Content of the composer when the snapshot is computed */
|
||||
content: string;
|
||||
}
|
||||
|
||||
/** Props for MessageComposerUrlPreviewView. */
|
||||
@@ -33,15 +35,18 @@ export interface MessageComposerUrlPreviewProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* MessageComposerUrlPreviewView renders a preview of a single URL above the messasge composer.
|
||||
* MessageComposerUrlPreviewView renders a preview of all previewable URLs above the messasge composer.
|
||||
*/
|
||||
export function MessageComposerUrlPreviewView({ vm, className }: MessageComposerUrlPreviewProps): JSX.Element | null {
|
||||
const { preview } = useViewModel(vm);
|
||||
if (!preview) {
|
||||
const { previews } = useViewModel(vm);
|
||||
if (previews.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={classNames(className, styles.container)}>
|
||||
|
||||
// Show only the first preview to revert back to previous behaviour
|
||||
// But have previews fetch all URL previews in the message text
|
||||
const previewViews = previews.slice(0, 1).map((preview) => (
|
||||
<div key={preview.link} className={classNames(className, styles.container)}>
|
||||
<div>
|
||||
{preview?.image?.imageThumb && (
|
||||
<img className={styles.image} src={preview.image?.imageThumb} alt={preview.image.alt} />
|
||||
@@ -53,5 +58,7 @@ export function MessageComposerUrlPreviewView({ vm, className }: MessageComposer
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
));
|
||||
|
||||
return <>{previewViews}</>;
|
||||
}
|
||||
|
||||
+6
@@ -57,6 +57,7 @@ Default.args = {
|
||||
imageFull: imageFile,
|
||||
alt: "Element logo",
|
||||
playable: false,
|
||||
mxcImageFull: "mxc://server/file",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -112,6 +113,7 @@ Article.args = {
|
||||
imageFull: imageFileWide,
|
||||
alt: "A dog",
|
||||
playable: false,
|
||||
mxcImageFull: "mxc://server/file",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -127,6 +129,7 @@ Video.args = {
|
||||
imageFull: imageFileWide,
|
||||
alt: "A dog",
|
||||
playable: true,
|
||||
mxcImageFull: "mxc://server/file",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -151,6 +154,7 @@ SocialWithImage.args = {
|
||||
imageFull: imageFileWide,
|
||||
alt: "A dog",
|
||||
playable: false,
|
||||
mxcImageFull: "mxc://server/file",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -166,6 +170,7 @@ WithVeryLongText.args = {
|
||||
imageFull: imageFile,
|
||||
alt: "Element logo",
|
||||
playable: false,
|
||||
mxcImageFull: "mxc://server/file",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -180,5 +185,6 @@ WithTallImage.args = {
|
||||
imageFull: imageFileTall,
|
||||
alt: "Element logo",
|
||||
playable: false,
|
||||
mxcImageFull: "mxc://server/file",
|
||||
},
|
||||
};
|
||||
|
||||
+4
@@ -96,6 +96,7 @@ Default.args = {
|
||||
imageFull: imageFile,
|
||||
alt: "The element logo",
|
||||
playable: false,
|
||||
mxcImageFull: "mxc://server/file",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -123,6 +124,7 @@ MultiplePreviewsVisible.args = {
|
||||
imageFull: imageFile,
|
||||
alt: "The element logo",
|
||||
playable: false,
|
||||
mxcImageFull: "mxc://server/file",
|
||||
},
|
||||
},
|
||||
// These images should appear the same size despite having different dimensions.
|
||||
@@ -137,6 +139,7 @@ MultiplePreviewsVisible.args = {
|
||||
imageFull: tallImageFile,
|
||||
alt: "A dog",
|
||||
playable: false,
|
||||
mxcImageFull: "mxc://server/file",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -150,6 +153,7 @@ MultiplePreviewsVisible.args = {
|
||||
imageFull: imageFile,
|
||||
alt: "The element logo",
|
||||
playable: false,
|
||||
mxcImageFull: "mxc://server/file",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -22,6 +22,10 @@ export interface UrlPreview {
|
||||
* The site name to be displayed alongside the title.
|
||||
*/
|
||||
siteName: string;
|
||||
/**
|
||||
* The og:url value of the page, could be different from link
|
||||
*/
|
||||
ogUrl?: string;
|
||||
/**
|
||||
* The HTTP URI of the the sites icon.
|
||||
*/
|
||||
@@ -42,6 +46,14 @@ export interface UrlPreview {
|
||||
* The HTTP URI of the full image.
|
||||
*/
|
||||
imageFull: string;
|
||||
/**
|
||||
* The mxc:// URI of the full image.
|
||||
*/
|
||||
mxcImageFull: string;
|
||||
/**
|
||||
* The type/subtype of the image format
|
||||
*/
|
||||
imageType?: string;
|
||||
/**
|
||||
* File size in bytes.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user