diff --git a/src/app/components/url-preview/UrlPreview.css.tsx b/src/app/components/url-preview/UrlPreview.css.tsx index 3192dc49b6..8ec366eec7 100644 --- a/src/app/components/url-preview/UrlPreview.css.tsx +++ b/src/app/components/url-preview/UrlPreview.css.tsx @@ -5,7 +5,8 @@ export const UrlPreview = style([ DefaultReset, { width: toRem(400), - minHeight: toRem(102), + display: 'flex', + flexDirection: 'column', backgroundColor: color.SurfaceVariant.Container, color: color.SurfaceVariant.OnContainer, border: `${config.borderWidth.B300} solid ${color.SurfaceVariant.ContainerLine}`, @@ -18,11 +19,23 @@ export const UrlPreviewImg = style([ DefaultReset, { width: toRem(100), - height: toRem(100), + minHeight: toRem(100), + height: '100%', objectFit: 'cover', objectPosition: 'center', flexShrink: 0, - overflow: 'hidden', + cursor: 'pointer', + }, +]); + +export const UrlPreviewHeroImg = style([ + DefaultReset, + { + width: '100%', + maxHeight: toRem(300), + objectFit: 'contain', + backgroundColor: '#000', + cursor: 'pointer', }, ]); @@ -30,9 +43,16 @@ export const UrlPreviewContent = style([ DefaultReset, { padding: config.space.S200, + flexGrow: 1, }, ]); +export const UrlPreviewCardRow = style({ + display: 'flex', + flexDirection: 'row', + alignItems: 'stretch', +}); + export const UrlPreviewDescription = style([ DefaultReset, { diff --git a/src/app/components/url-preview/UrlPreview.tsx b/src/app/components/url-preview/UrlPreview.tsx index 4ba3e4e224..9711ac8232 100644 --- a/src/app/components/url-preview/UrlPreview.tsx +++ b/src/app/components/url-preview/UrlPreview.tsx @@ -11,6 +11,10 @@ export const UrlPreviewImg = as<'img'>(({ className, alt, ...props }, ref) => ( {alt} )); +export const UrlPreviewHeroImg = as<'img'>(({ className, alt, ...props }, ref) => ( + {alt} +)); + export const UrlPreviewContent = as<'div'>(({ className, ...props }, ref) => ( ( ({ url, ts, ...props }, ref) => { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); + const [viewImage, setViewImage] = useState(null); + const [previewStatus, loadPreview] = useAsyncCallback( useCallback(() => mx.getUrlPreview(url, ts), [url, ts, mx]) ); @@ -31,10 +58,53 @@ export const UrlPreviewCard = as<'div', { url: string; ts: number }>( const renderContent = (prev: IPreviewUrlResponse) => { const imgUrl = mxcUrlToHttp(mx, prev['og:image'] || '', useAuthentication, 256, 256, 'scale', false); + const rawImgUrl = prev['og:image'] ? mxcUrlToHttp(mx, prev['og:image'], useAuthentication) : null; + + const title = prev['og:title']; + const description = prev['og:description']; + const siteName = prev['og:site_name']; + + const isHeroImage = + rawImgUrl && + (prev['og:type']?.startsWith('image') || !description || title === url); + + if (isHeroImage) { + return ( + <> + setViewImage(rawImgUrl)} + /> + + + {tryDecodeURIComponent(url)} + + + + ); + } return ( - <> - {imgUrl && } +
+ {imgUrl && ( + setViewImage(rawImgUrl || imgUrl)} + /> + )} ( size="T200" priority="300" > - {typeof prev['og:site_name'] === 'string' && `${prev['og:site_name']} | `} + {typeof siteName === 'string' && `${siteName} | `} {tryDecodeURIComponent(url)} - {prev['og:title']} + {title} - {prev['og:description']} + {description} - +
); }; return ( - - {previewStatus.status === AsyncStatus.Success ? ( - renderContent(previewStatus.data) - ) : ( - - - + <> + + {previewStatus.status === AsyncStatus.Success ? ( + renderContent(previewStatus.data) + ) : ( + + + + )} + + + {viewImage && ( + }> + + setViewImage(null), + clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, + }} + > + evt.stopPropagation()} + > + setViewImage(null)} + /> + + + + )} - + ); } );