diff --git a/packages/pl-fe/src/components/preview-card.tsx b/packages/pl-fe/src/components/preview-card.tsx index 71344f9f2..74845cdec 100644 --- a/packages/pl-fe/src/components/preview-card.tsx +++ b/packages/pl-fe/src/components/preview-card.tsx @@ -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, 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 = React.memo( + React.forwardRef(({ 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 ( +
+ ); + }), +); + +/** Props for `PreviewCard`. */ +interface IPreviewCard { + card: CardEntity; + maxTitle?: number; + maxDescription?: number; + onOpenMedia: (attachments: Array, 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 = ({ card, @@ -119,29 +153,6 @@ const PreviewCard: React.FC = ({ } }; - const renderVideo = () => { - const content = { __html: handleIframeUrl(card.html, card.url, card.provider_name) }; - const ratio = getRatio(card); - const height = width / ratio; - - 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); - }; - const interactive = card.type !== 'link'; const horizontal = interactive || embedded; const className = clsx( @@ -209,7 +220,7 @@ const PreviewCard: React.FC = ({ if (interactive) { if (embedded) { - embed = renderVideo(); + embed = ; } else { let iconVariant = require('@phosphor-icons/core/regular/play.svg'); diff --git a/packages/pl-fe/src/components/status-content.tsx b/packages/pl-fe/src/components/status-content.tsx index 84bc7a322..a72343b46 100644 --- a/packages/pl-fe/src/components/status-content.tsx +++ b/packages/pl-fe/src/components/status-content.tsx @@ -262,11 +262,11 @@ const StatusContent: React.FC = React.memo( } } - const media = (quote ?? - status.card ?? + const media = (quote || + status.card || (withMedia && status.media_attachments.length > 0)) && ( - {((withMedia && status.media_attachments.length > 0) ?? (status.card && !quote)) && ( + {((withMedia && status.media_attachments.length > 0) || (status.card && !quote)) && (
{withMedia && } diff --git a/packages/pl-fe/src/styles/components/status.scss b/packages/pl-fe/src/styles/components/status.scss index cddf136cc..e5ad6fa71 100644 --- a/packages/pl-fe/src/styles/components/status.scss +++ b/packages/pl-fe/src/styles/components/status.scss @@ -31,6 +31,7 @@ iframe { width: 100% !important; + height: 100%; } }