From aab6ee34c28d4add73061273aea1916108bb0dc7 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 8 Feb 2023 19:20:17 -0600 Subject: [PATCH 1/7] EmojiSelector: render full picker --- .../ui/emoji-selector/emoji-selector.tsx | 51 ++++++++++++++----- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx b/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx index 32851a4bf..2be8e21d8 100644 --- a/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx +++ b/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx @@ -3,7 +3,8 @@ import clsx from 'clsx'; import React, { useEffect, useState } from 'react'; import { usePopper } from 'react-popper'; -import { Emoji, HStack } from 'soapbox/components/ui'; +import { Emoji, HStack, IconButton } from 'soapbox/components/ui'; +import { Picker } from 'soapbox/features/emoji/emoji-picker'; import { useSoapboxConfig } from 'soapbox/hooks'; interface IEmojiButton { @@ -42,6 +43,8 @@ interface IEmojiSelector { placement?: Placement /** Whether the selector should be visible. */ visible?: boolean + /** Whether to allow any emoji to be chosen. */ + all?: boolean } /** Panel with a row of emoji buttons. */ @@ -51,9 +54,12 @@ const EmojiSelector: React.FC = ({ onReact, placement = 'top', visible = false, + all = true, }): JSX.Element => { const soapboxConfig = useSoapboxConfig(); + const [expanded, setExpanded] = useState(false); + // `useRef` won't trigger a re-render, while `useState` does. // https://popper.js.org/react-popper/v2/ const [popperElement, setPopperElement] = useState(null); @@ -80,6 +86,14 @@ const EmojiSelector: React.FC = ({ ], }); + const handleExpand: React.MouseEventHandler = () => { + setExpanded(true); + }; + + useEffect(() => { + setExpanded(false); + }, [visible]); + useEffect(() => { document.addEventListener('mousedown', handleClickOutside); @@ -103,18 +117,29 @@ const EmojiSelector: React.FC = ({ style={styles.popper} {...attributes.popper} > - - {Array.from(soapboxConfig.allowedEmoji).map((emoji, i) => ( - - ))} - + {expanded ? ( + + ) : ( + + {Array.from(soapboxConfig.allowedEmoji).map((emoji, i) => ( + + ))} + + {all && ( + + )} + + )} ); }; From 06ea520e890d6ac7dd179d2f2c9666c2d36e13bd Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 8 Feb 2023 20:23:26 -0600 Subject: [PATCH 2/7] EmojiSelector: allow reacting from full picker --- app/soapbox/components/ui/emoji-selector/emoji-selector.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx b/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx index 2be8e21d8..f3d09af84 100644 --- a/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx +++ b/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx @@ -118,7 +118,11 @@ const EmojiSelector: React.FC = ({ {...attributes.popper} > {expanded ? ( - + require('emoji-datasource/img/twitter/sheets/32.png')} + onClick={(emoji: any) => onReact(emoji.native)} + /> ) : ( Date: Wed, 8 Feb 2023 20:26:37 -0600 Subject: [PATCH 3/7] StatusReactionWrapper: put the picker in a portal --- .../components/status-reaction-wrapper.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/soapbox/components/status-reaction-wrapper.tsx b/app/soapbox/components/status-reaction-wrapper.tsx index 545d6fe7d..c2ce020f9 100644 --- a/app/soapbox/components/status-reaction-wrapper.tsx +++ b/app/soapbox/components/status-reaction-wrapper.tsx @@ -1,3 +1,4 @@ +import { Portal } from '@reach/portal'; import React, { useState, useEffect, useRef } from 'react'; import { simpleEmojiReact } from 'soapbox/actions/emoji-reacts'; @@ -105,12 +106,14 @@ const StatusReactionWrapper: React.FC = ({ statusId, chi ref: setReferenceElement, })} - + + + ); }; From e2e3af4a8b796b68ad0eeb095e0442dc8f5875b6 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 8 Feb 2023 20:28:44 -0600 Subject: [PATCH 4/7] Don't allow reacting with any emoji to a chat for now --- .../chat-message-reaction-wrapper.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/soapbox/features/chats/components/chat-message-reaction-wrapper/chat-message-reaction-wrapper.tsx b/app/soapbox/features/chats/components/chat-message-reaction-wrapper/chat-message-reaction-wrapper.tsx index f6c8b7f82..d9403af4a 100644 --- a/app/soapbox/features/chats/components/chat-message-reaction-wrapper/chat-message-reaction-wrapper.tsx +++ b/app/soapbox/features/chats/components/chat-message-reaction-wrapper/chat-message-reaction-wrapper.tsx @@ -41,6 +41,7 @@ function ChatMessageReactionWrapper(props: IChatMessageReactionWrapper) { referenceElement={referenceElement} onReact={handleSelect} onClose={() => setIsOpen(false)} + all={false} /> ); From cf487b901d9a48b2aa003910ab15ea4063f80f04 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 8 Feb 2023 20:59:26 -0600 Subject: [PATCH 5/7] Display all emoji reactions --- .../utils/__tests__/emoji-reacts.test.ts | 29 +++++-------------- app/soapbox/utils/emoji-reacts.ts | 22 +++++--------- 2 files changed, 15 insertions(+), 36 deletions(-) diff --git a/app/soapbox/utils/__tests__/emoji-reacts.test.ts b/app/soapbox/utils/__tests__/emoji-reacts.test.ts index 97f8e34db..f5c6f150c 100644 --- a/app/soapbox/utils/__tests__/emoji-reacts.test.ts +++ b/app/soapbox/utils/__tests__/emoji-reacts.test.ts @@ -5,7 +5,6 @@ import { normalizeStatus } from 'soapbox/normalizers'; import { sortEmoji, mergeEmojiFavourites, - filterEmoji, oneEmojiPerAccount, reduceEmoji, getReactForStatus, @@ -22,29 +21,10 @@ const ALLOWED_EMOJI = ImmutableList([ '😡', ]); -describe('filterEmoji', () => { - describe('with a mix of allowed and disallowed emoji', () => { - const emojiReacts = fromJS([ - { 'count': 1, 'me': true, 'name': '🌵' }, - { 'count': 1, 'me': true, 'name': '😂' }, - { 'count': 1, 'me': true, 'name': '👀' }, - { 'count': 1, 'me': true, 'name': '🍩' }, - { 'count': 1, 'me': true, 'name': '😡' }, - { 'count': 1, 'me': true, 'name': '🔪' }, - { 'count': 1, 'me': true, 'name': '😠' }, - ]) as ImmutableList>; - it('filters only allowed emoji', () => { - expect(filterEmoji(emojiReacts, ALLOWED_EMOJI)).toEqual(fromJS([ - { 'count': 1, 'me': true, 'name': '😂' }, - { 'count': 1, 'me': true, 'name': '😡' }, - ])); - }); - }); -}); - describe('sortEmoji', () => { describe('with an unsorted list of emoji', () => { const emojiReacts = fromJS([ + { 'count': 7, 'me': true, 'name': '😃' }, { 'count': 7, 'me': true, 'name': '😯' }, { 'count': 3, 'me': true, 'name': '😢' }, { 'count': 1, 'me': true, 'name': '😡' }, @@ -53,11 +33,12 @@ describe('sortEmoji', () => { { 'count': 15, 'me': true, 'name': '❤' }, ]) as ImmutableList>; it('sorts the emoji by count', () => { - expect(sortEmoji(emojiReacts)).toEqual(fromJS([ + expect(sortEmoji(emojiReacts, ALLOWED_EMOJI)).toEqual(fromJS([ { 'count': 20, 'me': true, 'name': '👍' }, { 'count': 15, 'me': true, 'name': '❤' }, { 'count': 7, 'me': true, 'name': '😯' }, { 'count': 7, 'me': true, 'name': '😂' }, + { 'count': 7, 'me': true, 'name': '😃' }, { 'count': 3, 'me': true, 'name': '😢' }, { 'count': 1, 'me': true, 'name': '😡' }, ])); @@ -127,6 +108,10 @@ describe('reduceEmoji', () => { { 'count': 7, 'me': false, 'name': '😂' }, { 'count': 3, 'me': false, 'name': '😢' }, { 'count': 1, 'me': false, 'name': '😡' }, + { 'count': 1, 'me': true, 'name': '🔪' }, + { 'count': 1, 'me': true, 'name': '🌵' }, + { 'count': 1, 'me': false, 'name': '👀' }, + { 'count': 1, 'me': false, 'name': '🍩' }, ])); }); }); diff --git a/app/soapbox/utils/emoji-reacts.ts b/app/soapbox/utils/emoji-reacts.ts index 2e13627d3..b9104e783 100644 --- a/app/soapbox/utils/emoji-reacts.ts +++ b/app/soapbox/utils/emoji-reacts.ts @@ -19,12 +19,10 @@ export const ALLOWED_EMOJI = ImmutableList([ type Account = ImmutableMap; type EmojiReact = ImmutableMap; -export const sortEmoji = (emojiReacts: ImmutableList): ImmutableList => ( - emojiReacts.sortBy(emojiReact => -emojiReact.get('count')) -); - -export const mergeEmoji = (emojiReacts: ImmutableList): ImmutableList => ( - emojiReacts // TODO: Merge similar emoji +export const sortEmoji = (emojiReacts: ImmutableList, allowedEmoji: ImmutableList): ImmutableList => ( + emojiReacts + .sortBy(emojiReact => + -(emojiReact.get('count') + Number(allowedEmoji.includes(emojiReact.get('name'))))) ); export const mergeEmojiFavourites = (emojiReacts = ImmutableList(), favouritesCount: number, favourited: boolean) => { @@ -70,15 +68,11 @@ export const oneEmojiPerAccount = (emojiReacts: ImmutableList, me: M .reverse(); }; -export const filterEmoji = (emojiReacts: ImmutableList, allowedEmoji = ALLOWED_EMOJI): ImmutableList => ( - emojiReacts.filter(emojiReact => ( - allowedEmoji.includes(emojiReact.get('name')) - ))); - export const reduceEmoji = (emojiReacts: ImmutableList, favouritesCount: number, favourited: boolean, allowedEmoji = ALLOWED_EMOJI): ImmutableList => ( - filterEmoji(sortEmoji(mergeEmoji(mergeEmojiFavourites( - emojiReacts, favouritesCount, favourited, - ))), allowedEmoji)); + sortEmoji( + mergeEmojiFavourites(emojiReacts, favouritesCount, favourited), + allowedEmoji, + )); export const getReactForStatus = (status: any, allowedEmoji = ALLOWED_EMOJI): string | undefined => { const result = reduceEmoji( From 16179a637151c9f4b23ed90b14820944824e40f8 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 12 Feb 2023 20:23:29 -0600 Subject: [PATCH 6/7] Transpile emoji-mart for jest --- app/soapbox/features/emoji/{emoji-picker.js => emoji-picker.ts} | 2 ++ jest.config.cjs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) rename app/soapbox/features/emoji/{emoji-picker.js => emoji-picker.ts} (77%) diff --git a/app/soapbox/features/emoji/emoji-picker.js b/app/soapbox/features/emoji/emoji-picker.ts similarity index 77% rename from app/soapbox/features/emoji/emoji-picker.js rename to app/soapbox/features/emoji/emoji-picker.ts index 8725d39ec..f4dd98a51 100644 --- a/app/soapbox/features/emoji/emoji-picker.js +++ b/app/soapbox/features/emoji/emoji-picker.ts @@ -1,4 +1,6 @@ +// @ts-ignore no types import Emoji from 'emoji-mart/dist-es/components/emoji/emoji'; +// @ts-ignore no types import Picker from 'emoji-mart/dist-es/components/picker/picker'; export { diff --git a/jest.config.cjs b/jest.config.cjs index 300e2c3ba..62139dedb 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -39,7 +39,7 @@ module.exports = { 'transformIgnorePatterns': [ // FIXME: react-sticky-box doesn't provide a CJS build, so transform it for now // https://github.com/codecks-io/react-sticky-box/issues/79 - `/node_modules/(?!(react-sticky-box|blurhash|.+\\.(${ASSET_EXTS})$))`, + `/node_modules/(?!(react-sticky-box|blurhash|emoji-mart|.+\\.(${ASSET_EXTS})$))`, // Ignore node_modules, except static assets // `/node_modules/(?!.+\\.(${ASSET_EXTS})$)`, ], From f9cb9d468b449353252544e85d00c8b296214978 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 12 Feb 2023 20:29:33 -0600 Subject: [PATCH 7/7] EmojiSelector: improve style of 3-dots icon --- app/soapbox/components/ui/emoji-selector/emoji-selector.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx b/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx index f3d09af84..7f8336035 100644 --- a/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx +++ b/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx @@ -138,6 +138,7 @@ const EmojiSelector: React.FC = ({ {all && (