From f316dac83e91e934f85b78bdab7ca05617d855f9 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 10 Apr 2022 19:59:53 -0500 Subject: [PATCH 1/4] eslint: scream if I try putting a JS comment in a JSX text node --- .eslintrc.js | 1 + app/soapbox/components/ui/icon/svg-icon.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 9a92e50a8..d885cbeea 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -141,6 +141,7 @@ module.exports = { 'react/jsx-first-prop-new-line': ['error', 'multiline-multiprop'], 'react/jsx-indent': ['error', 2], // 'react/jsx-no-bind': ['error'], + 'react/jsx-no-comment-textnodes': 'error', 'react/jsx-no-duplicate-props': 'error', 'react/jsx-no-undef': 'error', 'react/jsx-tag-spacing': 'error', diff --git a/app/soapbox/components/ui/icon/svg-icon.tsx b/app/soapbox/components/ui/icon/svg-icon.tsx index 5cb2dd192..84604150d 100644 --- a/app/soapbox/components/ui/icon/svg-icon.tsx +++ b/app/soapbox/components/ui/icon/svg-icon.tsx @@ -30,7 +30,7 @@ const SvgIcon: React.FC = ({ src, alt, size = 24, className }): JSX.El loader={loader} data-testid='svg-icon' > - /* If the fetch fails, fall back to displaying the loader */ + {/* If the fetch fails, fall back to displaying the loader */} {loader} ); From 0912700d153ae7efebd4cf313d79053571872791 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 10 Apr 2022 20:31:24 -0500 Subject: [PATCH 2/4] Create preliminary EmojiButtonWrapper component --- app/soapbox/actions/{modals.js => modals.ts} | 6 +- .../components/emoji-button-wrapper.tsx | 98 +++++++++++++++++++ app/soapbox/components/hoverable.tsx | 63 ------------ app/soapbox/components/status_action_bar.tsx | 15 +-- .../ui/emoji-selector/emoji-selector.tsx | 4 +- .../features/status/components/action-bar.tsx | 45 ++++++--- 6 files changed, 139 insertions(+), 92 deletions(-) rename app/soapbox/actions/{modals.js => modals.ts} (59%) create mode 100644 app/soapbox/components/emoji-button-wrapper.tsx delete mode 100644 app/soapbox/components/hoverable.tsx diff --git a/app/soapbox/actions/modals.js b/app/soapbox/actions/modals.ts similarity index 59% rename from app/soapbox/actions/modals.js rename to app/soapbox/actions/modals.ts index 72604ecc6..9d6e85139 100644 --- a/app/soapbox/actions/modals.js +++ b/app/soapbox/actions/modals.ts @@ -1,7 +1,8 @@ export const MODAL_OPEN = 'MODAL_OPEN'; export const MODAL_CLOSE = 'MODAL_CLOSE'; -export function openModal(type, props) { +/** Open a modal of the given type */ +export function openModal(type: string, props?: any) { return { type: MODAL_OPEN, modalType: type, @@ -9,7 +10,8 @@ export function openModal(type, props) { }; } -export function closeModal(type) { +/** Close the modal */ +export function closeModal(type: string) { return { type: MODAL_CLOSE, modalType: type, diff --git a/app/soapbox/components/emoji-button-wrapper.tsx b/app/soapbox/components/emoji-button-wrapper.tsx new file mode 100644 index 000000000..eb2ec2f3b --- /dev/null +++ b/app/soapbox/components/emoji-button-wrapper.tsx @@ -0,0 +1,98 @@ +import classNames from 'classnames'; +import React, { useState, useRef } from 'react'; +import { usePopper } from 'react-popper'; +import { useDispatch } from 'react-redux'; + +import { simpleEmojiReact } from 'soapbox/actions/emoji_reacts'; +import { openModal } from 'soapbox/actions/modals'; +import EmojiSelector from 'soapbox/components/ui/emoji-selector/emoji-selector'; +import { useAppSelector, useOwnAccount, useSoapboxConfig } from 'soapbox/hooks'; + +interface IEmojiButtonWrapper { + statusId: string, + children: JSX.Element, +} + +/** Provides emoji reaction functionality to the underlying button component */ +const EmojiButtonWrapper: React.FC = ({ statusId, children }): JSX.Element | null => { + const dispatch = useDispatch(); + const ownAccount = useOwnAccount(); + const status = useAppSelector(state => state.statuses.get(statusId)); + const soapboxConfig = useSoapboxConfig(); + + const [visible, setVisible] = useState(false); + // const [focused, setFocused] = useState(false); + + const ref = useRef(null); + const popperRef = useRef(null); + + const { styles, attributes } = usePopper(ref.current, popperRef.current, { + placement: 'top-start', + strategy: 'fixed', + modifiers: [ + { + name: 'offset', + options: { + offset: [-10, 0], + }, + }, + ], + }); + + if (!status) return null; + + const handleMouseEnter = () => { + setVisible(true); + }; + + const handleMouseLeave = () => { + setVisible(false); + }; + + const handleReact = (emoji: string): void => { + if (ownAccount) { + dispatch(simpleEmojiReact(status, emoji)); + } else { + dispatch(openModal('UNAUTHORIZED', { + action: 'FAVOURITE', + ap_id: status.url, + })); + } + + setVisible(false); + }; + + // const handleUnfocus: React.EventHandler = () => { + // setFocused(false); + // }; + + const selector = ( +
+ +
+ ); + + return ( +
+ {React.cloneElement(children, { + ref, + })} + + {selector} +
+ ); +}; + +export default EmojiButtonWrapper; diff --git a/app/soapbox/components/hoverable.tsx b/app/soapbox/components/hoverable.tsx deleted file mode 100644 index 751c413c1..000000000 --- a/app/soapbox/components/hoverable.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import classNames from 'classnames'; -import React, { useState, useRef } from 'react'; -import { usePopper } from 'react-popper'; - -interface IHoverable { - component: JSX.Element, -} - -/** Wrapper to render a given component when hovered */ -const Hoverable: React.FC = ({ - component, - children, -}): JSX.Element => { - - const [portalActive, setPortalActive] = useState(false); - - const ref = useRef(null); - const popperRef = useRef(null); - - const handleMouseEnter = () => { - setPortalActive(true); - }; - - const handleMouseLeave = () => { - setPortalActive(false); - }; - - const { styles, attributes } = usePopper(ref.current, popperRef.current, { - placement: 'top-start', - strategy: 'fixed', - modifiers: [ - { - name: 'offset', - options: { - offset: [-10, 0], - }, - }, - ], - }); - - return ( -
- {children} - -
- {component} -
-
- ); -}; - -export default Hoverable; diff --git a/app/soapbox/components/status_action_bar.tsx b/app/soapbox/components/status_action_bar.tsx index 9011bec71..dbafd64fd 100644 --- a/app/soapbox/components/status_action_bar.tsx +++ b/app/soapbox/components/status_action_bar.tsx @@ -6,8 +6,7 @@ import { connect } from 'react-redux'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import { simpleEmojiReact } from 'soapbox/actions/emoji_reacts'; -import EmojiSelector from 'soapbox/components/emoji_selector'; -import Hoverable from 'soapbox/components/hoverable'; +import EmojiButtonWrapper from 'soapbox/components/emoji-button-wrapper'; import StatusActionButton from 'soapbox/components/status-action-button'; import DropdownMenuContainer from 'soapbox/containers/dropdown_menu_container'; import { isUserTouching } from 'soapbox/is_mobile'; @@ -641,15 +640,7 @@ class StatusActionBar extends ImmutablePureComponent - )} - > + - + ): ( = ({ emoji, className, onClick, tabInd }; interface IEmojiSelector { - emojis: string[], + emojis: Iterable, onReact: (emoji: string) => void, visible?: boolean, focused?: boolean, @@ -40,7 +40,7 @@ const EmojiSelector: React.FC = ({ emojis, onReact, visible = fa space={2} className={classNames('bg-white dark:bg-slate-900 p-3 rounded-full shadow-md z-[999] w-max')} > - {emojis.map((emoji, i) => ( + {Array.from(emojis).map((emoji, i) => ( { {reblogButton} - + {features.emojiReacts ? ( + + + + ) : ( + + )} {canShare && ( Date: Sun, 10 Apr 2022 20:41:00 -0500 Subject: [PATCH 3/4] EmojiButtonWrapper: handle click --- .../components/emoji-button-wrapper.tsx | 19 +++++++++++++++++++ app/soapbox/components/status_action_bar.tsx | 1 - .../features/status/components/action-bar.tsx | 1 - 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/soapbox/components/emoji-button-wrapper.tsx b/app/soapbox/components/emoji-button-wrapper.tsx index eb2ec2f3b..32159b329 100644 --- a/app/soapbox/components/emoji-button-wrapper.tsx +++ b/app/soapbox/components/emoji-button-wrapper.tsx @@ -7,6 +7,8 @@ import { simpleEmojiReact } from 'soapbox/actions/emoji_reacts'; import { openModal } from 'soapbox/actions/modals'; import EmojiSelector from 'soapbox/components/ui/emoji-selector/emoji-selector'; import { useAppSelector, useOwnAccount, useSoapboxConfig } from 'soapbox/hooks'; +import { isUserTouching } from 'soapbox/is_mobile'; +import { getReactForStatus } from 'soapbox/utils/emoji_reacts'; interface IEmojiButtonWrapper { statusId: string, @@ -62,6 +64,22 @@ const EmojiButtonWrapper: React.FC = ({ statusId, children setVisible(false); }; + const handleClick: React.EventHandler = e => { + const meEmojiReact = getReactForStatus(status, soapboxConfig.allowedEmoji) || '👍'; + + if (isUserTouching()) { + if (visible) { + handleReact(meEmojiReact); + } else { + setVisible(true); + } + } else { + handleReact(meEmojiReact); + } + + e.stopPropagation(); + }; + // const handleUnfocus: React.EventHandler = () => { // setFocused(false); // }; @@ -87,6 +105,7 @@ const EmojiButtonWrapper: React.FC = ({ statusId, children return (
{React.cloneElement(children, { + onClick: handleClick, ref, })} diff --git a/app/soapbox/components/status_action_bar.tsx b/app/soapbox/components/status_action_bar.tsx index dbafd64fd..fa99ffa4e 100644 --- a/app/soapbox/components/status_action_bar.tsx +++ b/app/soapbox/components/status_action_bar.tsx @@ -645,7 +645,6 @@ class StatusActionBar extends ImmutablePureComponent diff --git a/app/soapbox/features/status/components/action-bar.tsx b/app/soapbox/features/status/components/action-bar.tsx index 7efb40aff..9ac9b007f 100644 --- a/app/soapbox/features/status/components/action-bar.tsx +++ b/app/soapbox/features/status/components/action-bar.tsx @@ -588,7 +588,6 @@ class ActionBar extends React.PureComponent { 'fill-accent-300': Boolean(meEmojiReact), })} text={meEmojiTitle} - onClick={this.handleLikeButtonClick} /> ) : ( From c5c1f83f36a51168414fdf9dd113c143b5dd19c5 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 10 Apr 2022 20:49:36 -0500 Subject: [PATCH 4/4] Fix lint --- app/soapbox/components/status_action_bar.tsx | 2 +- app/soapbox/features/ui/index.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/soapbox/components/status_action_bar.tsx b/app/soapbox/components/status_action_bar.tsx index fa99ffa4e..41c873c7c 100644 --- a/app/soapbox/components/status_action_bar.tsx +++ b/app/soapbox/components/status_action_bar.tsx @@ -553,7 +553,7 @@ class StatusActionBar extends ImmutablePureComponent - // NOTE: we cannot nest routes in a fragment - // https://stackoverflow.com/a/68637108 + {/* + NOTE: we cannot nest routes in a fragment + https://stackoverflow.com/a/68637108 + */} {features.federating && } {features.federating && } {features.federating && }