Fix/simplify onlyEmoji

This commit is contained in:
Alex Gleason 2024-11-15 18:29:01 -06:00
parent d1738273ed
commit 1738f4365a
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
5 changed files with 13 additions and 58 deletions

View File

@ -102,10 +102,6 @@ body.underline-links [data-markup] a {
@apply underline;
}
[data-markup].big-emoji img.emojione {
@apply inline w-9 h-9 p-1;
}
[data-markup] .status-link {
@apply hover:underline text-primary-600 dark:text-accent-blue hover:text-primary-800 dark:hover:text-accent-blue;
}

View File

@ -1,11 +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 { useState, useRef, useLayoutEffect, useMemo, memo } from 'react';
import { FormattedMessage } from 'react-intl';
import Icon from 'soapbox/components/icon.tsx';
import { onlyEmoji as isOnlyEmoji } from 'soapbox/utils/rich-content.ts';
import { getTextDirection } from '../utils/rtl.ts';
@ -49,10 +49,14 @@ const StatusContent: React.FC<IStatusContent> = ({
textSize = 'md',
}) => {
const [collapsed, setCollapsed] = useState(false);
const [onlyEmoji, setOnlyEmoji] = useState(false);
const node = useRef<HTMLDivElement>(null);
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 maybeSetCollapsed = (): void => {
if (!node.current) return;
@ -63,18 +67,8 @@ const StatusContent: React.FC<IStatusContent> = ({
}
};
const maybeSetOnlyEmoji = (): void => {
if (!node.current) return;
const only = isOnlyEmoji(node.current, BIG_EMOJI_LIMIT, true);
if (only !== onlyEmoji) {
setOnlyEmoji(only);
}
};
useLayoutEffect(() => {
maybeSetCollapsed();
maybeSetOnlyEmoji();
});
const parsedHtml = useMemo((): string => {
@ -149,7 +143,7 @@ const StatusContent: React.FC<IStatusContent> = ({
'cursor-pointer': onClick,
'whitespace-normal': withSpoiler,
'max-h-[300px]': collapsed,
'leading-normal big-emoji': onlyEmoji,
'leading-normal !text-4xl': isOnlyEmoji,
});
if (onClick) {
@ -184,7 +178,7 @@ const StatusContent: React.FC<IStatusContent> = ({
tabIndex={0}
key='content'
className={clsx(baseClassName, {
'leading-normal big-emoji': onlyEmoji,
'leading-normal !text-4xl': isOnlyEmoji,
})}
direction={direction}
lang={status.language || undefined}

View File

@ -6,12 +6,12 @@ import moodSmileIcon from '@tabler/icons/outline/mood-smile.svg';
import trashIcon from '@tabler/icons/outline/trash.svg';
import { useMutation } from '@tanstack/react-query';
import clsx from 'clsx';
import graphemesplit from 'graphemesplit';
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import escape from 'lodash/escape';
import { useMemo, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { openModal } from 'soapbox/actions/modals.ts';
import { initReport, ReportableEntities } from 'soapbox/actions/reports.ts';
import DropdownMenu from 'soapbox/components/dropdown-menu/index.ts';
@ -27,7 +27,6 @@ import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
import { ChatKeys, IChat, useChatActions } from 'soapbox/queries/chats.ts';
import { queryClient } from 'soapbox/queries/client.ts';
import { stripHTML } from 'soapbox/utils/html.ts';
import { onlyEmoji } from 'soapbox/utils/rich-content.ts';
import ChatMessageReactionWrapper from './chat-message-reaction-wrapper/chat-message-reaction-wrapper.tsx';
import ChatMessageReaction from './chat-message-reaction.tsx';
@ -100,10 +99,9 @@ const ChatMessage = (props: IChatMessage) => {
&& lastReadMessageTimestamp >= new Date(chatMessage.created_at);
const isOnlyEmoji = useMemo(() => {
const hiddenEl = document.createElement('div');
hiddenEl.innerHTML = content;
return onlyEmoji(hiddenEl, BIG_EMOJI_LIMIT, false);
}, []);
const textContent = new DOMParser().parseFromString(content, 'text/html').body.firstChild?.textContent;
return Boolean(textContent && /^\p{Extended_Pictographic}+$/u.test(textContent) && (graphemesplit(textContent).length <= BIG_EMOJI_LIMIT));
}, [content]);
const emojiReactionRows = useMemo(() => {
if (!chatMessage.emoji_reactions) {
@ -302,7 +300,7 @@ const ChatMessage = (props: IChatMessage) => {
'[&_.mention]:text-white dark:[&_.mention]:white': isMyMessage,
'bg-primary-500 text-white': isMyMessage,
'bg-gray-200 dark:bg-gray-800 text-gray-900 dark:text-gray-100': !isMyMessage,
'!bg-transparent !p-0 emoji-lg': isOnlyEmoji,
'!bg-transparent !p-0 text-4xl': isOnlyEmoji,
})
}
ref={setBubbleRef}

View File

@ -130,15 +130,6 @@
padding-left: 4px;
}
.emoji-lg img.emojione {
width: 36px !important;
height: 36px !important;
}
.emojione {
@apply w-4 h-4 -mt-[0.2ex] mb-[0.2ex] inline-block align-middle object-contain;
}
.compose-form-warning {
strong {
@apply font-medium;

View File

@ -1,24 +0,0 @@
/** Returns `true` if the node contains only emojis, up to a limit */
export const onlyEmoji = (node: HTMLElement, limit = 1, ignoreMentions = true): boolean => {
if (!node) return false;
try {
// Remove mentions before checking content
if (ignoreMentions) {
node = node.cloneNode(true) as HTMLElement;
node.querySelectorAll('a.mention').forEach(m => m.parentNode?.removeChild(m));
}
if (node.textContent?.replace(new RegExp(' ', 'g'), '') !== '') return false;
const emojis = Array.from(node.querySelectorAll('img.emojione'));
if (emojis.length === 0) return false;
if (emojis.length > limit) return false;
const images = Array.from(node.querySelectorAll('img'));
if (images.length > emojis.length) return false;
return true;
} catch (e) {
// If anything in here crashes, skipping it is inconsequential.
console.error(e);
return false;
}
};