From 584cde8c4010c398a7e9d0400737a7010283febc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 11 Sep 2024 15:21:32 +0200 Subject: [PATCH] pl-fe: We're not Facebook anymore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-fe/src/actions/emoji-reacts.ts | 29 +--- .../src/components/announcements/reaction.tsx | 2 +- .../src/components/status-action-bar.tsx | 91 ++++------- .../components/status-reaction-wrapper.tsx | 120 -------------- .../src/components/status-reactions-bar.tsx | 113 +++++++++++++ packages/pl-fe/src/components/status.tsx | 17 +- .../pl-fe/src/components/translate-button.tsx | 2 +- .../ui/emoji-selector/emoji-selector.tsx | 152 ------------------ packages/pl-fe/src/components/ui/index.ts | 1 - .../emoji-picker-dropdown-container.tsx | 47 ++++-- .../status/components/detailed-status.tsx | 3 + .../components/status-interaction-bar.tsx | 56 +------ .../src/features/status/components/thread.tsx | 3 +- .../ui/components/modals/reactions-modal.tsx | 16 +- packages/pl-fe/src/locales/en.json | 8 +- .../src/normalizers/pl-fe/pl-fe-config.ts | 8 - packages/pl-fe/src/utils/emoji-reacts.ts | 52 ------ 17 files changed, 203 insertions(+), 517 deletions(-) delete mode 100644 packages/pl-fe/src/components/status-reaction-wrapper.tsx create mode 100644 packages/pl-fe/src/components/status-reactions-bar.tsx delete mode 100644 packages/pl-fe/src/components/ui/emoji-selector/emoji-selector.tsx diff --git a/packages/pl-fe/src/actions/emoji-reacts.ts b/packages/pl-fe/src/actions/emoji-reacts.ts index 66b6527c4..dcc34cbe0 100644 --- a/packages/pl-fe/src/actions/emoji-reacts.ts +++ b/packages/pl-fe/src/actions/emoji-reacts.ts @@ -3,9 +3,8 @@ import { isLoggedIn } from 'pl-fe/utils/auth'; import { getClient } from '../api'; import { importFetchedStatus } from './importer'; -import { favourite, unfavourite } from './interactions'; -import type { EmojiReaction, Status } from 'pl-api'; +import type { Status } from 'pl-api'; import type { AppDispatch, RootState } from 'pl-fe/store'; const EMOJI_REACT_REQUEST = 'EMOJI_REACT_REQUEST' as const; @@ -18,31 +17,6 @@ const UNEMOJI_REACT_FAIL = 'UNEMOJI_REACT_FAIL' as const; const noOp = () => () => new Promise(f => f(undefined)); -const simpleEmojiReact = (status: Pick, emoji: string, custom?: string) => - (dispatch: AppDispatch) => { - const emojiReacts: Array = status.emoji_reactions || []; - - if (emoji === '👍' && status.favourited) return dispatch(unfavourite(status)); - - const undo = emojiReacts.filter(e => e.me === true && e.name === emoji).length > 0; - if (undo) return dispatch(unEmojiReact(status, emoji)); - - return Promise.all([ - ...emojiReacts - .filter((emojiReact) => emojiReact.me === true) - .map(emojiReact => dispatch(unEmojiReact(status, emojiReact.name))), - status.favourited && dispatch(unfavourite(status)), - ]).then(() => { - if (emoji === '👍') { - dispatch(favourite(status)); - } else { - dispatch(emojiReact(status, emoji, custom)); - } - }).catch(err => { - console.error(err); - }); - }; - const emojiReact = (status: Pick, emoji: string, custom?: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return dispatch(noOp()); @@ -119,7 +93,6 @@ export { UNEMOJI_REACT_REQUEST, UNEMOJI_REACT_SUCCESS, UNEMOJI_REACT_FAIL, - simpleEmojiReact, emojiReact, unEmojiReact, emojiReactRequest, diff --git a/packages/pl-fe/src/components/announcements/reaction.tsx b/packages/pl-fe/src/components/announcements/reaction.tsx index 45eee413c..3b9d1c27f 100644 --- a/packages/pl-fe/src/components/announcements/reaction.tsx +++ b/packages/pl-fe/src/components/announcements/reaction.tsx @@ -39,7 +39,7 @@ const Reaction: React.FC = ({ announcementId, reaction, emojiMap, sty // @ts-ignore if (unicodeMapping[shortCode]) { // @ts-ignore - shortCode = unicodeMapping[shortCode].shortCode; + shortCode = unicodeMapping[shortCode].shortcode; } return ( diff --git a/packages/pl-fe/src/components/status-action-bar.tsx b/packages/pl-fe/src/components/status-action-bar.tsx index 37d54eae5..d0fe4f0f8 100644 --- a/packages/pl-fe/src/components/status-action-bar.tsx +++ b/packages/pl-fe/src/components/status-action-bar.tsx @@ -5,6 +5,7 @@ import { useHistory, useRouteMatch } from 'react-router-dom'; import { blockAccount } from 'pl-fe/actions/accounts'; import { directCompose, mentionCompose, quoteCompose, replyCompose } from 'pl-fe/actions/compose'; +import { emojiReact } from 'pl-fe/actions/emoji-reacts'; import { editEvent } from 'pl-fe/actions/events'; import { toggleBookmark, toggleDislike, toggleFavourite, togglePin, toggleReblog } from 'pl-fe/actions/interactions'; import { openModal } from 'pl-fe/actions/modals'; @@ -18,18 +19,18 @@ import { useBlockGroupMember, useGroup, useGroupRelationship, useTranslationLang import { useDeleteGroupStatus } from 'pl-fe/api/hooks/groups/useDeleteGroupStatus'; import DropdownMenu from 'pl-fe/components/dropdown-menu'; import StatusActionButton from 'pl-fe/components/status-action-button'; -import StatusReactionWrapper from 'pl-fe/components/status-reaction-wrapper'; import { HStack } from 'pl-fe/components/ui'; +import EmojiPickerDropdown from 'pl-fe/features/emoji/containers/emoji-picker-dropdown-container'; import { languages } from 'pl-fe/features/preferences'; import { useAppDispatch, useAppSelector, useFeatures, useInstance, useOwnAccount, useSettings } from 'pl-fe/hooks'; import { useChats } from 'pl-fe/queries/chats'; import toast from 'pl-fe/toast'; import copy from 'pl-fe/utils/copy'; -import { getReactForStatus, reduceEmoji } from 'pl-fe/utils/emoji-reacts'; import GroupPopover from './groups/popover/group-popover'; import type { Menu } from 'pl-fe/components/dropdown-menu'; +import type { Emoji as EmojiType } from 'pl-fe/features/emoji'; import type { UnauthorizedModalAction } from 'pl-fe/features/ui/components/modals/unauthorized-modal'; import type { Account, Group } from 'pl-fe/normalizers'; import type { SelectedStatus } from 'pl-fe/selectors'; @@ -77,12 +78,6 @@ const messages = defineMessages({ open: { id: 'status.open', defaultMessage: 'Show post details' }, pin: { id: 'status.pin', defaultMessage: 'Pin on profile' }, quotePost: { id: 'status.quote', defaultMessage: 'Quote post' }, - reactionCry: { id: 'status.reactions.cry', defaultMessage: 'Sad' }, - reactionHeart: { id: 'status.reactions.heart', defaultMessage: 'Love' }, - reactionLaughing: { id: 'status.reactions.laughing', defaultMessage: 'Haha' }, - reactionLike: { id: 'status.reactions.like', defaultMessage: 'Like' }, - reactionOpenMouth: { id: 'status.reactions.open_mouth', defaultMessage: 'Wow' }, - reactionWeary: { id: 'status.reactions.weary', defaultMessage: 'Weary' }, reblog: { id: 'status.reblog', defaultMessage: 'Repost' }, reblog_private: { id: 'status.reblog_private', defaultMessage: 'Repost to original audience' }, redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' }, @@ -99,6 +94,7 @@ const messages = defineMessages({ unbookmark: { id: 'status.unbookmark', defaultMessage: 'Remove bookmark' }, unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' }, unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' }, + viewReactions: { id: 'status.view_reactions', defaultMessage: 'View reactions' }, addKnownLanguage: { id: 'status.add_known_language', defaultMessage: 'Do not auto-translate posts in {language}.' }, translate: { id: 'status.translate', defaultMessage: 'Translate' }, hideTranslation: { id: 'status.hide_translation', defaultMessage: 'Hide translation' }, @@ -201,6 +197,10 @@ const StatusActionBar: React.FC = ({ } }; + const handlePickEmoji = (emoji: EmojiType) => { + dispatch(emojiReact(status, emoji.custom ? emoji.id : emoji.native, emoji.custom ? emoji.imageUrl : undefined)); + }; + const handleBookmarkClick: React.EventHandler = (e) => { dispatch(toggleBookmark(status)); }; @@ -307,6 +307,10 @@ const StatusActionBar: React.FC = ({ })); }; + const handleOpenReactionsModal = (): void => { + dispatch(openModal('REACTIONS', { statusId: status.id })); + }; + const handleReport: React.EventHandler = (e) => { dispatch(initReport(ReportableEntities.STATUS, status.account, { status })); }; @@ -410,6 +414,14 @@ const StatusActionBar: React.FC = ({ } } + if (status.emoji_reactions.length && features.exposableReactions) { + menu.push({ + text: intl.formatMessage(messages.viewReactions), + action: handleOpenReactionsModal, + icon: require('@tabler/icons/outline/mood-happy.svg'), + }); + } + if (!me) { return menu; } @@ -625,27 +637,6 @@ const StatusActionBar: React.FC = ({ const quoteCount = status.quotes_count; const favouriteCount = status.favourites_count; - const emojiReactCount = status.emoji_reactions ? reduceEmoji( - status.emoji_reactions, - favouriteCount, - status.favourited, - ).reduce((acc, cur) => acc + (cur.count || 0), 0) : undefined; - - const meEmojiReact = getReactForStatus(status); - const meEmojiName = meEmojiReact?.name as keyof typeof reactMessages | undefined; - - const reactMessages = { - '👍': messages.reactionLike, - '❤️': messages.reactionHeart, - '😆': messages.reactionLaughing, - '😮': messages.reactionOpenMouth, - '😢': messages.reactionCry, - '😩': messages.reactionWeary, - '': messages.favourite, - }; - - const meEmojiTitle = intl.formatMessage(reactMessages[meEmojiName || ''] || messages.favourite); - const menu = _makeMenu(publicStatus); let reblogIcon = require('@tabler/icons/outline/repeat.svg'); let replyTitle; @@ -738,33 +729,17 @@ const StatusActionBar: React.FC = ({ reblogButton )} - {features.emojiReacts ? ( - - - - ) : ( - - )} + {features.statusDislikes && ( = ({ /> )} + {expandable && (features.emojiReacts || features.emojiReactsMastodon) && ( + + )} + {canShare && ( = ({ statusId, children }): JSX.Element | null => { - const dispatch = useAppDispatch(); - const { account: ownAccount } = useOwnAccount(); - const status = useAppSelector(state => state.statuses.get(statusId)); - - const timeout = useRef(); - const [visible, setVisible] = useState(false); - - const [referenceElement, setReferenceElement] = useState(null); - - useEffect(() => () => { - if (timeout.current) { - clearTimeout(timeout.current); - } - }, []); - - if (!status) return null; - - const handleMouseEnter = () => { - if (timeout.current) { - clearTimeout(timeout.current); - } - - if (!userTouching.matches) { - setVisible(true); - } - }; - - const handleMouseLeave = () => { - if (timeout.current) { - clearTimeout(timeout.current); - } - - // Unless the user is touching, delay closing the emoji selector briefly - // so the user can move the mouse diagonally to make a selection. - if (userTouching.matches) { - setVisible(false); - } else { - timeout.current = setTimeout(() => { - setVisible(false); - }, 500); - } - }; - - const handleReact = (emoji: string, custom?: string): void => { - if (ownAccount) { - dispatch(simpleEmojiReact(status, emoji, custom)); - } else { - handleUnauthorized(); - } - - setVisible(false); - }; - - const handleClick: React.EventHandler = e => { - const meEmojiReact = getReactForStatus(status)?.name || '👍'; - - if (userTouching.matches) { - if (ownAccount) { - if (visible) { - handleReact(meEmojiReact); - } else { - setVisible(true); - } - } else { - handleUnauthorized(); - } - } else { - handleReact(meEmojiReact); - } - - e.preventDefault(); - e.stopPropagation(); - }; - - const handleUnauthorized = () => { - dispatch(openModal('UNAUTHORIZED', { - action: 'FAVOURITE', - ap_id: status.url, - })); - }; - - return ( -
- {React.cloneElement(children, { - onClick: handleClick, - ref: setReferenceElement, - })} - - {visible && ( - - setVisible(false)} - /> - - )} -
- ); -}; - -export { StatusReactionWrapper as default }; diff --git a/packages/pl-fe/src/components/status-reactions-bar.tsx b/packages/pl-fe/src/components/status-reactions-bar.tsx new file mode 100644 index 000000000..6dd94c116 --- /dev/null +++ b/packages/pl-fe/src/components/status-reactions-bar.tsx @@ -0,0 +1,113 @@ +import clsx from 'clsx'; +import { EmojiReaction } from 'pl-api'; +import React from 'react'; +import { defineMessages, useIntl } from 'react-intl'; + +import { emojiReact, unEmojiReact } from 'pl-fe/actions/emoji-reacts'; +import EmojiPickerDropdown from 'pl-fe/features/emoji/containers/emoji-picker-dropdown-container'; +import unicodeMapping from 'pl-fe/features/emoji/mapping'; +import { useAppDispatch, useSettings } from 'pl-fe/hooks'; +import { sortEmoji } from 'pl-fe/utils/emoji-reacts'; + +import AnimatedNumber from './animated-number'; +import { Emoji, HStack, Icon, Text } from './ui'; + +import type { Emoji as EmojiType } from 'pl-fe/features/emoji'; +import type { SelectedStatus } from 'pl-fe/selectors'; + +const messages = defineMessages({ + emojiCount: { id: 'status.reactions.label', defaultMessage: '{count} {count, plural, one {person} other {people}} reacted with {emoji}' }, + addEmoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, +}); + +interface IStatusReactionsBar { + status: Pick; + collapsed?: boolean; +} + +interface IStatusReaction { + status: Pick; + reaction: EmojiReaction; + obfuscate?: boolean; +} + +const StatusReaction: React.FC = ({ reaction, status, obfuscate }) => { + const dispatch = useAppDispatch(); + const intl = useIntl(); + + if (!reaction.count) return null; + + const handleClick: React.MouseEventHandler = (e) => { + e.stopPropagation(); + + if (reaction.me) { + dispatch(unEmojiReact(status, reaction.name)); + } else { + dispatch(emojiReact(status, reaction.name)); + } + }; + + let shortCode = reaction.name; + + // @ts-ignore + if (unicodeMapping[shortCode]?.shortcode) { + // @ts-ignore + shortCode = unicodeMapping[shortCode].shortcode; + } + + return ( + + ); +}; + +const StatusReactionsBar: React.FC = ({ status, collapsed }) => { + const dispatch = useAppDispatch(); + const intl = useIntl(); + const { demetricator } = useSettings(); + + const handlePickEmoji = (emoji: EmojiType) => { + dispatch(emojiReact(status, emoji.custom ? emoji.id : emoji.native, emoji.custom ? emoji.imageUrl : undefined)); + }; + + if ((demetricator || status.emoji_reactions.length === 0) && collapsed) return null; + + const sortedReactions = sortEmoji(status.emoji_reactions); + + return ( + + {sortedReactions.map((reaction) => reaction.count ? ( + + ) : null)} + + + + + ); +}; + +export { StatusReactionsBar as default }; diff --git a/packages/pl-fe/src/components/status.tsx b/packages/pl-fe/src/components/status.tsx index e621c9e3d..6eb79e638 100644 --- a/packages/pl-fe/src/components/status.tsx +++ b/packages/pl-fe/src/components/status.tsx @@ -21,6 +21,7 @@ import StatusActionBar from './status-action-bar'; import StatusContent from './status-content'; import StatusLanguagePicker from './status-language-picker'; import StatusMedia from './status-media'; +import StatusReactionsBar from './status-reactions-bar'; import StatusReplyMentions from './status-reply-mentions'; import SensitiveContentOverlay from './statuses/sensitive-content-overlay'; import StatusInfo from './statuses/status-info'; @@ -174,16 +175,11 @@ const Status: React.FC = (props) => { }; const handleHotkeyReact = (): void => { - _expandEmojiSelector(); + (node.current?.querySelector('.emoji-picker-dropdown') as HTMLButtonElement)?.click(); }; const handleUnfilter = () => dispatch(unfilterStatus(status.filtered.length ? status.id : actualStatus.id)); - const _expandEmojiSelector = (): void => { - const firstEmoji: HTMLDivElement | null | undefined = node.current?.querySelector('.emoji-react-selector .emoji-react-selector__emoji'); - firstEmoji?.focus(); - }; - const renderStatusInfo = () => { if (isReblog && showGroup && group) { return ( @@ -452,8 +448,15 @@ const Status: React.FC = (props) => { )} + + {!hideActionBar && ( -
+
)} diff --git a/packages/pl-fe/src/components/translate-button.tsx b/packages/pl-fe/src/components/translate-button.tsx index 37aaf722b..b96ee8484 100644 --- a/packages/pl-fe/src/components/translate-button.tsx +++ b/packages/pl-fe/src/components/translate-button.tsx @@ -54,7 +54,7 @@ const TranslateButton: React.FC = ({ status }) => { const button = ( - ); -}; - -interface IEmojiSelector { - onClose?(): void; - /** Event handler when an emoji is clicked. */ - onReact(emoji: string, custom?: string): void; - /** Element that triggers the EmojiSelector Popper */ - referenceElement: HTMLElement | null; - placement?: Placement; - /** Whether the selector should be visible. */ - visible?: boolean; - offsetOptions?: OffsetOptions; - /** Whether to allow any emoji to be chosen. */ - all?: boolean; -} - -/** Panel with a row of emoji buttons. */ -const EmojiSelector: React.FC = ({ - referenceElement, - onClose, - onReact, - placement = 'top', - visible = false, - offsetOptions, - all = true, -}): JSX.Element => { - const plFeConfig = usePlFeConfig(); - const { customEmojiReacts } = useFeatures(); - - const [expanded, setExpanded] = useState(false); - - const { x, y, strategy, refs, update } = useFloating({ - placement, - middleware: [offset(offsetOptions), shift()], - }); - - const handleExpand: React.MouseEventHandler = () => { - setExpanded(true); - }; - - const handlePickEmoji = (emoji: Emoji) => { - onReact(emoji.custom ? emoji.id : emoji.native, emoji.custom ? emoji.imageUrl : undefined); - }; - - useEffect(() => { - refs.setReference(referenceElement); - }, [referenceElement]); - - useEffect(() => () => { - document.body.style.overflow = ''; - }, []); - - useEffect(() => { - setExpanded(false); - }, [visible]); - - useClickOutside(refs, () => { - if (onClose) { - onClose(); - } - }); - - return ( -
- {expanded ? ( - - ) : ( - - {Array.from(plFeConfig.allowedEmoji).map((emoji) => ( - - ))} - - {all && ( - - )} - - )} -
- ); -}; - -export { EmojiSelector as default }; diff --git a/packages/pl-fe/src/components/ui/index.ts b/packages/pl-fe/src/components/ui/index.ts index c565641f4..eeb0b1eb4 100644 --- a/packages/pl-fe/src/components/ui/index.ts +++ b/packages/pl-fe/src/components/ui/index.ts @@ -18,7 +18,6 @@ export { default as Counter } from './counter/counter'; export { default as Datepicker } from './datepicker/datepicker'; export { default as Divider } from './divider/divider'; export { default as Emoji } from './emoji/emoji'; -export { default as EmojiSelector } from './emoji-selector/emoji-selector'; export { default as FileInput } from './file-input/file-input'; export { default as Form } from './form/form'; export { default as FormActions } from './form-actions/form-actions'; diff --git a/packages/pl-fe/src/features/emoji/containers/emoji-picker-dropdown-container.tsx b/packages/pl-fe/src/features/emoji/containers/emoji-picker-dropdown-container.tsx index c6359a5d3..2e5e8dffb 100644 --- a/packages/pl-fe/src/features/emoji/containers/emoji-picker-dropdown-container.tsx +++ b/packages/pl-fe/src/features/emoji/containers/emoji-picker-dropdown-container.tsx @@ -1,5 +1,4 @@ import { useFloating, shift, flip } from '@floating-ui/react'; -import clsx from 'clsx'; import React, { KeyboardEvent, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; @@ -13,7 +12,7 @@ const messages = defineMessages({ }); const EmojiPickerDropdownContainer = ( - props: Pick, + { children, ...props }: Pick & { children?: JSX.Element }, ) => { const intl = useIntl(); const title = intl.formatMessage(messages.emoji); @@ -27,27 +26,41 @@ const EmojiPickerDropdownContainer = ( setVisible(false); }); - const handleToggle = (e: MouseEvent | KeyboardEvent) => { + const handleClick = (e: MouseEvent) => { e.stopPropagation(); + e.preventDefault(); setVisible(!visible); }; + const handleKeyDown = (e: KeyboardEvent) => { + if (['Enter', ' '].includes(e.key)) { + e.stopPropagation(); + e.preventDefault(); + setVisible(!visible); + } + }; + return (
- } - tabIndex={0} - /> + {children ? ( + React.cloneElement(children, { + onClick: handleClick, + onKeyDown: handleKeyDown, + ref: refs.setReference, + }) + ) : ( + } + tabIndex={0} + />)}
= ({ + + diff --git a/packages/pl-fe/src/features/status/components/status-interaction-bar.tsx b/packages/pl-fe/src/features/status/components/status-interaction-bar.tsx index b22389da3..1ed7b6d71 100644 --- a/packages/pl-fe/src/features/status/components/status-interaction-bar.tsx +++ b/packages/pl-fe/src/features/status/components/status-interaction-bar.tsx @@ -4,14 +4,13 @@ import { Link } from 'react-router-dom'; import { openModal } from 'pl-fe/actions/modals'; import AnimatedNumber from 'pl-fe/components/animated-number'; -import { HStack, Text, Emoji } from 'pl-fe/components/ui'; +import { HStack, Text } from 'pl-fe/components/ui'; import { useAppSelector, useFeatures, useAppDispatch } from 'pl-fe/hooks'; -import { reduceEmoji } from 'pl-fe/utils/emoji-reacts'; import type { Status } from 'pl-fe/normalizers'; interface IStatusInteractionBar { - status: Pick; + status: Pick; } const StatusInteractionBar: React.FC = ({ status }): JSX.Element | null => { @@ -38,16 +37,6 @@ const StatusInteractionBar: React.FC = ({ status }): JSX. dispatch(openModal('DISLIKES', { statusId })); }; - const onOpenReactionsModal = (username: string, statusId: string): void => { - dispatch(openModal('REACTIONS', { statusId })); - }; - - const getNormalizedReacts = () => reduceEmoji( - status.emoji_reactions, - status.favourites_count, - status.favourited, - ); - const handleOpenReblogsModal: React.EventHandler = (e) => { e.preventDefault(); @@ -135,49 +124,11 @@ const StatusInteractionBar: React.FC = ({ status }): JSX. return null; }; - const handleOpenReactionsModal = () => { - if (!me) { - return onOpenUnauthorizedModal(); - } - - onOpenReactionsModal(account.acct, status.id); - }; - - const getEmojiReacts = () => { - const emojiReacts = getNormalizedReacts(); - const count = emojiReacts.reduce((acc, cur) => ( - acc + (cur.count || 0) - ), 0); - - const handleClick = features.emojiReacts ? handleOpenReactionsModal : handleOpenFavouritesModal; - - if (count) { - return ( - - - {emojiReacts.slice(0, 3).map((e, i) => { - return ( - - ); - })} - - - ); - } - - return null; - }; - return ( {getReposts()} {getQuotes()} - {(features.emojiReacts || features.emojiReactsMastodon) ? getEmojiReacts() : getFavourites()} + {getFavourites()} {getDislikes()} ); @@ -203,7 +154,6 @@ const InteractionCounter: React.FC = ({ count, children, on - {/* {shortNumberFormat(count)} */} diff --git a/packages/pl-fe/src/features/status/components/thread.tsx b/packages/pl-fe/src/features/status/components/thread.tsx index f56d947d2..27512c263 100644 --- a/packages/pl-fe/src/features/status/components/thread.tsx +++ b/packages/pl-fe/src/features/status/components/thread.tsx @@ -120,8 +120,7 @@ const Thread: React.FC = ({ const handleHotkeyReact = () => { if (statusRef.current) { - const firstEmoji: HTMLButtonElement | null = statusRef.current.querySelector('.emoji-react-selector .emoji-react-selector__emoji'); - firstEmoji?.focus(); + (node.current?.querySelector('.emoji-picker-dropdown') as HTMLButtonElement)?.click(); } }; diff --git a/packages/pl-fe/src/features/ui/components/modals/reactions-modal.tsx b/packages/pl-fe/src/features/ui/components/modals/reactions-modal.tsx index b9bf364e0..680b468f1 100644 --- a/packages/pl-fe/src/features/ui/components/modals/reactions-modal.tsx +++ b/packages/pl-fe/src/features/ui/components/modals/reactions-modal.tsx @@ -3,12 +3,11 @@ import { List as ImmutableList } from 'immutable'; import React, { useEffect, useMemo, useState } from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; -import { fetchFavourites, fetchReactions } from 'pl-fe/actions/interactions'; +import { fetchReactions } from 'pl-fe/actions/interactions'; import ScrollableList from 'pl-fe/components/scrollable-list'; import { Emoji, Modal, Spinner, Tabs } from 'pl-fe/components/ui'; import AccountContainer from 'pl-fe/containers/account-container'; import { useAppDispatch, useAppSelector } from 'pl-fe/hooks'; -import { ReactionRecord } from 'pl-fe/reducers/user-lists'; import type { BaseModalProps } from '../modal-root'; import type { Item } from 'pl-fe/components/ui/tabs/tabs'; @@ -32,16 +31,7 @@ const ReactionsModal: React.FC = ({ onClos const dispatch = useAppDispatch(); const intl = useIntl(); const [reaction, setReaction] = useState(initialReaction); - const reactions = useAppSelector> | undefined>((state) => { - const favourites = state.user_lists.favourited_by.get(statusId)?.items; - const reactions = state.user_lists.reactions.get(statusId)?.items; - return favourites && reactions && ImmutableList(favourites?.size ? [ReactionRecord({ accounts: favourites, count: favourites.size, name: '👍' })] : []).concat(reactions || []); - }); - - const fetchData = () => { - dispatch(fetchFavourites(statusId)); - dispatch(fetchReactions(statusId)); - }; + const reactions = useAppSelector((state) => state.user_lists.reactions.get(statusId)?.items); const onClickClose = () => { onClose('REACTIONS'); @@ -83,7 +73,7 @@ const ReactionsModal: React.FC = ({ onClos }, [reactions, reaction]); useEffect(() => { - fetchData(); + dispatch(fetchReactions(statusId)); }, []); let body; diff --git a/packages/pl-fe/src/locales/en.json b/packages/pl-fe/src/locales/en.json index b6d2e200d..8eaa31210 100644 --- a/packages/pl-fe/src/locales/en.json +++ b/packages/pl-fe/src/locales/en.json @@ -1475,13 +1475,8 @@ "status.pinned": "Pinned post", "status.quote": "Quote post", "status.quote_tombstone": "Post is unavailable.", - "status.reactions.cry": "Sad", "status.reactions.empty": "No one has reacted to this post yet. When someone does, they will show up here.", - "status.reactions.heart": "Love", - "status.reactions.laughing": "Haha", - "status.reactions.like": "Like", - "status.reactions.open_mouth": "Wow", - "status.reactions.weary": "Weary", + "status.reactions.label": "{count} {count, plural, one {person} other {people}} reacted with {emoji}", "status.read_more": "Read more", "status.reblog": "Repost", "status.reblog_private": "Repost to original audience", @@ -1512,6 +1507,7 @@ "status.unbookmarked": "Bookmark removed.", "status.unmute_conversation": "Unmute conversation", "status.unpin": "Unpin from profile", + "status.view_reactions": "View reactions", "status.visibility.direct": "The post is only visible to mentioned users", "status.visibility.list": "The post is only visible to the members of a list", "status.visibility.list.named": "The post is only visible to the members of a {name} list", diff --git a/packages/pl-fe/src/normalizers/pl-fe/pl-fe-config.ts b/packages/pl-fe/src/normalizers/pl-fe/pl-fe-config.ts index 2ba15a854..08034bc46 100644 --- a/packages/pl-fe/src/normalizers/pl-fe/pl-fe-config.ts +++ b/packages/pl-fe/src/normalizers/pl-fe/pl-fe-config.ts @@ -86,14 +86,6 @@ const PlFeConfigRecord = ImmutableRecord({ navlinks: ImmutableMap({ homeFooter: ImmutableList(), }), - allowedEmoji: ImmutableList([ - '👍', - '❤️', - '😆', - '😮', - '😢', - '😩', - ]), verifiedIcon: '', displayFqn: true, cryptoAddresses: ImmutableList(), diff --git a/packages/pl-fe/src/utils/emoji-reacts.ts b/packages/pl-fe/src/utils/emoji-reacts.ts index 236003d26..a5e743a13 100644 --- a/packages/pl-fe/src/utils/emoji-reacts.ts +++ b/packages/pl-fe/src/utils/emoji-reacts.ts @@ -1,56 +1,8 @@ -import { List as ImmutableList } from 'immutable'; import { emojiReactionSchema, type EmojiReaction } from 'pl-api'; -import type { Status } from 'pl-fe/normalizers'; - -// https://emojipedia.org/facebook -// I've customized them. -const ALLOWED_EMOJI = ImmutableList([ - '👍', - '❤️', - '😆', - '😮', - '😢', - '😩', -]); - const sortEmoji = (emojiReacts: Array): Array => emojiReacts.toSorted(emojiReact => -(emojiReact.count || 0)); -const mergeEmojiFavourites = (emojiReacts: Array | null, favouritesCount: number, favourited: boolean) => { - if (!emojiReacts) return [emojiReactionSchema.parse({ count: favouritesCount, me: favourited, name: '👍' })]; - if (!favouritesCount) return emojiReacts; - const likeIndex = emojiReacts.findIndex(emojiReact => emojiReact.name === '👍'); - if (likeIndex > -1) { - const likeCount = Number(emojiReacts[likeIndex].count); - favourited = favourited || Boolean(emojiReacts[likeIndex].me || false); - return emojiReacts.map((reaction, index) => index === likeIndex ? { - ...reaction, - count: likeCount + favouritesCount, - me: favourited, - } : reaction); - } else { - return [...emojiReacts, emojiReactionSchema.parse({ count: favouritesCount, me: favourited, name: '👍' })]; - } -}; - -const reduceEmoji = (emojiReacts: Array | null, favouritesCount: number, favourited: boolean): Array => ( - sortEmoji(mergeEmojiFavourites(emojiReacts, favouritesCount, favourited))); - -const getReactForStatus = ( - status: Pick, -): EmojiReaction | undefined => { - if (!status.emoji_reactions) return; - - const result = reduceEmoji( - status.emoji_reactions, - status.favourites_count || 0, - status.favourited, - ).filter(e => e.me === true)[0]; - - return typeof result?.name === 'string' ? result : undefined; -}; - const simulateEmojiReact = (emojiReacts: Array, emoji: string, url?: string) => { const idx = emojiReacts.findIndex(e => e.name === emoji); const emojiReact = emojiReacts[idx]; @@ -92,11 +44,7 @@ const simulateUnEmojiReact = (emojiReacts: Array, emoji: string) }; export { - ALLOWED_EMOJI, sortEmoji, - mergeEmojiFavourites, - reduceEmoji, - getReactForStatus, simulateEmojiReact, simulateUnEmojiReact, };