import classNames from 'classnames'; import React, { useState, useRef, useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; import { useHistory } from 'react-router-dom'; import Icon from 'soapbox/components/icon'; import Poll from 'soapbox/components/poll'; import { useSoapboxConfig } from 'soapbox/hooks'; import { addGreentext } from 'soapbox/utils/greentext'; import { onlyEmoji as isOnlyEmoji } from 'soapbox/utils/rich_content'; import { isRtl } from '../rtl'; // import Permalink from './permalink'; import type { Status, Mention } from 'soapbox/types/entities'; const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top) const BIG_EMOJI_LIMIT = 10; type Point = [ x: number, y: number, ] interface IStatusContent { status: Status, reblogContent?: string, expanded?: boolean, onExpandedToggle?: () => void, onClick?: () => void, collapsable?: boolean, } const StatusContent: React.FC = ({ status, expanded = false, onExpandedToggle, onClick, collapsable = false }) => { const history = useHistory(); const [hidden, setHidden] = useState(true); const [collapsed, setCollapsed] = useState(false); const [onlyEmoji, setOnlyEmoji] = useState(false); const startXY = useRef(); const node = useRef(null); const { greentext } = useSoapboxConfig(); const onMentionClick = (mention: Mention, e: MouseEvent) => { if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); history.push(`/@${mention.acct}`); } }; const onHashtagClick = (hashtag: string, e: MouseEvent) => { hashtag = hashtag.replace(/^#/, '').toLowerCase(); if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); history.push(`/tags/${hashtag}`); } }; const updateStatusLinks = () => { if (!node.current) return; const links = node.current.querySelectorAll('a'); for (let i = 0; i < links.length; ++i) { const link = links[i]; if (link.classList.contains('status-link')) { continue; } link.classList.add('status-link'); link.setAttribute('rel', 'nofollow noopener'); link.setAttribute('target', '_blank'); const mention = status.mentions.find(mention => link.href === `${mention.url}`); if (mention) { link.addEventListener('click', onMentionClick.bind(link, mention), false); link.setAttribute('title', mention.acct); } else if (link.textContent?.charAt(0) === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) { link.addEventListener('click', onHashtagClick.bind(link, link.text), false); } else { link.setAttribute('title', link.href); } } }; const maybeSetCollapsed = (): void => { if (!node.current) return; if (collapsable && onClick && !collapsed && status.spoiler_text.length === 0) { if (node.current.clientHeight > MAX_HEIGHT) { setCollapsed(true); } } }; const maybeSetOnlyEmoji = (): void => { if (!node.current) return; const only = isOnlyEmoji(node.current, BIG_EMOJI_LIMIT, true); if (only !== onlyEmoji) { setOnlyEmoji(only); } }; const refresh = (): void => { maybeSetCollapsed(); maybeSetOnlyEmoji(); updateStatusLinks(); }; useEffect(() => { refresh(); }); const handleMouseDown: React.EventHandler = (e) => { startXY.current = [e.clientX, e.clientY]; }; const handleMouseUp: React.EventHandler = (e) => { if (!startXY.current) return; const target = e.target as HTMLElement; const parentNode = target.parentNode as HTMLElement; const [ startX, startY ] = startXY.current; const [ deltaX, deltaY ] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)]; if (target.localName === 'button' || target.localName === 'a' || (parentNode && (parentNode.localName === 'button' || parentNode.localName === 'a'))) { return; } if (deltaX + deltaY < 5 && e.button === 0 && onClick) { onClick(); } startXY.current = undefined; }; const handleSpoilerClick: React.EventHandler = (e) => { e.preventDefault(); if (onExpandedToggle) { // The parent manages the state onExpandedToggle(); } else { setHidden(!hidden); } }; // const handleCollapsedClick: React.EventHandler = (e) => { // e.preventDefault(); // setCollapsed(!collapsed); // }; const getHtmlContent = (): string => { const { contentHtml: html } = status; if (greentext) return addGreentext(html); return html; }; if (status.content.length === 0) { return null; } const isHidden = onExpandedToggle ? !expanded : hidden; const content = { __html: getHtmlContent() }; const spoilerContent = { __html: status.spoilerHtml }; const directionStyle: React.CSSProperties = { direction: 'ltr' }; const className = classNames('status__content', { 'status__content--with-action': onClick, 'status__content--with-spoiler': status.spoiler_text.length > 0, 'status__content--collapsed': collapsed, 'status__content--big': onlyEmoji, }); if (isRtl(status.search_index)) { directionStyle.direction = 'rtl'; } const readMoreButton = ( ); if (status.spoiler_text.length > 0) { // let mentionsPlaceholder: React.ReactNode = ''; // // const mentionLinks = status.mentions.map(mention => ( // // @{mention.username} // // )).reduce((aggregate, mention) => [...aggregate, mention, ' '], []); // // if (isHidden) { // mentionsPlaceholder =
{mentionLinks}
; // } return (

{/* mentionsPlaceholder */}
{!isHidden && status.poll && typeof status.poll === 'string' && ( )}
); } else if (onClick) { const output = [
, ]; if (collapsed) { output.push(readMoreButton); } if (status.poll && typeof status.poll === 'string') { output.push(); } return <>{output}; } else { const output = [
, ]; if (status.poll && typeof status.poll === 'string') { output.push(); } return <>{output}; } }; export default StatusContent;