Fix/simplify onlyEmoji
This commit is contained in:
parent
d1738273ed
commit
1738f4365a
|
@ -102,10 +102,6 @@ body.underline-links [data-markup] a {
|
||||||
@apply underline;
|
@apply underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-markup].big-emoji img.emojione {
|
|
||||||
@apply inline w-9 h-9 p-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-markup] .status-link {
|
[data-markup] .status-link {
|
||||||
@apply hover:underline text-primary-600 dark:text-accent-blue hover:text-primary-800 dark:hover:text-accent-blue;
|
@apply hover:underline text-primary-600 dark:text-accent-blue hover:text-primary-800 dark:hover:text-accent-blue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import chevronRightIcon from '@tabler/icons/outline/chevron-right.svg';
|
import chevronRightIcon from '@tabler/icons/outline/chevron-right.svg';
|
||||||
import clsx from 'clsx';
|
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 } from 'html-react-parser';
|
||||||
import { useState, useRef, useLayoutEffect, useMemo, memo } from 'react';
|
import { useState, useRef, useLayoutEffect, useMemo, memo } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import Icon from 'soapbox/components/icon.tsx';
|
import Icon from 'soapbox/components/icon.tsx';
|
||||||
import { onlyEmoji as isOnlyEmoji } from 'soapbox/utils/rich-content.ts';
|
|
||||||
|
|
||||||
import { getTextDirection } from '../utils/rtl.ts';
|
import { getTextDirection } from '../utils/rtl.ts';
|
||||||
|
|
||||||
|
@ -49,10 +49,14 @@ const StatusContent: React.FC<IStatusContent> = ({
|
||||||
textSize = 'md',
|
textSize = 'md',
|
||||||
}) => {
|
}) => {
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
const [onlyEmoji, setOnlyEmoji] = useState(false);
|
|
||||||
|
|
||||||
const node = useRef<HTMLDivElement>(null);
|
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 => {
|
const maybeSetCollapsed = (): void => {
|
||||||
if (!node.current) return;
|
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(() => {
|
useLayoutEffect(() => {
|
||||||
maybeSetCollapsed();
|
maybeSetCollapsed();
|
||||||
maybeSetOnlyEmoji();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const parsedHtml = useMemo((): string => {
|
const parsedHtml = useMemo((): string => {
|
||||||
|
@ -149,7 +143,7 @@ const StatusContent: React.FC<IStatusContent> = ({
|
||||||
'cursor-pointer': onClick,
|
'cursor-pointer': onClick,
|
||||||
'whitespace-normal': withSpoiler,
|
'whitespace-normal': withSpoiler,
|
||||||
'max-h-[300px]': collapsed,
|
'max-h-[300px]': collapsed,
|
||||||
'leading-normal big-emoji': onlyEmoji,
|
'leading-normal !text-4xl': isOnlyEmoji,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (onClick) {
|
if (onClick) {
|
||||||
|
@ -184,7 +178,7 @@ const StatusContent: React.FC<IStatusContent> = ({
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
key='content'
|
key='content'
|
||||||
className={clsx(baseClassName, {
|
className={clsx(baseClassName, {
|
||||||
'leading-normal big-emoji': onlyEmoji,
|
'leading-normal !text-4xl': isOnlyEmoji,
|
||||||
})}
|
})}
|
||||||
direction={direction}
|
direction={direction}
|
||||||
lang={status.language || undefined}
|
lang={status.language || undefined}
|
||||||
|
|
|
@ -6,12 +6,12 @@ import moodSmileIcon from '@tabler/icons/outline/mood-smile.svg';
|
||||||
import trashIcon from '@tabler/icons/outline/trash.svg';
|
import trashIcon from '@tabler/icons/outline/trash.svg';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import graphemesplit from 'graphemesplit';
|
||||||
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
||||||
import escape from 'lodash/escape';
|
import escape from 'lodash/escape';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
|
||||||
import { openModal } from 'soapbox/actions/modals.ts';
|
import { openModal } from 'soapbox/actions/modals.ts';
|
||||||
import { initReport, ReportableEntities } from 'soapbox/actions/reports.ts';
|
import { initReport, ReportableEntities } from 'soapbox/actions/reports.ts';
|
||||||
import DropdownMenu from 'soapbox/components/dropdown-menu/index.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 { ChatKeys, IChat, useChatActions } from 'soapbox/queries/chats.ts';
|
||||||
import { queryClient } from 'soapbox/queries/client.ts';
|
import { queryClient } from 'soapbox/queries/client.ts';
|
||||||
import { stripHTML } from 'soapbox/utils/html.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 ChatMessageReactionWrapper from './chat-message-reaction-wrapper/chat-message-reaction-wrapper.tsx';
|
||||||
import ChatMessageReaction from './chat-message-reaction.tsx';
|
import ChatMessageReaction from './chat-message-reaction.tsx';
|
||||||
|
@ -100,10 +99,9 @@ const ChatMessage = (props: IChatMessage) => {
|
||||||
&& lastReadMessageTimestamp >= new Date(chatMessage.created_at);
|
&& lastReadMessageTimestamp >= new Date(chatMessage.created_at);
|
||||||
|
|
||||||
const isOnlyEmoji = useMemo(() => {
|
const isOnlyEmoji = useMemo(() => {
|
||||||
const hiddenEl = document.createElement('div');
|
const textContent = new DOMParser().parseFromString(content, 'text/html').body.firstChild?.textContent;
|
||||||
hiddenEl.innerHTML = content;
|
return Boolean(textContent && /^\p{Extended_Pictographic}+$/u.test(textContent) && (graphemesplit(textContent).length <= BIG_EMOJI_LIMIT));
|
||||||
return onlyEmoji(hiddenEl, BIG_EMOJI_LIMIT, false);
|
}, [content]);
|
||||||
}, []);
|
|
||||||
|
|
||||||
const emojiReactionRows = useMemo(() => {
|
const emojiReactionRows = useMemo(() => {
|
||||||
if (!chatMessage.emoji_reactions) {
|
if (!chatMessage.emoji_reactions) {
|
||||||
|
@ -302,7 +300,7 @@ const ChatMessage = (props: IChatMessage) => {
|
||||||
'[&_.mention]:text-white dark:[&_.mention]:white': isMyMessage,
|
'[&_.mention]:text-white dark:[&_.mention]:white': isMyMessage,
|
||||||
'bg-primary-500 text-white': isMyMessage,
|
'bg-primary-500 text-white': isMyMessage,
|
||||||
'bg-gray-200 dark:bg-gray-800 text-gray-900 dark:text-gray-100': !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}
|
ref={setBubbleRef}
|
||||||
|
|
|
@ -130,15 +130,6 @@
|
||||||
padding-left: 4px;
|
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 {
|
.compose-form-warning {
|
||||||
strong {
|
strong {
|
||||||
@apply font-medium;
|
@apply font-medium;
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
Loading…
Reference in New Issue