Add back an emojifyText function, do it during render

This commit is contained in:
Alex Gleason 2024-11-27 18:24:36 -06:00
parent 3cd479b601
commit c27e4c6556
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
17 changed files with 90 additions and 70 deletions

View File

@ -15,6 +15,7 @@ import VerificationBadge from 'soapbox/components/verification-badge.tsx';
import ActionButton from 'soapbox/features/ui/components/action-button.tsx';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { getAcct } from 'soapbox/utils/accounts.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
import { displayFqn } from 'soapbox/utils/state.ts';
import Badge from './badge.tsx';
@ -233,7 +234,7 @@ const Account = ({
<LinkEl {...linkProps}>
<HStack space={1} alignItems='center' grow>
<Text size='sm' weight='semibold' truncate>
{account.display_name}
{emojifyText(account.display_name, account.emojis)}
</Text>
{account.verified && <VerificationBadge />}

View File

@ -1,6 +1,7 @@
import HStack from 'soapbox/components/ui/hstack.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import { useSoapboxConfig } from 'soapbox/hooks/useSoapboxConfig.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
import { getAcct } from '../utils/accounts.ts';
@ -10,7 +11,7 @@ import VerificationBadge from './verification-badge.tsx';
import type { Account } from 'soapbox/schemas/index.ts';
interface IDisplayName {
account: Pick<Account, 'id' | 'acct' | 'fqn' | 'verified' | 'display_name'>;
account: Pick<Account, 'id' | 'acct' | 'emojis' | 'fqn' | 'verified' | 'display_name'>;
withSuffix?: boolean;
}
@ -25,7 +26,7 @@ const DisplayNameInline: React.FC<IDisplayName> = ({ account, withSuffix = true
weight='normal'
truncate
>
{account.display_name}
{emojifyText(account.display_name, account.emojis)}
</Text>
{verified && <VerificationBadge />}

View File

@ -2,16 +2,15 @@ import HoverRefWrapper from 'soapbox/components/hover-ref-wrapper.tsx';
import HStack from 'soapbox/components/ui/hstack.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import { useSoapboxConfig } from 'soapbox/hooks/useSoapboxConfig.ts';
import { getAcct } from '../utils/accounts.ts';
import { getAcct } from 'soapbox/utils/accounts.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
import VerificationBadge from './verification-badge.tsx';
import type { Account } from 'soapbox/schemas/index.ts';
interface IDisplayName {
account: Pick<Account, 'id' | 'acct' | 'fqn' | 'verified' | 'display_name'>;
account: Pick<Account, 'id' | 'acct' | 'emojis' | 'fqn' | 'verified' | 'display_name'>;
withSuffix?: boolean;
children?: React.ReactNode;
}
@ -27,7 +26,7 @@ const DisplayName: React.FC<IDisplayName> = ({ account, children, withSuffix = t
weight='semibold'
truncate
>
{account.display_name}
{emojifyText(account.display_name, account.emojis)}
</Text>
{verified && <VerificationBadge />}

View File

@ -6,8 +6,8 @@ import { useState, useRef, useLayoutEffect, useMemo, memo } from 'react';
import { FormattedMessage } from 'react-intl';
import Icon from 'soapbox/components/icon.tsx';
import { getTextDirection } from '../utils/rtl.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
import { getTextDirection } from 'soapbox/utils/rtl.ts';
import HashtagLink from './hashtag-link.tsx';
import Markup from './markup.tsx';
@ -90,27 +90,7 @@ const StatusContent: React.FC<IStatusContent> = ({
}
if (domNode instanceof DOMText) {
const parts: Array<string | JSX.Element> = [];
const textNodes = domNode.data.split(/:\w+:/);
const shortcodes = [...domNode.data.matchAll(/:(\w+):/g)];
for (let i = 0; i < textNodes.length; i++) {
parts.push(textNodes[i]);
if (shortcodes[i]) {
const [text, shortcode] = shortcodes[i];
const customEmoji = status.emojis.find((e) => e.shortcode === shortcode);
if (customEmoji) {
parts.push(<img key={i} src={customEmoji.url} alt={shortcode} className='inline-block h-[1em]' />);
} else {
parts.push(text);
}
}
}
return <>{parts}</>;
return emojifyText(domNode.data, status.emojis.toJS());
}
if (domNode instanceof Element && domNode.name === 'a') {

View File

@ -20,6 +20,7 @@ import QuotedStatus from 'soapbox/features/status/containers/quoted-status-conta
import { HotKeys } from 'soapbox/features/ui/components/hotkeys.tsx';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useSettings } from 'soapbox/hooks/useSettings.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
import { defaultMediaVisibility, textForScreenReader, getActualStatus } from 'soapbox/utils/status.ts';
import EventPreview from './event-preview.tsx';
@ -232,7 +233,7 @@ const Status: React.FC<IStatus> = (props) => {
>
<bdi className='truncate'>
<strong className='text-gray-800 dark:text-gray-200'>
{status.account.display_name}
{emojifyText(status.account.display_name, status.account.emojis)}
</strong>
</bdi>
</Link>
@ -263,7 +264,7 @@ const Status: React.FC<IStatus> = (props) => {
<Link to={`/@${status.account.acct}`} className='hover:underline'>
<bdi className='truncate'>
<strong className='text-gray-800 dark:text-gray-200'>
{status.account.display_name}
{emojifyText(status.account.display_name, status.account.emojis)}
</strong>
</bdi>
</Link>

View File

@ -1,19 +0,0 @@
interface ICustomEmoji {
/** Custom emoji URL. */
url: string;
/** Image alt text, usually the shortcode. */
alt?: string;
/** `img` tag className. Default: `h-[1em]` */
className?: string;
}
/** A single custom emoji image. */
const CustomEmoji: React.FC<ICustomEmoji> = (props): JSX.Element | null => {
const { url, alt, className = 'h-[1em]' } = props;
return (
<img src={url} alt={alt} className={className} />
);
};
export default CustomEmoji;

View File

@ -8,6 +8,7 @@ import Stack from 'soapbox/components/ui/stack.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import VerificationBadge from 'soapbox/components/verification-badge.tsx';
import useAccountSearch from 'soapbox/queries/search.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
import type { Account } from 'soapbox/types/entities.ts';
@ -41,7 +42,10 @@ const Results = ({ accountSearchResult, onSelect }: IResults) => {
<Stack alignItems='start'>
<div className='flex grow items-center space-x-1'>
<Text weight='bold' size='sm' truncate>{account.display_name}</Text>
<Text weight='bold' size='sm' truncate>
{emojifyText(account.display_name, account.emojis)}
</Text>
{account.verified && <VerificationBadge />}
</div>
<Text size='sm' weight='medium' theme='muted' direction='ltr' truncate>@{account.acct}</Text> {/* eslint-disable-line formatjs/no-literal-string-in-jsx */}

View File

@ -8,6 +8,7 @@ import Stack from 'soapbox/components/ui/stack.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import VerificationBadge from 'soapbox/components/verification-badge.tsx';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
import ActionButton from '../ui/components/action-button.tsx';
import { HotKeys } from '../ui/components/hotkeys.tsx';
@ -47,7 +48,7 @@ const SuggestionItem: React.FC<ISuggestionItem> = ({ accountId }) => {
size='sm'
className='max-w-[95%]'
>
{account.display_name}
{emojifyText(account.display_name, account.emojis)}
</Text>
{account.verified && <VerificationBadge />}

View File

@ -37,6 +37,7 @@ import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { useInstance } from 'soapbox/hooks/useInstance.ts';
import { makeGetNotification } from 'soapbox/selectors/index.ts';
import toast from 'soapbox/toast.tsx';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
import { NotificationType, validType } from 'soapbox/utils/notification.ts';
import type { ScrollPosition } from 'soapbox/components/status.tsx';
@ -57,7 +58,7 @@ const buildLink = (account: AccountEntity): JSX.Element => (
title={account.acct}
to={`/@${account.acct}`}
>
{account.display_name}
{emojifyText(account.display_name, account.emojis)}
</Link>
</bdi>
);

View File

@ -7,6 +7,7 @@ import Spinner from 'soapbox/components/ui/spinner.tsx';
import AccountContainer from 'soapbox/containers/account-container.tsx';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { makeGetAccount } from 'soapbox/selectors/index.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
const getAccount = makeGetAccount();
@ -32,7 +33,7 @@ const FamiliarFollowersModal = ({ accountId, onClose }: IFamiliarFollowersModal)
<FormattedMessage
id='account.familiar_followers.empty'
defaultMessage='No one you know follows {name}.'
values={{ name: account.display_name }}
values={{ name: emojifyText(account.display_name, account.emojis) }}
/>
);
@ -57,7 +58,7 @@ const FamiliarFollowersModal = ({ accountId, onClose }: IFamiliarFollowersModal)
<FormattedMessage
id='column.familiar_followers'
defaultMessage='People you know following {name}'
values={{ name: account?.display_name ?? '' }}
values={{ name: account ? emojifyText(account.display_name, account.emojis) : '' }}
/>
)}
onClose={onClickClose}

View File

@ -15,6 +15,7 @@ import Modal from 'soapbox/components/ui/modal.tsx';
import Stack from 'soapbox/components/ui/stack.tsx';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { ZapSplitData } from 'soapbox/schemas/zap-split.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
import type { Account as AccountEntity } from 'soapbox/types/entities.ts';
@ -48,7 +49,13 @@ const ZapInvoiceModal: React.FC<IZapInvoice> = ({ account, invoice, splitData, o
};
const renderTitle = () => {
return <FormattedMessage id='zap.send_to' defaultMessage='Send zaps to {target}' values={{ target: account.display_name }} />;
return (
<FormattedMessage
id='zap.send_to'
defaultMessage='Send zaps to {target}'
values={{ target: emojifyText(account.display_name, account.emojis) }}
/>
);
};
const handleNext = () => {

View File

@ -8,6 +8,7 @@ import Button from 'soapbox/components/ui/button.tsx';
import HStack from 'soapbox/components/ui/hstack.tsx';
import Stack from 'soapbox/components/ui/stack.tsx';
import { ZapSplitData } from 'soapbox/schemas/zap-split.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
const messages = defineMessages({
zap_open_wallet: { id: 'zap.open_wallet', defaultMessage: 'Open Wallet' },
@ -31,7 +32,11 @@ const ZapSplit = ({ zapData, zapAmount, invoice, onNext, isLastStep, onFinish }:
const renderTitleQr = () => {
return (
<div className='max-w-[280px] truncate'>
<FormattedMessage id='zap.send_to' defaultMessage='Send zaps to {target}' values={{ target: account.display_name }} />
<FormattedMessage
id='zap.send_to'
defaultMessage='Send zaps to {target}'
values={{ target: emojifyText(account.display_name, account.emojis) }}
/>
</div>
);
};

View File

@ -14,6 +14,7 @@ import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
import { makeGetAccount } from 'soapbox/selectors/index.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
import type { Account } from 'soapbox/schemas/index.ts';
@ -51,7 +52,7 @@ const ProfileFamiliarFollowers: React.FC<IProfileFamiliarFollowers> = ({ account
<Link className='inline-block text-primary-600 hover:underline dark:text-accent-blue' to={`/@${account.acct}`}>
<HStack space={1} alignItems='center' grow>
<Text size='sm' theme='primary' truncate>
{account.display_name}
{emojifyText(account.display_name, account.emojis)}
</Text>
{account.verified && <VerificationBadge />}

View File

@ -15,6 +15,7 @@ import Stack from 'soapbox/components/ui/stack.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { useSoapboxConfig } from 'soapbox/hooks/useSoapboxConfig.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
import { capitalize } from 'soapbox/utils/strings.ts';
import ProfileFamiliarFollowers from './profile-familiar-followers.tsx';
@ -151,7 +152,7 @@ const ProfileInfoPanel: React.FC<IProfileInfoPanel> = ({ account, username }) =>
<Stack>
<HStack space={1} alignItems='center'>
<Text size='lg' weight='bold' truncate>
{deactivated ? intl.formatMessage(messages.deactivated) : account.display_name}
{deactivated ? intl.formatMessage(messages.deactivated) : emojifyText(account.display_name, account.emojis)}
</Text>
{account.bot && <Badge slug='bot' title={intl.formatMessage(messages.bot)} />}

View File

@ -10,6 +10,7 @@ import Text from 'soapbox/components/ui/text.tsx';
import VerificationBadge from 'soapbox/components/verification-badge.tsx';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { getAcct } from 'soapbox/utils/accounts.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
import { shortNumberFormat } from 'soapbox/utils/numbers.tsx';
import { displayFqn } from 'soapbox/utils/state.ts';
@ -59,7 +60,7 @@ const UserPanel: React.FC<IUserPanel> = ({ accountId, action, badges, domain })
<Link to={`/@${account.acct}`}>
<HStack space={1} alignItems='center'>
<Text size='lg' weight='bold' truncate>
{account.display_name}
{emojifyText(account.display_name, account.emojis)}
</Text>
{verified && <VerificationBadge />}

View File

@ -22,6 +22,7 @@ import Stack from 'soapbox/components/ui/stack.tsx';
import SvgIcon from 'soapbox/components/ui/svg-icon.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
import ZapButton from './zap-button/zap-button.tsx';
@ -113,7 +114,11 @@ const ZapPayRequestForm = ({ account, status, onClose }: IZapPayRequestForm) =>
/>
<Text weight='semibold'>
<FormattedMessage id='zap.send_to' defaultMessage='Send zaps to {target}' values={{ target: account.display_name }} />
<FormattedMessage
id='zap.send_to'
defaultMessage='Send zaps to {target}'
values={{ target: emojifyText(account.display_name, account.emojis) }}
/>
</Text>
<Avatar src={account.avatar} size={50} />
<DisplayNameInline account={account} />
@ -141,12 +146,15 @@ const ZapPayRequestForm = ({ account, status, onClose }: IZapPayRequestForm) =>
{hasZapSplit && <p className='absolute right-0 font-bold sm:-right-6 sm:text-xl'>sats</p>}
</div>
{hasZapSplit && <span className='flex justify-center text-xs'>
<FormattedMessage
id='zap.split_message.receiver'
defaultMessage='{receiver} will receive {amountReceiver} sats*' values={{ receiver: account.display_name, amountReceiver: zapSplitData.receiveAmount }}
/>
</span>}
{hasZapSplit && (
<span className='flex justify-center text-xs'>
<FormattedMessage
id='zap.split_message.receiver'
defaultMessage='{receiver} will receive {amountReceiver} sats*'
values={{ receiver: emojifyText(account.display_name, account.emojis), amountReceiver: zapSplitData.receiveAmount }}
/>
</span>
)}
</Stack>
@ -162,7 +170,8 @@ const ZapPayRequestForm = ({ account, status, onClose }: IZapPayRequestForm) =>
<span className='text-[10px] sm:text-xs'>
<FormattedMessage
id='zap.split_message.deducted'
defaultMessage='{amountDeducted} sats will deducted*' values={{ instance: account.display_name, amountDeducted: zapSplitData.splitAmount }}
defaultMessage='{amountDeducted} sats will deducted*'
values={{ instance: emojifyText(account.display_name, account.emojis), amountDeducted: zapSplitData.splitAmount }}
/>
</span>

26
src/utils/emojify.tsx Normal file
View File

@ -0,0 +1,26 @@
import { CustomEmoji } from 'soapbox/schemas/custom-emoji.ts';
/** Given text and a list of custom emojis, return JSX with the emojis rendered as `<img>` elements. */
export function emojifyText(text: string, emojis: CustomEmoji[]): JSX.Element {
const parts: Array<string | JSX.Element> = [];
const textNodes = text.split(/:\w+:/);
const shortcodes = [...text.matchAll(/:(\w+):/g)];
for (let i = 0; i < textNodes.length; i++) {
parts.push(textNodes[i]);
if (shortcodes[i]) {
const [match, shortcode] = shortcodes[i];
const customEmoji = emojis.find((e) => e.shortcode === shortcode);
if (customEmoji) {
parts.push(<img key={i} src={customEmoji.url} alt={shortcode} className='inline h-[1em] align-text-bottom' />);
} else {
parts.push(match);
}
}
}
return <>{parts}</>;
}