nicolium: preview card improvements, memo and fixes

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-02-15 15:26:02 +01:00
parent 6df9a99c6e
commit fdb2befb52
3 changed files with 51 additions and 39 deletions

View File

@ -1,5 +1,6 @@
import { Link } from '@tanstack/react-router';
import clsx from 'clsx';
import DOMPurify from 'isomorphic-dompurify';
import {
type MediaAttachment,
type PreviewCard as CardEntity,
@ -20,18 +21,6 @@ import { getTextDirection } from '@/utils/rtl';
import HoverAccountWrapper from './hover-account-wrapper';
import Avatar from './ui/avatar';
/** Props for `PreviewCard`. */
interface IPreviewCard {
card: CardEntity;
maxTitle?: number;
maxDescription?: number;
onOpenMedia: (attachments: Array<MediaAttachment>, index: number) => void;
compact?: boolean;
defaultWidth?: number;
cacheWidth?: (width: number) => void;
horizontal?: boolean;
}
const domParser = new DOMParser();
const handleIframeUrl = (html: string, url: string, providerName: string) => {
@ -60,6 +49,51 @@ const handleIframeUrl = (html: string, url: string, providerName: string) => {
return '';
};
const getRatio = (card: CardEntity): number => {
const ratio = card.width / card.height || 16 / 9;
// Constrain to a sane limit
// https://en.wikipedia.org/wiki/Aspect_ratio_(image)
return Math.min(Math.max(9 / 16, ratio), 4);
};
interface IPreviewCardVideo {
card: CardEntity;
}
const PreviewCardVideo: React.FC<IPreviewCardVideo> = React.memo(
React.forwardRef<HTMLDivElement, IPreviewCardVideo>(({ card }, ref) => {
const html = DOMPurify.sanitize(handleIframeUrl(card.html, card.url, card.provider_name), {
ADD_TAGS: ['iframe'],
ADD_ATTR: ['allow', 'allowfullscreen', 'referrerpolicy'],
});
const content = { __html: html };
const ratio = getRatio(card);
return (
<div
ref={ref}
className='status-card__image status-card-video'
dangerouslySetInnerHTML={content}
style={{ aspectRatio: ratio }}
/>
);
}),
);
/** Props for `PreviewCard`. */
interface IPreviewCard {
card: CardEntity;
maxTitle?: number;
maxDescription?: number;
onOpenMedia: (attachments: Array<MediaAttachment>, index: number) => void;
compact?: boolean;
defaultWidth?: number;
cacheWidth?: (width: number) => void;
horizontal?: boolean;
}
/** Displays a Mastodon link preview. Similar to OEmbed. */
const PreviewCard: React.FC<IPreviewCard> = ({
card,
@ -119,29 +153,6 @@ const PreviewCard: React.FC<IPreviewCard> = ({
}
};
const renderVideo = () => {
const content = { __html: handleIframeUrl(card.html, card.url, card.provider_name) };
const ratio = getRatio(card);
const height = width / ratio;
return (
<div
ref={setRef}
className='status-card__image status-card-video'
dangerouslySetInnerHTML={content}
style={{ height }}
/>
);
};
const getRatio = (card: CardEntity): number => {
const ratio = card.width / card.height || 16 / 9;
// Constrain to a sane limit
// https://en.wikipedia.org/wiki/Aspect_ratio_(image)
return Math.min(Math.max(9 / 16, ratio), 4);
};
const interactive = card.type !== 'link';
const horizontal = interactive || embedded;
const className = clsx(
@ -209,7 +220,7 @@ const PreviewCard: React.FC<IPreviewCard> = ({
if (interactive) {
if (embedded) {
embed = renderVideo();
embed = <PreviewCardVideo card={card} />;
} else {
let iconVariant = require('@phosphor-icons/core/regular/play.svg');

View File

@ -262,11 +262,11 @@ const StatusContent: React.FC<IStatusContent> = React.memo(
}
}
const media = (quote ??
status.card ??
const media = (quote ||
status.card ||
(withMedia && status.media_attachments.length > 0)) && (
<Stack space={4} key='media'>
{((withMedia && status.media_attachments.length > 0) ?? (status.card && !quote)) && (
{((withMedia && status.media_attachments.length > 0) || (status.card && !quote)) && (
<div className='relative has-[div[data-testid="sensitive-overlay"]]:min-h-24'>
<SensitiveContentOverlay status={status} />
{withMedia && <StatusMedia status={status} muted={compose} />}

View File

@ -31,6 +31,7 @@
iframe {
width: 100% !important;
height: 100%;
}
}