diff --git a/src/components/status-content.tsx b/src/components/status-content.tsx index 3ee565f0e..265c53323 100644 --- a/src/components/status-content.tsx +++ b/src/components/status-content.tsx @@ -1,10 +1,11 @@ import chevronRightIcon from '@tabler/icons/outline/chevron-right.svg'; import clsx from 'clsx'; import graphemesplit from 'graphemesplit'; -import parse, { Element, type HTMLReactParserOptions, domToReact, type DOMNode } from 'html-react-parser'; +import parse, { Element, type HTMLReactParserOptions, domToReact, type DOMNode, Text as DOMText } from 'html-react-parser'; import { useState, useRef, useLayoutEffect, useMemo, memo } from 'react'; import { FormattedMessage } from 'react-intl'; +import { useCustomEmojis } from 'soapbox/api/hooks/useCustomEmojis.ts'; import Icon from 'soapbox/components/icon.tsx'; import { getTextDirection } from '../utils/rtl.ts'; @@ -51,11 +52,12 @@ const StatusContent: React.FC = ({ const [collapsed, setCollapsed] = useState(false); const node = useRef(null); + const { customEmojis } = useCustomEmojis(); const isOnlyEmoji = useMemo(() => { - const textContent = new DOMParser().parseFromString(status.contentHtml, 'text/html').body.firstChild?.textContent; - return Boolean(textContent && /^\p{Extended_Pictographic}+$/u.test(textContent) && (graphemesplit(textContent).length <= BIG_EMOJI_LIMIT)); - }, [status.contentHtml]); + const textContent = new DOMParser().parseFromString(status.content, 'text/html').body.firstChild?.textContent ?? ''; + return Boolean(/^\p{Extended_Pictographic}+$/u.test(textContent) && (graphemesplit(textContent).length <= BIG_EMOJI_LIMIT)); + }, [status.content]); const maybeSetCollapsed = (): void => { if (!node.current) return; @@ -72,8 +74,8 @@ const StatusContent: React.FC = ({ }); const parsedHtml = useMemo((): string => { - return translatable && status.translation ? status.translation.get('content')! : status.contentHtml; - }, [status.contentHtml, status.translation]); + return translatable && status.translation ? status.translation.get('content')! : status.content; + }, [status.content, status.translation]); if (status.content.length === 0) { return null; @@ -89,6 +91,22 @@ const StatusContent: React.FC = ({ return null; } + if (domNode instanceof DOMText) { + const parts = domNode.data.split(' ').map((part) => { + const match = part.match(/^:(\w+):$/); + if (!match) return part; + + const [, shortcode] = match; + const customEmoji = customEmojis.find((e) => e.shortcode === shortcode); + + if (!customEmoji) return part; + + return {part}; + }); + + return <>{parts}; + } + if (domNode instanceof Element && domNode.name === 'a') { const classes = domNode.attribs.class?.split(' ');