forked from github/cinny
🦉 img tag conversion
This commit is contained in:
@@ -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 (
|
||||||
|
<>
|
||||||
|
{imageUrls.map((url) => (
|
||||||
|
<ExternalImageCard key={url} url={url} />
|
||||||
|
))}
|
||||||
|
{otherUrls.length > 0 && (
|
||||||
<UrlPreviewHolder>
|
<UrlPreviewHolder>
|
||||||
{filteredUrls.map((url) => (
|
{otherUrls.map((url) => (
|
||||||
<UrlPreviewCard key={url} url={url} ts={ts} />
|
<UrlPreviewCard key={url} url={url} ts={ts} />
|
||||||
))}
|
))}
|
||||||
</UrlPreviewHolder>
|
</UrlPreviewHolder>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const renderCaption = () => {
|
const renderCaption = () => {
|
||||||
|
|||||||
28
src/owl/components/ExternalImageCard.css.tsx
Normal file
28
src/owl/components/ExternalImageCard.css.tsx
Normal 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',
|
||||||
|
},
|
||||||
|
]);
|
||||||
57
src/owl/components/ExternalImageCard.tsx
Normal file
57
src/owl/components/ExternalImageCard.tsx
Normal 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
10
src/owl/utils/imageUrl.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user