🦉 img tag conversion

This commit is contained in:
2026-04-15 23:09:03 +02:00
parent 7f12d047f3
commit 3b8d7fb026
4 changed files with 112 additions and 4 deletions

View File

@@ -25,6 +25,8 @@ import {
VideoContent, VideoContent,
} from './message'; } from './message';
import { UrlPreviewCard, UrlPreviewHolder } from './url-preview'; import { UrlPreviewCard, UrlPreviewHolder } from './url-preview';
import { ExternalImageCard } from '../../owl/components/ExternalImageCard';
import { isImageUrl } from '../../owl/utils/imageUrl';
import { Image, MediaControl, Video } from './media'; import { Image, MediaControl, Video } from './media';
import { ImageViewer } from './image-viewer'; import { ImageViewer } from './image-viewer';
import { PdfViewer } from './Pdf-viewer'; import { PdfViewer } from './Pdf-viewer';
@@ -61,12 +63,23 @@ export function RenderMessageContent({
const renderUrlsPreview = (urls: string[]) => { const renderUrlsPreview = (urls: string[]) => {
const filteredUrls = urls.filter((url) => !testMatrixTo(url)); const filteredUrls = urls.filter((url) => !testMatrixTo(url));
if (filteredUrls.length === 0) return undefined; if (filteredUrls.length === 0) return undefined;
const imageUrls = filteredUrls.filter(isImageUrl);
const otherUrls = filteredUrls.filter((url) => !isImageUrl(url));
return ( return (
<UrlPreviewHolder> <>
{filteredUrls.map((url) => ( {imageUrls.map((url) => (
<UrlPreviewCard key={url} url={url} ts={ts} /> <ExternalImageCard key={url} url={url} />
))} ))}
</UrlPreviewHolder> {otherUrls.length > 0 && (
<UrlPreviewHolder>
{otherUrls.map((url) => (
<UrlPreviewCard key={url} url={url} ts={ts} />
))}
</UrlPreviewHolder>
)}
</>
); );
}; };
const renderCaption = () => { const renderCaption = () => {

View File

@@ -0,0 +1,28 @@
import { style } from '@vanilla-extract/css';
import { DefaultReset, color, config, toRem } from 'folds';
export const ExternalImageCard = style([
DefaultReset,
{
display: 'inline-block',
maxWidth: toRem(400),
borderRadius: config.radii.R300,
overflow: 'hidden',
border: `${config.borderWidth.B300} solid ${color.SurfaceVariant.ContainerLine}`,
cursor: 'pointer',
':hover': {
filter: 'brightness(0.95)',
},
},
]);
export const ExternalImage = style([
DefaultReset,
{
display: 'block',
maxWidth: '100%',
maxHeight: toRem(400),
objectFit: 'contain',
},
]);

View File

@@ -0,0 +1,57 @@
import React, { useState } from 'react';
import { Box, Text, as, color } from 'folds';
import { ImageOverlay } from '../../app/components/ImageOverlay';
import { ImageViewer } from '../../app/components/image-viewer';
import { onEnterOrSpace } from '../../app/utils/keyboard';
import * as css from './ExternalImageCard.css';
import { tryDecodeURIComponent } from '../../app/utils/dom';
const linkStyles = { color: color.Success.Main };
export const ExternalImageCard = as<'div', { url: string }>(({ url, ...props }, ref) => {
const [viewer, setViewer] = useState(false);
const [error, setError] = useState(false);
if (error) return null;
const filename = url.split('/').pop()?.split('?')[0] || url;
return (
<Box direction="Column" gap="100" {...props} ref={ref}>
<div
className={css.ExternalImageCard}
role="button"
tabIndex={0}
onKeyDown={(evt) => onEnterOrSpace(() => setViewer(true))(evt)}
onClick={() => setViewer(true)}
>
<img
className={css.ExternalImage}
src={url}
alt={filename}
loading="lazy"
onError={() => setError(true)}
/>
</div>
<Text
style={linkStyles}
truncate
as="a"
href={url}
target="_blank"
rel="noreferrer"
size="T200"
priority="300"
>
{tryDecodeURIComponent(url)}
</Text>
<ImageOverlay
src={url}
alt={filename}
viewer={viewer}
requestClose={() => setViewer(false)}
renderViewer={(p) => <ImageViewer {...p} />}
/>
</Box>
);
});

10
src/owl/utils/imageUrl.ts Normal file
View File

@@ -0,0 +1,10 @@
const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.avif', '.svg', '.bmp', '.ico'];
export function isImageUrl(url: string): boolean {
try {
const { pathname } = new URL(url);
return IMAGE_EXTENSIONS.some((ext) => pathname.toLowerCase().endsWith(ext));
} catch {
return false;
}
}