import type React from 'react'; import { useCallback } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import classnames from 'classnames'; import { toggleStatusCollapse } from '@/mastodon/actions/statuses'; import { useStatus } from '@/mastodon/hooks/useStatus'; import { useIdentity } from '@/mastodon/identity_context'; import { languages as preloadedLanguages } from '@/mastodon/initial_state'; import type { StatusTranslation } from '@/mastodon/models/status'; import { useAppDispatch, useAppSelector } from '@/mastodon/store'; import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; import { EmojiHTML } from '../emoji/html'; import { Icon } from '../icon'; import { Poll } from '../poll'; import { useElementHandledLink } from './handled_link'; const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) export const StatusContent: React.FC<{ statusId: string; statusContent?: string; onClick?: React.MouseEventHandler; onTranslate?: React.MouseEventHandler; collapsible?: boolean; }> = ({ statusId, statusContent, onClick, onTranslate, collapsible }) => { const status = useStatus(statusId); const { signedIn } = useIdentity(); const targetLanguages = useAppSelector( (state) => state.server.translationLanguages.item?.[status?.language ?? 'und'], ); const intl = useIntl(); // Determines if a long post should show the read more button. const dispatch = useAppDispatch(); const handleCollapse = useCallback( (node: HTMLDivElement | null) => { if (!node || status?.collapsed !== null || !collapsible) { return; } const text = node.querySelector(':scope > .status__content__text'); const collapsed = (node.clientHeight > MAX_HEIGHT || (text !== null && text.scrollWidth > text.clientWidth)) && !status.spoiler_text; dispatch(toggleStatusCollapse(status.id, collapsed)); }, [collapsible, status, dispatch], ); // Trigger the click event if clicking outside a link, button, or label inside a status. const handleClick: React.MouseEventHandler = useCallback( (event) => { const { target } = event; if ( !onClick || !(target instanceof Element) || target.closest(':is(a, button, label)') ) { return; } onClick(event); }, [onClick], ); const hrefToMention = useCallback( (href: string) => status?.mentions.find((item) => item.url === href), [status?.mentions], ); const hrefToCollectionId = useCallback( (href: string) => status?.tagged_collections.find((item) => item.url === href)?.id, [status?.tagged_collections], ); const htmlHandlers = useElementHandledLink({ hashtagAccountId: status?.account, hrefToCollectionId, hrefToMention, }); if (!status) { return null; } const language = status.translation?.language ?? status.language; const renderReadMore = !!onClick && status.collapsed; const readMoreButton = renderReadMore && ( ); const renderTranslate = !!onTranslate && signedIn && ['public', 'unlisted'].includes(status.visibility) && status.search_index && status.search_index.trim().length > 0 && targetLanguages?.includes(intl.locale.replace(/[_-].*/, '')); const translateButton = renderTranslate && ( ); const poll = !!status.poll && ( ); const content = ( ); const classNames = classnames('status__content', { 'status__content--with-action': onClick, 'status__content--collapsed': renderReadMore, }); if (!onClick) { return (
{content} {poll} {translateButton}
); } /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ return ( <>
{content} {poll} {translateButton}
{readMoreButton} ); /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ }; const TranslateButton: React.FC<{ onClick: React.MouseEventHandler; translation?: StatusTranslation; }> = ({ translation, onClick }) => { if (!translation) { return ( ); } const language = preloadedLanguages?.find( (lang) => lang[0] === translation.detected_source_language, ); const languageName = language ? language[1] : translation.detected_source_language; const provider = translation.provider; return (
); };