diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts
index 9651842c9..37c37b004 100644
--- a/packages/pl-api/lib/entities/account.ts
+++ b/packages/pl-api/lib/entities/account.ts
@@ -9,13 +9,38 @@ import { coerceObject, datetimeSchema, filteredArray } from './utils';
const filterBadges = (tags?: string[]) =>
tags?.filter(tag => tag.startsWith('badge:')).map(tag => v.parse(roleSchema, { id: tag, name: tag.replace(/^badge:/, '') }));
+const getDomainFromURL = (account: any): string => {
+ try {
+ const url = account.url;
+ return new URL(url).host;
+ } catch {
+ return '';
+ }
+};
+
+const guessFqn = (account: any): string => {
+ const acct = account.acct;
+ const [user, domain] = acct.split('@');
+
+ if (domain) {
+ return acct;
+ } else {
+ return [user, getDomainFromURL(account)].join('@');
+ }
+};
+
const preprocessAccount = v.transform((account: any) => {
if (!account?.acct) return null;
const username = account.username || account.acct.split('@')[0];
+ const fqn = account.fqn || guessFqn(account);
+ const domain = fqn.split('@')[1] || '';
+
return {
username,
+ fqn,
+ domain,
avatar_static: account.avatar_static || account.avatar,
header_static: account.header_static || account.header,
local: typeof account.pleroma?.is_local === 'boolean' ? account.pleroma.is_local : account.acct.split('@')[1] === undefined,
@@ -73,7 +98,7 @@ const baseAccountSchema = v.object({
acct: v.fallback(v.string(), ''),
url: v.pipe(v.string(), v.url()),
display_name: v.fallback(v.string(), ''),
- note: v.fallback(v.string(), ''),
+ note: v.fallback(v.pipe(v.string(), v.transform(note => note === '
' ? '' : note)), ''),
avatar: v.fallback(v.string(), ''),
avatar_static: v.fallback(v.pipe(v.string(), v.url()), ''),
header: v.fallback(v.pipe(v.string(), v.url()), ''),
diff --git a/packages/pl-fe/src/components/account-hover-card.tsx b/packages/pl-fe/src/components/account-hover-card.tsx
index 01b6a22d3..0426fba86 100644
--- a/packages/pl-fe/src/components/account-hover-card.tsx
+++ b/packages/pl-fe/src/components/account-hover-card.tsx
@@ -160,7 +160,7 @@ const AccountHoverCard: React.FC = ({ visible = true }) => {
size='sm'
className='mr-2 rtl:ml-2 rtl:mr-0 [&_br]:hidden [&_p:first-child]:inline [&_p:first-child]:truncate [&_p]:hidden'
>
-
+
)}
diff --git a/packages/pl-fe/src/components/account.tsx b/packages/pl-fe/src/components/account.tsx
index fcb716158..affda2e66 100644
--- a/packages/pl-fe/src/components/account.tsx
+++ b/packages/pl-fe/src/components/account.tsx
@@ -11,6 +11,7 @@ import IconButton from 'pl-fe/components/ui/icon-button';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import VerificationBadge from 'pl-fe/components/verification-badge';
+import Emojify from 'pl-fe/features/emoji/emojify';
import ActionButton from 'pl-fe/features/ui/components/action-button';
import { useAppSelector } from 'pl-fe/hooks/useAppSelector';
import { getAcct } from 'pl-fe/utils/accounts';
@@ -219,8 +220,9 @@ const Account = ({
size='sm'
weight='semibold'
truncate
- dangerouslySetInnerHTML={{ __html: account.display_name_html }}
- />
+ >
+
+
{account.verified && }
@@ -281,8 +283,9 @@ const Account = ({
size='sm'
weight='semibold'
truncate
- dangerouslySetInnerHTML={{ __html: account.display_name_html }}
- />
+ >
+
+
{account.verified && }
@@ -356,7 +359,7 @@ const Account = ({
truncate
size='sm'
>
-
+
)}
diff --git a/packages/pl-fe/src/components/event-preview.tsx b/packages/pl-fe/src/components/event-preview.tsx
index 115d08512..c952cbdb7 100644
--- a/packages/pl-fe/src/components/event-preview.tsx
+++ b/packages/pl-fe/src/components/event-preview.tsx
@@ -8,6 +8,7 @@ import HStack from 'pl-fe/components/ui/hstack';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import VerificationBadge from 'pl-fe/components/verification-badge';
+import Emojify from 'pl-fe/features/emoji/emojify';
import EventActionButton from 'pl-fe/features/event/components/event-action-button';
import EventDate from 'pl-fe/features/event/components/event-date';
import { useAppSelector } from 'pl-fe/hooks/useAppSelector';
@@ -71,7 +72,9 @@ const EventPreview: React.FC = ({ status, className, hideAction,
-
+
+
+
{account.verified && }
diff --git a/packages/pl-fe/src/components/group-card.tsx b/packages/pl-fe/src/components/group-card.tsx
index 66c604e56..1a384169a 100644
--- a/packages/pl-fe/src/components/group-card.tsx
+++ b/packages/pl-fe/src/components/group-card.tsx
@@ -3,6 +3,7 @@ import React from 'react';
import HStack from 'pl-fe/components/ui/hstack';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
+import Emojify from 'pl-fe/features/emoji/emojify';
import GroupHeaderImage from 'pl-fe/features/group/components/group-header-image';
import GroupMemberCount from 'pl-fe/features/group/components/group-member-count';
import GroupPrivacy from 'pl-fe/features/group/components/group-privacy';
@@ -37,7 +38,9 @@ const GroupCard: React.FC = ({ group }) => (
{/* Group Info */}
-
+
+
+
diff --git a/packages/pl-fe/src/components/groups/popover/group-popover.tsx b/packages/pl-fe/src/components/groups/popover/group-popover.tsx
index 88daf39e8..c2f5f5bed 100644
--- a/packages/pl-fe/src/components/groups/popover/group-popover.tsx
+++ b/packages/pl-fe/src/components/groups/popover/group-popover.tsx
@@ -8,6 +8,7 @@ import HStack from 'pl-fe/components/ui/hstack';
import Popover from 'pl-fe/components/ui/popover';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
+import Emojify from 'pl-fe/features/emoji/emojify';
import GroupMemberCount from 'pl-fe/features/group/components/group-member-count';
import GroupPrivacy from 'pl-fe/features/group/components/group-privacy';
@@ -71,7 +72,9 @@ const GroupPopover = (props: IGroupPopoverContainer) => {
{/* Group Info */}
-
+
+
+
diff --git a/packages/pl-fe/src/components/parsed-content.tsx b/packages/pl-fe/src/components/parsed-content.tsx
index 234a86c85..2573a3c4b 100644
--- a/packages/pl-fe/src/components/parsed-content.tsx
+++ b/packages/pl-fe/src/components/parsed-content.tsx
@@ -3,11 +3,14 @@ import DOMPurify from 'isomorphic-dompurify';
import React, { useMemo } from 'react';
import { Link } from 'react-router-dom';
+import Emojify from 'pl-fe/features/emoji/emojify';
+import { makeEmojiMap } from 'pl-fe/utils/normalizers';
+
import HashtagLink from './hashtag-link';
import HoverAccountWrapper from './hover-account-wrapper';
import StatusMention from './status-mention';
-import type { Mention } from 'pl-api';
+import type { CustomEmoji, Mention } from 'pl-api';
const nodesToText = (nodes: Array): string =>
nodes.map(node => node.type === 'text' ? node.data : node.type === 'tag' ? nodesToText(node.children as Array) : '').join('');
@@ -19,14 +22,18 @@ interface IParsedContent {
mentions?: Array;
/** Whether it's a status which has a quote. */
hasQuote?: boolean;
+ /** Related custom emojis. */
+ emojis?: Array;
}
-const ParsedContent: React.FC = (({ html, mentions, hasQuote }) => {
+const ParsedContent: React.FC = (({ html, mentions, hasQuote, emojis }) => {
return useMemo(() => {
if (html.length === 0) {
return null;
}
+ const emojiMap = emojis ? makeEmojiMap(emojis) : undefined;
+
const selectors: Array = [];
// Explicit mentions
@@ -99,6 +106,14 @@ const ParsedContent: React.FC = (({ html, mentions, hasQuote })
return fallback;
}
},
+
+ transform(reactNode) {
+ if (typeof reactNode === 'string') {
+ return ;
+ }
+
+ return reactNode as JSX.Element;
+ },
};
return parse(DOMPurify.sanitize(html, { ADD_ATTR: ['target'], USE_PROFILES: { html: true } }), options);
diff --git a/packages/pl-fe/src/components/polls/poll-footer.tsx b/packages/pl-fe/src/components/polls/poll-footer.tsx
index d619a4bc4..b63aaa4a5 100644
--- a/packages/pl-fe/src/components/polls/poll-footer.tsx
+++ b/packages/pl-fe/src/components/polls/poll-footer.tsx
@@ -12,7 +12,7 @@ import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch';
import RelativeTimestamp from '../relative-timestamp';
import type { Selected } from './poll';
-import type { Poll } from 'pl-fe/normalizers/poll';
+import type { Poll } from 'pl-api';
const messages = defineMessages({
closed: { id: 'poll.closed', defaultMessage: 'Closed' },
diff --git a/packages/pl-fe/src/components/polls/poll-option.tsx b/packages/pl-fe/src/components/polls/poll-option.tsx
index 99cf9c14b..caef4e4fa 100644
--- a/packages/pl-fe/src/components/polls/poll-option.tsx
+++ b/packages/pl-fe/src/components/polls/poll-option.tsx
@@ -7,7 +7,9 @@ import HStack from 'pl-fe/components/ui/hstack';
import Icon from 'pl-fe/components/ui/icon';
import Text from 'pl-fe/components/ui/text';
-import type { Poll } from 'pl-fe/normalizers/poll';
+import { ParsedContent } from '../parsed-content';
+
+import type { Poll } from 'pl-api';
const messages = defineMessages({
voted: { id: 'poll.voted', defaultMessage: 'You voted for this answer' },
@@ -65,8 +67,9 @@ const PollOptionText: React.FC = ({ poll, option, index, active
theme='inherit'
weight='medium'
align='center'
- dangerouslySetInnerHTML={{ __html: option.title_emojified }}
- />
+ >
+
+
@@ -133,9 +136,10 @@ const PollOption: React.FC = (props): JSX.Element | null => {
+ >
+
+
diff --git a/packages/pl-fe/src/components/status.tsx b/packages/pl-fe/src/components/status.tsx
index 3593aec1c..67812204d 100644
--- a/packages/pl-fe/src/components/status.tsx
+++ b/packages/pl-fe/src/components/status.tsx
@@ -12,6 +12,7 @@ import Icon from 'pl-fe/components/ui/icon';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import AccountContainer from 'pl-fe/containers/account-container';
+import Emojify from 'pl-fe/features/emoji/emojify';
import StatusTypeIcon from 'pl-fe/features/status/components/status-type-icon';
import QuotedStatus from 'pl-fe/features/status/containers/quoted-status-container';
import { HotKeys } from 'pl-fe/features/ui/components/hotkeys';
@@ -204,23 +205,17 @@ const Status: React.FC = (props) => {
className='hover:underline'
>
-
+
+
+
),
group: (
-
+
+
+
),
}}
@@ -234,12 +229,9 @@ const Status: React.FC = (props) => {
const renderedAccounts = accounts.slice(0, 2).map(account => !!account && (
-
+
+
+
));
@@ -294,7 +286,7 @@ const Status: React.FC = (props) => {
-
+
diff --git a/packages/pl-fe/src/features/account-timeline/components/moved-note.tsx b/packages/pl-fe/src/features/account-timeline/components/moved-note.tsx
index c98f39a91..da8b93228 100644
--- a/packages/pl-fe/src/features/account-timeline/components/moved-note.tsx
+++ b/packages/pl-fe/src/features/account-timeline/components/moved-note.tsx
@@ -5,6 +5,7 @@ import Account from 'pl-fe/components/account';
import Icon from 'pl-fe/components/icon';
import HStack from 'pl-fe/components/ui/hstack';
import Text from 'pl-fe/components/ui/text';
+import Emojify from 'pl-fe/features/emoji/emojify';
import type { Account as AccountEntity } from 'pl-fe/normalizers/account';
@@ -27,7 +28,7 @@ const MovedNote: React.FC = ({ from, to }) => (
id='notification.move'
defaultMessage='{name} moved to {targetName}'
values={{
- name: ,
+ name: ,
targetName: to.acct,
}}
/>
diff --git a/packages/pl-fe/src/features/compose/components/reply-group-indicator.tsx b/packages/pl-fe/src/features/compose/components/reply-group-indicator.tsx
index 82edc5bc5..d67bae5b8 100644
--- a/packages/pl-fe/src/features/compose/components/reply-group-indicator.tsx
+++ b/packages/pl-fe/src/features/compose/components/reply-group-indicator.tsx
@@ -3,6 +3,7 @@ import { FormattedMessage } from 'react-intl';
import Link from 'pl-fe/components/link';
import Text from 'pl-fe/components/ui/text';
+import Emojify from 'pl-fe/features/emoji/emojify';
import { useAppSelector } from 'pl-fe/hooks/useAppSelector';
import { makeGetStatus } from 'pl-fe/selectors';
@@ -28,10 +29,11 @@ const ReplyGroupIndicator = (props: IReplyGroupIndicator) => {
id='compose.reply_group_indicator.message'
defaultMessage='Posting to {groupLink}'
values={{
- groupLink: ,
+ groupLink: (
+
+
+
+ ),
}}
/>
diff --git a/packages/pl-fe/src/features/directory/components/account-card.tsx b/packages/pl-fe/src/features/directory/components/account-card.tsx
index bfca9ddf1..c31c150be 100644
--- a/packages/pl-fe/src/features/directory/components/account-card.tsx
+++ b/packages/pl-fe/src/features/directory/components/account-card.tsx
@@ -70,13 +70,13 @@ const AccountCard: React.FC = ({ id }) => {
withRelationship={false}
/>
- {!!account.note_emojified && (
+ {!!account.note && (
-
+
)}
diff --git a/packages/pl-fe/src/features/emoji/emojify.tsx b/packages/pl-fe/src/features/emoji/emojify.tsx
new file mode 100644
index 000000000..ac159816a
--- /dev/null
+++ b/packages/pl-fe/src/features/emoji/emojify.tsx
@@ -0,0 +1,103 @@
+import split from 'graphemesplit';
+import React from 'react';
+
+import { makeEmojiMap } from 'pl-fe/utils/normalizers';
+
+import unicodeMapping from './mapping';
+
+import { validEmojiChar } from '.';
+
+import type { CustomEmoji } from 'pl-api';
+
+interface IMaybeEmoji {
+ text: string;
+ emojis: Record;
+}
+
+const MaybeEmoji: React.FC = ({ text, emojis }) => {
+ if (text.length < 3) return text;
+ if (text in emojis) {
+ const emoji = emojis[text];
+ const filename = emoji.static_url;
+
+ if (filename?.length > 0) {
+ return
;
+ }
+ }
+
+ return text;
+};
+
+interface IEmojify {
+ text: string;
+ emojis?: Array | Record;
+}
+
+const Emojify: React.FC = ({ text, emojis = {} }) => React.useMemo(() => {
+ if (Array.isArray(emojis)) emojis = makeEmojiMap(emojis);
+
+ const nodes = [];
+
+ let stack = '';
+ let open = false;
+
+ const clearStack = () => {
+ if (stack.length) nodes.push(stack);
+ open = false;
+ stack = '';
+ };
+
+ for (let c of split(text)) {
+ // convert FE0E selector to FE0F so it can be found in unimap
+ if (c.codePointAt(c.length - 1) === 65038) {
+ c = c.slice(0, -1) + String.fromCodePoint(65039);
+ }
+
+ // unqualified emojis aren't in emoji-mart's mappings so we just add FEOF
+ const unqualified = c + String.fromCodePoint(65039);
+
+ if (c in unicodeMapping) {
+ clearStack();
+
+ const { unified, shortcode } = unicodeMapping[c];
+
+ nodes.push(
+
,
+ );
+ } else if (unqualified in unicodeMapping) {
+ clearStack();
+
+ const { unified, shortcode } = unicodeMapping[unqualified];
+
+ nodes.push(
+
,
+ );
+ } else if (c === ':') {
+ if (!open) {
+ clearStack();
+ }
+
+ stack += ':';
+
+ // we see another : we convert it and clear the stack buffer
+ if (open) {
+ nodes.push();
+ stack = '';
+ }
+
+ open = !open;
+ } else {
+ stack += c;
+
+ if (open && !validEmojiChar(c)) {
+ clearStack();
+ }
+ }
+ }
+
+ if (stack.length) nodes.push(stack);
+
+ return nodes;
+}, [text, emojis]);
+
+export { Emojify as default };
diff --git a/packages/pl-fe/src/features/emoji/index.ts b/packages/pl-fe/src/features/emoji/index.ts
index aafe331d6..dcd14a060 100644
--- a/packages/pl-fe/src/features/emoji/index.ts
+++ b/packages/pl-fe/src/features/emoji/index.ts
@@ -225,5 +225,6 @@ export {
isCustomEmoji,
isNativeEmoji,
buildCustomEmojis,
+ validEmojiChar,
emojify as default,
};
diff --git a/packages/pl-fe/src/features/event/components/event-header.tsx b/packages/pl-fe/src/features/event/components/event-header.tsx
index a1bc20ba8..07cdde057 100644
--- a/packages/pl-fe/src/features/event/components/event-header.tsx
+++ b/packages/pl-fe/src/features/event/components/event-header.tsx
@@ -19,6 +19,7 @@ import IconButton from 'pl-fe/components/ui/icon-button';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import VerificationBadge from 'pl-fe/components/verification-badge';
+import Emojify from 'pl-fe/features/emoji/emojify';
import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch';
import { useFeatures } from 'pl-fe/hooks/useFeatures';
import { useOwnAccount } from 'pl-fe/hooks/useOwnAccount';
@@ -414,7 +415,7 @@ const EventHeader: React.FC = ({ status }) => {
name: (
-
+
{account.verified && }
diff --git a/packages/pl-fe/src/features/feed-suggestions/feed-suggestions.tsx b/packages/pl-fe/src/features/feed-suggestions/feed-suggestions.tsx
index ff009530f..30229ff7e 100644
--- a/packages/pl-fe/src/features/feed-suggestions/feed-suggestions.tsx
+++ b/packages/pl-fe/src/features/feed-suggestions/feed-suggestions.tsx
@@ -10,6 +10,7 @@ import Text from 'pl-fe/components/ui/text';
import VerificationBadge from 'pl-fe/components/verification-badge';
import { useAppSelector } from 'pl-fe/hooks/useAppSelector';
+import Emojify from '../emoji/emojify';
import ActionButton from '../ui/components/action-button';
import { HotKeys } from '../ui/components/hotkeys';
@@ -41,14 +42,9 @@ const SuggestionItem: React.FC = ({ accountId }) => {
-
+
+
+
{account.verified && }
diff --git a/packages/pl-fe/src/features/group/components/group-header.tsx b/packages/pl-fe/src/features/group/components/group-header.tsx
index d43ccac9c..b520c4896 100644
--- a/packages/pl-fe/src/features/group/components/group-header.tsx
+++ b/packages/pl-fe/src/features/group/components/group-header.tsx
@@ -10,6 +10,7 @@ import HStack from 'pl-fe/components/ui/hstack';
import Icon from 'pl-fe/components/ui/icon';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
+import Emojify from 'pl-fe/features/emoji/emojify';
import { useModalsStore } from 'pl-fe/stores/modals';
import { isDefaultHeader } from 'pl-fe/utils/accounts';
@@ -141,9 +142,10 @@ const GroupHeader: React.FC = ({ group }) => {
+ >
+
+
@@ -157,7 +159,7 @@ const GroupHeader: React.FC = ({ group }) => {
align='center'
className='[&_a]:text-primary-600 [&_a]:hover:underline [&_a]:dark:text-accent-blue'
>
-
+
diff --git a/packages/pl-fe/src/features/group/edit-group.tsx b/packages/pl-fe/src/features/group/edit-group.tsx
index 2ea0a1be0..e7c4ca078 100644
--- a/packages/pl-fe/src/features/group/edit-group.tsx
+++ b/packages/pl-fe/src/features/group/edit-group.tsx
@@ -18,6 +18,7 @@ import { useAppSelector } from 'pl-fe/hooks/useAppSelector';
import { useInstance } from 'pl-fe/hooks/useInstance';
import toast from 'pl-fe/toast';
import { isDefaultAvatar, isDefaultHeader } from 'pl-fe/utils/accounts';
+import { unescapeHTML } from 'pl-fe/utils/html';
import AvatarPicker from '../edit-profile/components/avatar-picker';
import HeaderPicker from '../edit-profile/components/header-picker';
@@ -51,7 +52,7 @@ const EditGroup: React.FC = ({ params: { groupId } }) => {
const header = useImageField({ maxPixels: 1920 * 1080, preview: nonDefaultHeader(group?.header) });
const displayName = useTextField(group?.display_name);
- const note = useTextField(group?.note_plain);
+ const note = useTextField(unescapeHTML(group?.note));
const maxName = Number(instance.configuration.groups.max_characters_name);
const maxNote = Number(instance.configuration.groups.max_characters_description);
diff --git a/packages/pl-fe/src/features/group/manage-group.tsx b/packages/pl-fe/src/features/group/manage-group.tsx
index aa63d4bcb..de8637df6 100644
--- a/packages/pl-fe/src/features/group/manage-group.tsx
+++ b/packages/pl-fe/src/features/group/manage-group.tsx
@@ -13,6 +13,7 @@ import Text from 'pl-fe/components/ui/text';
import { useModalsStore } from 'pl-fe/stores/modals';
import toast from 'pl-fe/toast';
+import Emojify from '../emoji/emojify';
import ColumnForbidden from '../ui/components/column-forbidden';
type RouteParams = { groupId: string };
@@ -86,7 +87,7 @@ const ManageGroup: React.FC = ({ params }) => {
-
+
>
diff --git a/packages/pl-fe/src/features/groups/components/discover/group-list-item.tsx b/packages/pl-fe/src/features/groups/components/discover/group-list-item.tsx
index 6201f591d..88e5f540b 100644
--- a/packages/pl-fe/src/features/groups/components/discover/group-list-item.tsx
+++ b/packages/pl-fe/src/features/groups/components/discover/group-list-item.tsx
@@ -7,13 +7,14 @@ import HStack from 'pl-fe/components/ui/hstack';
import Icon from 'pl-fe/components/ui/icon';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
+import Emojify from 'pl-fe/features/emoji/emojify';
import GroupActionButton from 'pl-fe/features/group/components/group-action-button';
import { shortNumberFormat } from 'pl-fe/utils/numbers';
import type { Group } from 'pl-fe/normalizers/group';
interface IGroupListItem {
- group: Pick;
+ group: Pick;
withJoinAction?: boolean;
}
@@ -34,11 +35,9 @@ const GroupListItem = (props: IGroupListItem) => {
/>
-
+
+
+
): JSX.Element => (
+const buildLink = (account: Pick): JSX.Element => (
+ >
+
+
);
@@ -153,7 +155,7 @@ const messages: Record = defineMe
const buildMessage = (
intl: IntlShape,
type: NotificationType | 'reply',
- accounts: Array>,
+ accounts: Array>,
targetName: string,
instanceTitle: string,
): React.ReactNode => {
diff --git a/packages/pl-fe/src/features/status/components/detailed-status.tsx b/packages/pl-fe/src/features/status/components/detailed-status.tsx
index f2cb80d94..6e218db4c 100644
--- a/packages/pl-fe/src/features/status/components/detailed-status.tsx
+++ b/packages/pl-fe/src/features/status/components/detailed-status.tsx
@@ -15,6 +15,7 @@ import HStack from 'pl-fe/components/ui/hstack';
import Icon from 'pl-fe/components/ui/icon';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
+import Emojify from 'pl-fe/features/emoji/emojify';
import QuotedStatus from 'pl-fe/features/status/containers/quoted-status-container';
import StatusInteractionBar from './status-interaction-bar';
@@ -66,7 +67,7 @@ const DetailedStatus: React.FC = ({
-
+
diff --git a/packages/pl-fe/src/features/ui/components/modals/compare-history-modal.tsx b/packages/pl-fe/src/features/ui/components/modals/compare-history-modal.tsx
index 02c972250..aabc3559f 100644
--- a/packages/pl-fe/src/features/ui/components/modals/compare-history-modal.tsx
+++ b/packages/pl-fe/src/features/ui/components/modals/compare-history-modal.tsx
@@ -9,6 +9,7 @@ import Modal from 'pl-fe/components/ui/modal';
import Spinner from 'pl-fe/components/ui/spinner';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
+import Emojify from 'pl-fe/features/emoji/emojify';
import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch';
import { useAppSelector } from 'pl-fe/hooks/useAppSelector';
@@ -43,7 +44,6 @@ const CompareHistoryModal: React.FC =
{versions?.map((version) => {
const content =
;
- const spoilerContent = { __html: version.spoilerHtml };
const poll = typeof version.poll !== 'string' && version.poll;
@@ -51,7 +51,9 @@ const CompareHistoryModal: React.FC
=
{version.spoiler_text?.length > 0 && (
<>
-
+
+
+
>
)}
@@ -71,7 +73,9 @@ const CompareHistoryModal: React.FC
=
role='radio'
/>
-
+
+
+
))}
diff --git a/packages/pl-fe/src/features/ui/components/modals/familiar-followers-modal.tsx b/packages/pl-fe/src/features/ui/components/modals/familiar-followers-modal.tsx
index f3725273c..4c5271839 100644
--- a/packages/pl-fe/src/features/ui/components/modals/familiar-followers-modal.tsx
+++ b/packages/pl-fe/src/features/ui/components/modals/familiar-followers-modal.tsx
@@ -6,6 +6,7 @@ import ScrollableList from 'pl-fe/components/scrollable-list';
import Modal from 'pl-fe/components/ui/modal';
import Spinner from 'pl-fe/components/ui/spinner';
import AccountContainer from 'pl-fe/containers/account-container';
+import Emojify from 'pl-fe/features/emoji/emojify';
import { useAppSelector } from 'pl-fe/hooks/useAppSelector';
import { makeGetAccount } from 'pl-fe/selectors';
@@ -31,7 +32,13 @@ const FamiliarFollowersModal: React.FC;
} else {
- const emptyMessage = }} />;
+ const emptyMessage = (
+ }}
+ />
+ );
body = (
}}
+ values={{ name: !!account && }}
/>
}
onClose={onClickClose}
diff --git a/packages/pl-fe/src/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx b/packages/pl-fe/src/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx
index 0acc3e4db..9d9ba62dc 100644
--- a/packages/pl-fe/src/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx
+++ b/packages/pl-fe/src/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx
@@ -64,7 +64,7 @@ const ConfirmationStep: React.FC = ({ group }) => {
size='md'
className='mx-auto max-w-sm [&_a]:text-primary-600 [&_a]:hover:underline [&_a]:dark:text-accent-blue'
>
-
+
diff --git a/packages/pl-fe/src/features/ui/components/panels/pinned-accounts-panel.tsx b/packages/pl-fe/src/features/ui/components/panels/pinned-accounts-panel.tsx
index 3814bcd47..0ae284fe3 100644
--- a/packages/pl-fe/src/features/ui/components/panels/pinned-accounts-panel.tsx
+++ b/packages/pl-fe/src/features/ui/components/panels/pinned-accounts-panel.tsx
@@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
import { fetchPinnedAccounts } from 'pl-fe/actions/accounts';
import Widget from 'pl-fe/components/ui/widget';
import AccountContainer from 'pl-fe/containers/account-container';
+import Emojify from 'pl-fe/features/emoji/emojify';
import { WhoToFollowPanel } from 'pl-fe/features/ui/util/async-components';
import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch';
import { useAppSelector } from 'pl-fe/hooks/useAppSelector';
@@ -12,7 +13,7 @@ import { useAppSelector } from 'pl-fe/hooks/useAppSelector';
import type { Account } from 'pl-fe/normalizers/account';
interface IPinnedAccountsPanel {
- account: Pick;
+ account: Pick;
limit: number;
}
@@ -36,7 +37,7 @@ const PinnedAccountsPanel: React.FC = ({ account, limit })
id='pinned_accounts.title'
defaultMessage='{name}’s choices'
values={{
- name: ,
+ name: ,
}}
/>}
>
diff --git a/packages/pl-fe/src/features/ui/components/panels/profile-fields-panel.tsx b/packages/pl-fe/src/features/ui/components/panels/profile-fields-panel.tsx
index dc6712505..dd2be80b9 100644
--- a/packages/pl-fe/src/features/ui/components/panels/profile-fields-panel.tsx
+++ b/packages/pl-fe/src/features/ui/components/panels/profile-fields-panel.tsx
@@ -8,7 +8,7 @@ import ProfileField from '../profile-field';
import type { Account } from 'pl-fe/normalizers/account';
interface IProfileFieldsPanel {
- account: Pick;
+ account: Pick;
}
/** Custom profile fields for sidebar. */
@@ -16,7 +16,7 @@ const ProfileFieldsPanel: React.FC = ({ account }) => (
{account.fields.map((field, i) => (
-
+
))}
diff --git a/packages/pl-fe/src/features/ui/components/panels/profile-info-panel.tsx b/packages/pl-fe/src/features/ui/components/panels/profile-info-panel.tsx
index 8ffc01e9d..65805e1c5 100644
--- a/packages/pl-fe/src/features/ui/components/panels/profile-info-panel.tsx
+++ b/packages/pl-fe/src/features/ui/components/panels/profile-info-panel.tsx
@@ -10,6 +10,7 @@ import HStack from 'pl-fe/components/ui/hstack';
import Icon from 'pl-fe/components/ui/icon';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
+import Emojify from 'pl-fe/features/emoji/emojify';
import { useAppSelector } from 'pl-fe/hooks/useAppSelector';
import { usePlFeConfig } from 'pl-fe/hooks/usePlFeConfig';
import { capitalize } from 'pl-fe/utils/strings';
@@ -122,8 +123,6 @@ const ProfileInfoPanel: React.FC = ({ account, username }) =>
);
}
- const deactivated = account.deactivated ?? false;
- const displayNameHtml = deactivated ? { __html: intl.formatMessage(messages.deactivated) } : { __html: account.display_name_html };
const memberSinceDate = intl.formatDate(account.created_at, { month: 'long', year: 'numeric' });
const badges = getBadges();
@@ -132,7 +131,11 @@ const ProfileInfoPanel: React.FC = ({ account, username }) =>
-
+
+ {account.deactivated
+ ?
+ : }
+
{account.bot && }
@@ -160,9 +163,9 @@ const ProfileInfoPanel: React.FC = ({ account, username }) =>
- {!!account.note_emojified && (
+ {!!account.note && (
-
+
)}
@@ -208,7 +211,7 @@ const ProfileInfoPanel: React.FC = ({ account, username }) =>
{account.fields.length > 0 && (
{account.fields.map((field, i) => (
-
+
))}
)}
diff --git a/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx b/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx
index 6d4cb889d..27a30185c 100644
--- a/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx
+++ b/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx
@@ -9,6 +9,7 @@ import HStack from 'pl-fe/components/ui/hstack';
import Stack from 'pl-fe/components/ui/stack';
import Text from 'pl-fe/components/ui/text';
import VerificationBadge from 'pl-fe/components/verification-badge';
+import Emojify from 'pl-fe/features/emoji/emojify';
import { useAppSelector } from 'pl-fe/hooks/useAppSelector';
import { useSettings } from 'pl-fe/hooks/useSettings';
import { getAcct } from 'pl-fe/utils/accounts';
@@ -29,7 +30,6 @@ const UserPanel: React.FC = ({ accountId, action, badges, domain })
const fqn = useAppSelector((state) => displayFqn(state));
if (!account) return null;
- const displayNameHtml = { __html: account.display_name_html };
const acct = !account.acct.includes('@') && domain ? `${account.acct}@${domain}` : account.acct;
const header = account.header;
const verified = account.verified;
@@ -67,7 +67,9 @@ const UserPanel: React.FC = ({ accountId, action, badges, domain })
-
+
+
+
{verified && }
diff --git a/packages/pl-fe/src/features/ui/components/poll-preview.tsx b/packages/pl-fe/src/features/ui/components/poll-preview.tsx
index b6bd4cfc5..0cf89614d 100644
--- a/packages/pl-fe/src/features/ui/components/poll-preview.tsx
+++ b/packages/pl-fe/src/features/ui/components/poll-preview.tsx
@@ -4,7 +4,7 @@ import React from 'react';
import PollOption from 'pl-fe/components/polls/poll-option';
import Stack from 'pl-fe/components/ui/stack';
-import type { Poll } from 'pl-fe/normalizers/poll';
+import type { Poll } from 'pl-api';
interface IPollPreview {
poll: Poll;
diff --git a/packages/pl-fe/src/features/ui/components/profile-familiar-followers.tsx b/packages/pl-fe/src/features/ui/components/profile-familiar-followers.tsx
index 50984d8d7..fe3db1fed 100644
--- a/packages/pl-fe/src/features/ui/components/profile-familiar-followers.tsx
+++ b/packages/pl-fe/src/features/ui/components/profile-familiar-followers.tsx
@@ -9,6 +9,7 @@ import HoverAccountWrapper from 'pl-fe/components/hover-account-wrapper';
import HStack from 'pl-fe/components/ui/hstack';
import Text from 'pl-fe/components/ui/text';
import VerificationBadge from 'pl-fe/components/verification-badge';
+import Emojify from 'pl-fe/features/emoji/emojify';
import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch';
import { useAppSelector } from 'pl-fe/hooks/useAppSelector';
import { useFeatures } from 'pl-fe/hooks/useFeatures';
@@ -51,12 +52,9 @@ const ProfileFamiliarFollowers: React.FC = ({ account
-
+
+
+
{account.verified && }
diff --git a/packages/pl-fe/src/features/ui/components/profile-field.tsx b/packages/pl-fe/src/features/ui/components/profile-field.tsx
index 1e0cae751..18ea25f59 100644
--- a/packages/pl-fe/src/features/ui/components/profile-field.tsx
+++ b/packages/pl-fe/src/features/ui/components/profile-field.tsx
@@ -3,9 +3,12 @@ import React from 'react';
import { defineMessages, useIntl, FormatDateOptions } from 'react-intl';
import Markup from 'pl-fe/components/markup';
+import { ParsedContent } from 'pl-fe/components/parsed-content';
import HStack from 'pl-fe/components/ui/hstack';
import Icon from 'pl-fe/components/ui/icon';
+import Emojify from 'pl-fe/features/emoji/emojify';
import { CryptoAddress, LightningAddress } from 'pl-fe/features/ui/util/async-components';
+import { unescapeHTML } from 'pl-fe/utils/html';
import type { Account } from 'pl-fe/normalizers/account';
@@ -28,32 +31,35 @@ const dateFormatOptions: FormatDateOptions = {
interface IProfileField {
field: Account['fields'][number];
+ emojis?: Account['emojis'];
}
/** Renders a single profile field. */
-const ProfileField: React.FC = ({ field }) => {
+const ProfileField: React.FC = ({ field, emojis }) => {
const intl = useIntl();
if (isTicker(field.name)) {
return (
);
} else if (isZapEmoji(field.name)) {
- return ;
+ return ;
}
return (
-
-
+
+
+
-
{field.verified_at && (
@@ -62,7 +68,9 @@ const ProfileField: React.FC = ({ field }) => {
)}
-
+
+
+
diff --git a/packages/pl-fe/src/normalizers/account.ts b/packages/pl-fe/src/normalizers/account.ts
index 0a8d96353..9ee10a773 100644
--- a/packages/pl-fe/src/normalizers/account.ts
+++ b/packages/pl-fe/src/normalizers/account.ts
@@ -1,9 +1,3 @@
-import escapeTextContentForBrowser from 'escape-html';
-
-import emojify from 'pl-fe/features/emoji';
-import { unescapeHTML } from 'pl-fe/utils/html';
-import { makeEmojiMap } from 'pl-fe/utils/normalizers';
-
import type { Account as BaseAccount } from 'pl-api';
const getDomainFromURL = (account: Pick): string => {
@@ -34,8 +28,6 @@ const normalizeAccount = (account: BaseAccount) => {
const domain = fqn.split('@')[1] || '';
const note = account.note === '' ? '' : account.note;
- const emojiMap = makeEmojiMap(account.emojis);
-
return {
mute_expires_at: null,
...account,
@@ -46,15 +38,6 @@ const normalizeAccount = (account: BaseAccount) => {
fqn,
domain,
note,
- display_name_html: emojify(escapeTextContentForBrowser(account.display_name), emojiMap),
- note_emojified: emojify(account.note, emojiMap),
- note_plain: unescapeHTML(account.note),
- fields: account.fields.map(field => ({
- ...field,
- name_emojified: emojify(escapeTextContentForBrowser(field.name), emojiMap),
- value_emojified: emojify(field.value, emojiMap),
- value_plain: unescapeHTML(field.value),
- })),
};
};
diff --git a/packages/pl-fe/src/normalizers/announcement.ts b/packages/pl-fe/src/normalizers/announcement.ts
index ffb1ad4c8..9574a0827 100644
--- a/packages/pl-fe/src/normalizers/announcement.ts
+++ b/packages/pl-fe/src/normalizers/announcement.ts
@@ -1,10 +1,10 @@
import emojify from 'pl-fe/features/emoji';
-import { makeCustomEmojiMap } from 'pl-fe/schemas/utils';
+import { makeEmojiMap } from 'pl-fe/utils/normalizers';
import type { AdminAnnouncement as BaseAdminAnnouncement, Announcement as BaseAnnouncement } from 'pl-api';
const normalizeAnnouncement = (announcement: T) => {
- const emojiMap = makeCustomEmojiMap(announcement.emojis);
+ const emojiMap = makeEmojiMap(announcement.emojis);
const contentHtml = emojify(announcement.content, emojiMap);
diff --git a/packages/pl-fe/src/normalizers/group.ts b/packages/pl-fe/src/normalizers/group.ts
index 9d564e0c5..856cb33b8 100644
--- a/packages/pl-fe/src/normalizers/group.ts
+++ b/packages/pl-fe/src/normalizers/group.ts
@@ -1,9 +1,3 @@
-import escapeTextContentForBrowser from 'escape-html';
-
-import emojify from 'pl-fe/features/emoji';
-import { unescapeHTML } from 'pl-fe/utils/html';
-import { makeEmojiMap } from 'pl-fe/utils/normalizers';
-
import type { Group as BaseGroup } from 'pl-api';
const getDomainFromURL = (group: Pick): string => {
@@ -20,9 +14,6 @@ const normalizeGroup = (group: BaseGroup) => {
const missingHeader = require('pl-fe/assets/images/header-missing.png');
const domain = getDomainFromURL(group);
- const note = group.note === '' ? '' : group.note;
-
- const emojiMap = makeEmojiMap(group.emojis);
return {
...group,
@@ -31,10 +22,6 @@ const normalizeGroup = (group: BaseGroup) => {
header: group.header || group.header_static || missingHeader,
header_static: group.header_static || group.header || missingHeader,
domain,
- note,
- display_name_html: emojify(escapeTextContentForBrowser(group.display_name), emojiMap),
- note_emojified: emojify(group.note, emojiMap),
- note_plain: unescapeHTML(group.note),
};
};
diff --git a/packages/pl-fe/src/normalizers/status-edit.ts b/packages/pl-fe/src/normalizers/status-edit.ts
index 80809dc79..e5aa79149 100644
--- a/packages/pl-fe/src/normalizers/status-edit.ts
+++ b/packages/pl-fe/src/normalizers/status-edit.ts
@@ -6,18 +6,13 @@ import escapeTextContentForBrowser from 'escape-html';
import emojify from 'pl-fe/features/emoji';
import { makeEmojiMap } from 'pl-fe/utils/normalizers';
-import { normalizePollEdit } from './poll';
-
import type { StatusEdit as BaseStatusEdit } from 'pl-api';
const normalizeStatusEdit = (statusEdit: BaseStatusEdit) => {
const emojiMap = makeEmojiMap(statusEdit.emojis);
- const poll = statusEdit.poll ? normalizePollEdit(statusEdit.poll, statusEdit.emojis) : null;
-
return {
...statusEdit,
- poll,
contentHtml: emojify(statusEdit.content, emojiMap),
spoilerHtml: emojify(escapeTextContentForBrowser(statusEdit.spoiler_text), emojiMap),
};
diff --git a/packages/pl-fe/src/reducers/polls.ts b/packages/pl-fe/src/reducers/polls.ts
index 27102d118..f5015dc8d 100644
--- a/packages/pl-fe/src/reducers/polls.ts
+++ b/packages/pl-fe/src/reducers/polls.ts
@@ -1,16 +1,15 @@
import { Map as ImmutableMap } from 'immutable';
import { POLLS_IMPORT } from 'pl-fe/actions/importer';
-import { normalizePoll } from 'pl-fe/normalizers/poll';
-import type { Status } from 'pl-api';
+import type { Poll, Status } from 'pl-api';
import type { AnyAction } from 'redux';
-type State = ImmutableMap>;
+type State = ImmutableMap;
const importPolls = (state: State, polls: Array>) =>
state.withMutations(map =>
- polls.forEach(poll => map.set(poll.id, normalizePoll(poll))),
+ polls.forEach(poll => map.set(poll.id, poll)),
);
const initialState: State = ImmutableMap();
diff --git a/packages/pl-fe/src/schemas/utils.ts b/packages/pl-fe/src/schemas/utils.ts
index ed75567ee..1cc70c64e 100644
--- a/packages/pl-fe/src/schemas/utils.ts
+++ b/packages/pl-fe/src/schemas/utils.ts
@@ -1,7 +1,5 @@
import * as v from 'valibot';
-import type { CustomEmoji } from 'pl-api';
-
/** Validates individual items in an array, dropping any that aren't valid. */
const filteredArray = (schema: v.BaseSchema>) =>
v.pipe(
@@ -14,13 +12,6 @@ const filteredArray = (schema: v.BaseSchema>) =>
)),
);
-/** Map a list of CustomEmoji to their shortcodes. */
-const makeCustomEmojiMap = (customEmojis: CustomEmoji[]) =>
- customEmojis.reduce>((result, emoji) => {
- result[`:${emoji.shortcode}:`] = emoji;
- return result;
- }, {});
-
/** valibot schema to force the value into an object, if it isn't already. */
const coerceObject = (shape: T) =>
v.pipe(
@@ -29,4 +20,4 @@ const coerceObject = (shape: T) =>
v.object(shape),
);
-export { filteredArray, makeCustomEmojiMap, coerceObject };
+export { filteredArray, coerceObject };