EmojiText component replacing dangerouslySetInnerHTML

This commit is contained in:
Lim Chee Aun 2023-06-14 17:37:41 +08:00
parent d2826085e1
commit 3b3e0e6fde
8 changed files with 69 additions and 46 deletions

View file

@ -2,11 +2,11 @@ import './account-block.css';
import { useNavigate } from 'react-router-dom';
import emojifyText from '../utils/emojify-text';
import niceDateTime from '../utils/nice-date-time';
import states from '../utils/states';
import Avatar from './avatar';
import EmojiText from './emoji-text';
function AccountBlock({
skeleton,
@ -46,7 +46,6 @@ function AccountBlock({
lastStatusAt,
bot,
} = account;
const displayNameWithEmoji = emojifyText(displayName, emojis);
const [_, acct1, acct2] = acct.match(/([^@]+)(@.+)/i) || [, acct];
return (
@ -72,11 +71,9 @@ function AccountBlock({
<Avatar url={avatar} size={avatarSize} squircle={bot} />
<span>
{displayName ? (
<b
dangerouslySetInnerHTML={{
__html: displayNameWithEmoji,
}}
/>
<b>
<EmojiText text={displayName} emojis={emojis} />
</b>
) : (
<b>{username}</b>
)}

View file

@ -4,7 +4,6 @@ import { Menu, MenuDivider, MenuItem, SubMenu } from '@szhsin/react-menu';
import { useEffect, useReducer, useRef, useState } from 'preact/hooks';
import { api } from '../utils/api';
import emojifyText from '../utils/emojify-text';
import enhanceContent from '../utils/enhance-content';
import getHTMLText from '../utils/getHTMLText';
import handleContentLinks from '../utils/handle-content-links';
@ -16,6 +15,7 @@ import store from '../utils/store';
import AccountBlock from './account-block';
import Avatar from './avatar';
import EmojiText from './emoji-text';
import Icon from './icon';
import Link from './link';
import ListAddEdit from './list-add-edit';
@ -301,11 +301,7 @@ function AccountInfo({
key={name}
>
<b>
<span
dangerouslySetInnerHTML={{
__html: emojifyText(name, emojis),
}}
/>{' '}
<EmojiText text={name} emojis={emojis} />{' '}
{!!verifiedAt && <Icon icon="check-circle" size="s" />}
</b>
<p

View file

@ -0,0 +1,42 @@
function EmojiText({ text, emojis }) {
if (!text) return '';
if (!emojis?.length) return text;
if (text.indexOf(':') === -1) return text;
const components = [];
let lastIndex = 0;
emojis.forEach((shortcodeObj) => {
const { shortcode, staticUrl, url } = shortcodeObj;
const regex = new RegExp(`:${shortcode}:`, 'g');
let match;
while ((match = regex.exec(text))) {
const beforeText = text.substring(lastIndex, match.index);
if (beforeText) {
components.push(beforeText);
}
components.push(
<img
src={url}
alt={shortcode}
class="shortcode-emoji emoji"
width="12"
height="12"
loading="lazy"
decoding="async"
/>,
);
lastIndex = match.index + match[0].length;
}
});
const afterText = text.substring(lastIndex);
if (afterText) {
components.push(afterText);
}
return components;
}
export default EmojiText;

View file

@ -1,9 +1,9 @@
import './name-text.css';
import emojifyText from '../utils/emojify-text';
import states from '../utils/states';
import Avatar from './avatar';
import EmojiText from './emoji-text';
function NameText({
account,
@ -18,8 +18,6 @@ function NameText({
account;
let { username } = account;
const displayNameWithEmoji = emojifyText(displayName, emojis);
const trimmedUsername = username.toLowerCase().trim();
const trimmedDisplayName = (displayName || '').toLowerCase().trim();
const shortenedDisplayName = trimmedDisplayName
@ -58,11 +56,9 @@ function NameText({
)}
{displayName && !short ? (
<>
<b
dangerouslySetInnerHTML={{
__html: displayNameWithEmoji,
}}
/>
<b>
<EmojiText text={displayName} emojis={emojis} />
</b>
{!showAcct && username && (
<>
{' '}

View file

@ -1,8 +1,8 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import emojifyText from '../utils/emojify-text';
import shortenNumber from '../utils/shorten-number';
import EmojiText from './emoji-text';
import Icon from './icon';
import RelativeTime from './relative-time';
@ -112,11 +112,9 @@ export default function Poll({
}}
>
<div class="poll-option-title">
<span
dangerouslySetInnerHTML={{
__html: emojifyText(title, emojis),
}}
/>
<span>
<EmojiText text={title} emojis={emojis} />
</span>
{voted && ownVotes.includes(i) && (
<>
{' '}
@ -179,12 +177,9 @@ export default function Poll({
disabled={uiState === 'loading'}
readOnly={readOnly}
/>
<span
class="poll-option-title"
dangerouslySetInnerHTML={{
__html: emojifyText(title, emojis),
}}
/>
<span class="poll-option-title">
<EmojiText text={title} emojis={emojis} />
</span>
</label>
</div>
);

View file

@ -26,12 +26,12 @@ import { useSnapshot } from 'valtio';
import { snapshot } from 'valtio/vanilla';
import AccountBlock from '../components/account-block';
import EmojiText from '../components/emoji-text';
import Loader from '../components/loader';
import Modal from '../components/modal';
import NameText from '../components/name-text';
import Poll from '../components/poll';
import { api } from '../utils/api';
import emojifyText from '../utils/emojify-text';
import enhanceContent from '../utils/enhance-content';
import getTranslateTargetLanguage from '../utils/get-translate-target-language';
import getHTMLText from '../utils/getHTMLText';
@ -926,11 +926,9 @@ function Status({
ref={spoilerContentRef}
data-read-more={readMoreText}
>
<p
dangerouslySetInnerHTML={{
__html: emojifyText(spoilerText, emojis),
}}
/>
<p>
<EmojiText text={spoilerText} emojis={emojis} />
</p>
</div>
<button
class={`light spoiler ${showSpoiler ? 'spoiling' : ''}`}

View file

@ -4,12 +4,12 @@ import { useParams, useSearchParams } from 'react-router-dom';
import { useSnapshot } from 'valtio';
import AccountInfo from '../components/account-info';
import EmojiText from '../components/emoji-text';
import Icon from '../components/icon';
import Link from '../components/link';
import Menu2 from '../components/menu2';
import Timeline from '../components/timeline';
import { api } from '../utils/api';
import emojifyText from '../utils/emojify-text';
import showToast from '../utils/show-toast';
import states from '../utils/states';
import { saveStatus } from '../utils/states';
@ -236,11 +236,9 @@ function AccountStatuses() {
// };
// }}
>
<b
dangerouslySetInnerHTML={{
__html: emojifyText(displayName, emojis),
}}
/>
<b>
<EmojiText text={displayName} emojis={emojis} />
</b>
<div>
<span>@{acct}</span>
</div>

View file

@ -1,13 +1,14 @@
function emojifyText(text, emojis = []) {
if (!text) return '';
if (!emojis.length) return text;
if (text.indexOf(':') === -1) return text;
// Replace shortcodes in text with emoji
// emojis = [{ shortcode: 'smile', url: 'https://example.com/emoji.png' }]
emojis.forEach((emoji) => {
const { shortcode, staticUrl, url } = emoji;
text = text.replace(
new RegExp(`:${shortcode}:`, 'g'),
`<img class="shortcode-emoji emoji" src="${url}" alt=":${shortcode}:" width="12" height="12" loading="lazy" />`,
`<img class="shortcode-emoji emoji" src="${url}" alt=":${shortcode}:" width="12" height="12" loading="lazy" decoding="async" />`,
);
});
// console.log(text, emojis);