import { useCallback, useId, useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import { isMap } from 'immutable'; import punycode from 'punycode/'; import DescriptionIcon from '@/material-icons/400-24px/description-fill.svg?react'; import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react'; import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react'; import { Blurhash } from 'mastodon/components/blurhash'; import { Icon } from 'mastodon/components/icon'; import { MoreFromAuthor } from 'mastodon/components/more_from_author'; import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; import { displayMedia, useBlurhash } from 'mastodon/initial_state'; import type { CardShape, Card as CardType } from 'mastodon/models/status'; const IDNA_PREFIX = 'xn--'; const decodeIDNA = (domain: string) => { return domain .split('.') .map((part) => part.startsWith(IDNA_PREFIX) ? punycode.decode(part.slice(IDNA_PREFIX.length)) : part, ) .join('.'); }; const getHostname = (url: string) => { const parser = document.createElement('a'); parser.href = url; return parser.hostname; }; const domParser = new DOMParser(); const handleIframeUrl = (html: string, url: string, providerName: string) => { const document = domParser.parseFromString(html, 'text/html').documentElement; const iframe = document.querySelector('iframe'); const startTime = new URL(url).searchParams.get('t'); if (iframe) { const iframeUrl = new URL(iframe.src); iframeUrl.searchParams.set('autoplay', '1'); iframeUrl.searchParams.set('auto_play', '1'); if (providerName === 'YouTube') { iframeUrl.searchParams.set('start', startTime ?? ''); iframe.referrerPolicy = 'strict-origin-when-cross-origin'; } iframe.src = iframeUrl.href; // DOM parser creates html/body elements around original HTML fragment, // so we need to get innerHTML out of the body and not the entire document return document.querySelector('body')?.innerHTML ?? ''; } return html; }; const hideAllMedia = displayMedia === 'hide_all'; interface CardProps { card: CardType | CardShape | null; sensitive?: boolean; } const CardVideo: React.FC<{ card: CardShape }> = ({ card }) => { const { html, url, provider_name } = card; const iframeContent = useMemo( () => ({ __html: handleIframeUrl(html, url, provider_name), }), [html, provider_name, url], ); return (
); }; const Card: React.FC