(),
expiration: null as number | null,
- emoji_reactions: null as ImmutableList
| null,
+ emoji_reactions: null as readonly EmojiReaction[] | null,
id: '',
unread: false,
deleting: false,
@@ -41,13 +41,8 @@ const normalizeMedia = (status: ImmutableMap) => {
};
const normalizeChatMessageEmojiReaction = (chatMessage: ImmutableMap) => {
- const emojiReactions = chatMessage.get('emoji_reactions');
-
- if (emojiReactions) {
- return chatMessage.set('emoji_reactions', ImmutableList(emojiReactions.map(normalizeEmojiReaction)));
- } else {
- return chatMessage;
- }
+ const emojiReactions = chatMessage.get('emoji_reactions') || ImmutableList();
+ return chatMessage.set('emoji_reactions', filteredArray(emojiReactionSchema).parse(emojiReactions.toJS()));
};
/** Rewrite `` to empty string. */
diff --git a/app/soapbox/normalizers/emoji-reaction.ts b/app/soapbox/normalizers/emoji-reaction.ts
deleted file mode 100644
index 88dcfd1e4..000000000
--- a/app/soapbox/normalizers/emoji-reaction.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Map as ImmutableMap, Record as ImmutableRecord, fromJS } from 'immutable';
-
-// https://docs.joinmastodon.org/entities/emoji/
-export const EmojiReactionRecord = ImmutableRecord({
- name: '',
- count: null as number | null,
- me: false,
-});
-
-export const normalizeEmojiReaction = (emojiReaction: Record) => {
- return EmojiReactionRecord(
- ImmutableMap(fromJS(emojiReaction)),
- );
-};
diff --git a/app/soapbox/normalizers/index.ts b/app/soapbox/normalizers/index.ts
index d22bce0c9..e7100fa9d 100644
--- a/app/soapbox/normalizers/index.ts
+++ b/app/soapbox/normalizers/index.ts
@@ -7,7 +7,6 @@ export { AttachmentRecord, normalizeAttachment } from './attachment';
export { ChatRecord, normalizeChat } from './chat';
export { ChatMessageRecord, normalizeChatMessage } from './chat-message';
export { EmojiRecord, normalizeEmoji } from './emoji';
-export { EmojiReactionRecord } from './emoji-reaction';
export { FilterRecord, normalizeFilter } from './filter';
export { FilterKeywordRecord, normalizeFilterKeyword } from './filter-keyword';
export { FilterStatusRecord, normalizeFilterStatus } from './filter-status';
diff --git a/app/soapbox/queries/__tests__/chats.test.ts b/app/soapbox/queries/__tests__/chats.test.ts
index 981250456..3bcd1b9a7 100644
--- a/app/soapbox/queries/__tests__/chats.test.ts
+++ b/app/soapbox/queries/__tests__/chats.test.ts
@@ -1,4 +1,4 @@
-import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+import { Map as ImmutableMap } from 'immutable';
import sumBy from 'lodash/sumBy';
import { useEffect } from 'react';
@@ -6,7 +6,6 @@ import { __stub } from 'soapbox/api';
import { buildRelationship } from 'soapbox/jest/factory';
import { createTestStore, mockStore, queryClient, renderHook, rootState, waitFor } from 'soapbox/jest/test-helpers';
import { normalizeChatMessage } from 'soapbox/normalizers';
-import { normalizeEmojiReaction } from 'soapbox/normalizers/emoji-reaction';
import { Store } from 'soapbox/store';
import { ChatMessage } from 'soapbox/types/entities';
import { flattenPages } from 'soapbox/utils/queries';
@@ -426,11 +425,11 @@ describe('useChatActions', () => {
});
const updatedChatMessage = (queryClient.getQueryData(ChatKeys.chatMessages(chat.id)) as any).pages[0].result[0] as ChatMessage;
- expect(updatedChatMessage.emoji_reactions).toEqual(ImmutableList([normalizeEmojiReaction({
+ expect(updatedChatMessage.emoji_reactions).toEqual([{
name: '👍',
count: 1,
me: true,
- })]));
+ }]);
});
});
});
diff --git a/app/soapbox/schemas/emoji-reaction.ts b/app/soapbox/schemas/emoji-reaction.ts
new file mode 100644
index 000000000..55c1762a0
--- /dev/null
+++ b/app/soapbox/schemas/emoji-reaction.ts
@@ -0,0 +1,15 @@
+import { z } from 'zod';
+
+/** Validates the string as an emoji. */
+const emojiSchema = z.string().refine((v) => /\p{Extended_Pictographic}/u.test(v));
+
+/** Pleroma emoji reaction. */
+const emojiReactionSchema = z.object({
+ name: emojiSchema,
+ count: z.number().nullable().catch(null),
+ me: z.boolean().catch(false),
+});
+
+type EmojiReaction = z.infer;
+
+export { emojiReactionSchema, EmojiReaction };
\ No newline at end of file
diff --git a/app/soapbox/schemas/index.ts b/app/soapbox/schemas/index.ts
index a64ab2942..29b1c2edd 100644
--- a/app/soapbox/schemas/index.ts
+++ b/app/soapbox/schemas/index.ts
@@ -1,6 +1,7 @@
export { accountSchema, type Account } from './account';
export { cardSchema, type Card } from './card';
export { customEmojiSchema, type CustomEmoji } from './custom-emoji';
+export { emojiReactionSchema, type EmojiReaction } from './emoji-reaction';
export { groupSchema, type Group } from './group';
export { groupMemberSchema, type GroupMember } from './group-member';
export { groupRelationshipSchema, type GroupRelationship } from './group-relationship';
diff --git a/app/soapbox/types/entities.ts b/app/soapbox/types/entities.ts
index 27082f76f..e99aa3acf 100644
--- a/app/soapbox/types/entities.ts
+++ b/app/soapbox/types/entities.ts
@@ -8,7 +8,6 @@ import {
ChatRecord,
ChatMessageRecord,
EmojiRecord,
- EmojiReactionRecord,
FieldRecord,
FilterRecord,
FilterKeywordRecord,
@@ -38,7 +37,6 @@ type Attachment = ReturnType;
type Chat = ReturnType;
type ChatMessage = ReturnType;
type Emoji = ReturnType;
-type EmojiReaction = ReturnType;
type Field = ReturnType;
type Filter = ReturnType;
type FilterKeyword = ReturnType;
@@ -81,7 +79,6 @@ export {
Chat,
ChatMessage,
Emoji,
- EmojiReaction,
Field,
Filter,
FilterKeyword,
@@ -105,6 +102,7 @@ export {
export type {
Card,
+ EmojiReaction,
Group,
GroupMember,
GroupRelationship,
diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts
index 9c5a68bda..dd818ac2b 100644
--- a/app/soapbox/utils/features.ts
+++ b/app/soapbox/utils/features.ts
@@ -254,7 +254,7 @@ const getInstanceFeatures = (instance: Instance) => {
/**
* Ability to add reactions to chat messages.
*/
- chatEmojiReactions: v.software === TRUTHSOCIAL && v.build === UNRELEASED,
+ chatEmojiReactions: v.software === TRUTHSOCIAL,
/**
* Pleroma chats API.