From cc7058349f5c6f572e028573bf8be3d0e6dff876 Mon Sep 17 00:00:00 2001 From: ewwwwwwww Date: Thu, 16 Jun 2022 21:19:53 -0700 Subject: [PATCH 01/15] add status reply hover --- app/soapbox/actions/status_hover_card.js | 24 +++++ .../components/hover_status_wrapper.tsx | 57 ++++++++++ app/soapbox/components/status-hover-card.tsx | 102 ++++++++++++++++++ .../components/status-reply-mentions.tsx | 12 ++- app/soapbox/components/status.tsx | 19 ++-- app/soapbox/features/ui/index.tsx | 5 + .../features/ui/util/async-components.ts | 4 + app/soapbox/reducers/index.ts | 2 + app/soapbox/reducers/status_hover_card.ts | 37 +++++++ 9 files changed, 253 insertions(+), 9 deletions(-) create mode 100644 app/soapbox/actions/status_hover_card.js create mode 100644 app/soapbox/components/hover_status_wrapper.tsx create mode 100644 app/soapbox/components/status-hover-card.tsx create mode 100644 app/soapbox/reducers/status_hover_card.ts diff --git a/app/soapbox/actions/status_hover_card.js b/app/soapbox/actions/status_hover_card.js new file mode 100644 index 000000000..85a946e1f --- /dev/null +++ b/app/soapbox/actions/status_hover_card.js @@ -0,0 +1,24 @@ +export const STATUS_HOVER_CARD_OPEN = 'STATUS_HOVER_CARD_OPEN'; +export const STATUS_HOVER_CARD_UPDATE = 'STATUS_HOVER_CARD_UPDATE'; +export const STATUS_HOVER_CARD_CLOSE = 'STATUS_HOVER_CARD_CLOSE'; + +export function openStatusHoverCard(ref, statusId) { + return { + type: STATUS_HOVER_CARD_OPEN, + ref, + statusId, + }; +} + +export function updateStatusHoverCard() { + return { + type: STATUS_HOVER_CARD_UPDATE, + }; +} + +export function closeStatusHoverCard(force = false) { + return { + type: STATUS_HOVER_CARD_CLOSE, + force, + }; +} diff --git a/app/soapbox/components/hover_status_wrapper.tsx b/app/soapbox/components/hover_status_wrapper.tsx new file mode 100644 index 000000000..580fdaaf9 --- /dev/null +++ b/app/soapbox/components/hover_status_wrapper.tsx @@ -0,0 +1,57 @@ +import classNames from 'classnames'; +import { debounce } from 'lodash'; +import React, { useRef } from 'react'; +import { useDispatch } from 'react-redux'; + +import { + openStatusHoverCard, + closeStatusHoverCard, +} from 'soapbox/actions/status_hover_card'; +import { isMobile } from 'soapbox/is_mobile'; + +const showStatusHoverCard = debounce((dispatch, ref, statusId) => { + dispatch(openStatusHoverCard(ref, statusId)); +}, 300); + +interface IHoverStatusWrapper { + statusId: any, + inline: boolean, + className?: string, +} + +/** Makes a status hover card appear when the wrapped element is hovered. */ +export const HoverStatusWrapper: React.FC = ({ statusId, children, inline = false, className }) => { + const dispatch = useDispatch(); + const ref = useRef(null); + const Elem: keyof JSX.IntrinsicElements = inline ? 'span' : 'div'; + + const handleMouseEnter = () => { + if (!isMobile(window.innerWidth)) { + showStatusHoverCard(dispatch, ref, statusId); + } + }; + + const handleMouseLeave = () => { + showStatusHoverCard.cancel(); + setTimeout(() => dispatch(closeStatusHoverCard()), 200); + }; + + const handleClick = () => { + showStatusHoverCard.cancel(); + dispatch(closeStatusHoverCard(true)); + }; + + return ( + + {children} + + ); +}; + +export { HoverStatusWrapper as default, showStatusHoverCard }; diff --git a/app/soapbox/components/status-hover-card.tsx b/app/soapbox/components/status-hover-card.tsx new file mode 100644 index 000000000..a68bfd9fa --- /dev/null +++ b/app/soapbox/components/status-hover-card.tsx @@ -0,0 +1,102 @@ +import classNames from 'classnames'; +import React, { useEffect, useState } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { usePopper } from 'react-popper'; +import { useHistory } from 'react-router-dom'; + +import { fetchRelationships } from 'soapbox/actions/accounts'; +import { + closeStatusHoverCard, + updateStatusHoverCard, +} from 'soapbox/actions/status_hover_card'; +import ActionButton from 'soapbox/features/ui/components/action-button'; +import BundleContainer from 'soapbox/features/ui/containers/bundle_container'; +import { UserPanel } from 'soapbox/features/ui/util/async-components'; +import StatusContainer from 'soapbox/containers/status_container'; +import { useAppSelector, useAppDispatch } from 'soapbox/hooks'; +import { makeGetStatus } from 'soapbox/selectors'; + +import { showStatusHoverCard } from './hover_status_wrapper'; +import { Card, CardBody, Stack, Text } from './ui'; + +import type { AppDispatch } from 'soapbox/store'; + +const getStatus = makeGetStatus(); + +const handleMouseEnter = (dispatch: AppDispatch): React.MouseEventHandler => { + return () => { + dispatch(updateStatusHoverCard()); + }; +}; + +const handleMouseLeave = (dispatch: AppDispatch): React.MouseEventHandler => { + return () => { + dispatch(closeStatusHoverCard(true)); + }; +}; + +interface IStatusHoverCard { + visible: boolean, +} + +/** Popup status preview that appears when hovering reply to */ +export const StatusHoverCard: React.FC = ({ visible = true }) => { + const dispatch = useAppDispatch(); + const history = useHistory(); + + const [popperElement, setPopperElement] = useState(null); + + const statusId: string | undefined = useAppSelector(state => state.status_hover_card.statusId || undefined); + const targetRef = useAppSelector(state => state.status_hover_card.ref?.current); + + useEffect(() => { + const unlisten = history.listen(() => { + showStatusHoverCard.cancel(); + dispatch(closeStatusHoverCard()); + }); + + return () => { + unlisten(); + }; + }, []); + + const { styles, attributes } = usePopper(targetRef, popperElement, { + placement: 'top' + }); + + if (!statusId) return null; + + const renderStatus = (statusId: string) => { + return ( + // @ts-ignore + + ); + }; + + return ( +
+ + + {renderStatus(statusId)} + + +
+ ); +}; + +export default StatusHoverCard; diff --git a/app/soapbox/components/status-reply-mentions.tsx b/app/soapbox/components/status-reply-mentions.tsx index 9f8d890ae..f52c6c7fd 100644 --- a/app/soapbox/components/status-reply-mentions.tsx +++ b/app/soapbox/components/status-reply-mentions.tsx @@ -4,6 +4,7 @@ import { Link } from 'react-router-dom'; import { openModal } from 'soapbox/actions/modals'; import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper'; +import HoverStatusWrapper from 'soapbox/components/hover_status_wrapper'; import { useAppDispatch } from 'soapbox/hooks'; import type { Account, Status } from 'soapbox/types/entities'; @@ -64,9 +65,18 @@ const StatusReplyMentions: React.FC = ({ status }) => {
, + hover: (children: any) => + + {children} + + }} />
diff --git a/app/soapbox/components/status.tsx b/app/soapbox/components/status.tsx index 4389b1fc1..73a59519c 100644 --- a/app/soapbox/components/status.tsx +++ b/app/soapbox/components/status.tsx @@ -93,6 +93,7 @@ interface IStatus extends RouteComponentProps { history: History, featured?: boolean, withDismiss?: boolean, + hideActionBar?: boolean, } interface IStatusState { @@ -512,14 +513,16 @@ class Status extends ImmutablePureComponent { {poll} {quote} - + {!this.props.hideActionBar && ( + + )} diff --git a/app/soapbox/features/ui/index.tsx b/app/soapbox/features/ui/index.tsx index c1057950c..bcc1153cc 100644 --- a/app/soapbox/features/ui/index.tsx +++ b/app/soapbox/features/ui/index.tsx @@ -102,6 +102,7 @@ import { SidebarMenu, UploadArea, ProfileHoverCard, + StatusHoverCard, Share, NewStatus, IntentionalError, @@ -693,6 +694,10 @@ const UI: React.FC = ({ children }) => { {Component => } + + + {Component => } + diff --git a/app/soapbox/features/ui/util/async-components.ts b/app/soapbox/features/ui/util/async-components.ts index 4670a77c3..2e3c7b8be 100644 --- a/app/soapbox/features/ui/util/async-components.ts +++ b/app/soapbox/features/ui/util/async-components.ts @@ -406,6 +406,10 @@ export function ProfileHoverCard() { return import(/* webpackChunkName: "features/ui" */'soapbox/components/profile-hover-card'); } +export function StatusHoverCard() { + return import(/* webpackChunkName: "features/ui" */'soapbox/components/status-hover-card'); +} + export function CryptoDonate() { return import(/* webpackChunkName: "features/crypto_donate" */'../../crypto_donate'); } diff --git a/app/soapbox/reducers/index.ts b/app/soapbox/reducers/index.ts index a05ef47c4..7c907de75 100644 --- a/app/soapbox/reducers/index.ts +++ b/app/soapbox/reducers/index.ts @@ -53,6 +53,7 @@ import security from './security'; import settings from './settings'; import sidebar from './sidebar'; import soapbox from './soapbox'; +import status_hover_card from './status_hover_card'; import status_lists from './status_lists'; import statuses from './statuses'; import suggestions from './suggestions'; @@ -108,6 +109,7 @@ const reducers = { chat_messages, chat_message_lists, profile_hover_card, + status_hover_card, backups, admin_log, security, diff --git a/app/soapbox/reducers/status_hover_card.ts b/app/soapbox/reducers/status_hover_card.ts new file mode 100644 index 000000000..80169ab85 --- /dev/null +++ b/app/soapbox/reducers/status_hover_card.ts @@ -0,0 +1,37 @@ +import { Record as ImmutableRecord } from 'immutable'; + +import { + STATUS_HOVER_CARD_OPEN, + STATUS_HOVER_CARD_CLOSE, + STATUS_HOVER_CARD_UPDATE, +} from 'soapbox/actions/status_hover_card'; + +import type { AnyAction } from 'redux'; + +const ReducerRecord = ImmutableRecord({ + ref: null as React.MutableRefObject | null, + statusId: '', + hovered: false, +}); + +type State = ReturnType; + +export default function statusHoverCard(state: State = ReducerRecord(), action: AnyAction) { + switch (action.type) { + case STATUS_HOVER_CARD_OPEN: + return state.withMutations((state) => { + state.set('ref', action.ref); + state.set('statusId', action.statusId); + }); + case STATUS_HOVER_CARD_UPDATE: + return state.set('hovered', true); + case STATUS_HOVER_CARD_CLOSE: + if (state.get('hovered') === true && !action.force) + return state; + else + return ReducerRecord(); + default: + return state; + } +} + From 3299d0b972c38d9bd38572f4e46665394b5a98fb Mon Sep 17 00:00:00 2001 From: ewwwwwwww Date: Fri, 17 Jun 2022 12:39:17 -0700 Subject: [PATCH 02/15] fix reply hover blocking bug --- .../{status_hover_card.js => status-hover-card.js} | 0 ...hover_status_wrapper.tsx => hover-status-wrapper.tsx} | 2 +- app/soapbox/components/status-hover-card.tsx | 9 +++++++-- app/soapbox/components/status-reply-mentions.tsx | 2 +- app/soapbox/reducers/index.ts | 2 +- .../{status_hover_card.ts => status-hover-card.ts} | 2 +- 6 files changed, 11 insertions(+), 6 deletions(-) rename app/soapbox/actions/{status_hover_card.js => status-hover-card.js} (100%) rename app/soapbox/components/{hover_status_wrapper.tsx => hover-status-wrapper.tsx} (97%) rename app/soapbox/reducers/{status_hover_card.ts => status-hover-card.ts} (95%) diff --git a/app/soapbox/actions/status_hover_card.js b/app/soapbox/actions/status-hover-card.js similarity index 100% rename from app/soapbox/actions/status_hover_card.js rename to app/soapbox/actions/status-hover-card.js diff --git a/app/soapbox/components/hover_status_wrapper.tsx b/app/soapbox/components/hover-status-wrapper.tsx similarity index 97% rename from app/soapbox/components/hover_status_wrapper.tsx rename to app/soapbox/components/hover-status-wrapper.tsx index 580fdaaf9..6860762e7 100644 --- a/app/soapbox/components/hover_status_wrapper.tsx +++ b/app/soapbox/components/hover-status-wrapper.tsx @@ -6,7 +6,7 @@ import { useDispatch } from 'react-redux'; import { openStatusHoverCard, closeStatusHoverCard, -} from 'soapbox/actions/status_hover_card'; +} from 'soapbox/actions/status-hover-card'; import { isMobile } from 'soapbox/is_mobile'; const showStatusHoverCard = debounce((dispatch, ref, statusId) => { diff --git a/app/soapbox/components/status-hover-card.tsx b/app/soapbox/components/status-hover-card.tsx index a68bfd9fa..379d4b6e2 100644 --- a/app/soapbox/components/status-hover-card.tsx +++ b/app/soapbox/components/status-hover-card.tsx @@ -8,15 +8,16 @@ import { fetchRelationships } from 'soapbox/actions/accounts'; import { closeStatusHoverCard, updateStatusHoverCard, -} from 'soapbox/actions/status_hover_card'; +} from 'soapbox/actions/status-hover-card'; import ActionButton from 'soapbox/features/ui/components/action-button'; import BundleContainer from 'soapbox/features/ui/containers/bundle_container'; import { UserPanel } from 'soapbox/features/ui/util/async-components'; import StatusContainer from 'soapbox/containers/status_container'; import { useAppSelector, useAppDispatch } from 'soapbox/hooks'; import { makeGetStatus } from 'soapbox/selectors'; +import { fetchStatus } from 'soapbox/actions/statuses'; -import { showStatusHoverCard } from './hover_status_wrapper'; +import { showStatusHoverCard } from './hover-status-wrapper'; import { Card, CardBody, Stack, Text } from './ui'; import type { AppDispatch } from 'soapbox/store'; @@ -49,6 +50,10 @@ export const StatusHoverCard: React.FC = ({ visible = true }) const statusId: string | undefined = useAppSelector(state => state.status_hover_card.statusId || undefined); const targetRef = useAppSelector(state => state.status_hover_card.ref?.current); + useEffect(() => { + if (statusId) dispatch(fetchStatus(statusId)); + }, [dispatch, statusId]) + useEffect(() => { const unlisten = history.listen(() => { showStatusHoverCard.cancel(); diff --git a/app/soapbox/components/status-reply-mentions.tsx b/app/soapbox/components/status-reply-mentions.tsx index f52c6c7fd..3a287344a 100644 --- a/app/soapbox/components/status-reply-mentions.tsx +++ b/app/soapbox/components/status-reply-mentions.tsx @@ -4,7 +4,7 @@ import { Link } from 'react-router-dom'; import { openModal } from 'soapbox/actions/modals'; import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper'; -import HoverStatusWrapper from 'soapbox/components/hover_status_wrapper'; +import HoverStatusWrapper from 'soapbox/components/hover-status-wrapper'; import { useAppDispatch } from 'soapbox/hooks'; import type { Account, Status } from 'soapbox/types/entities'; diff --git a/app/soapbox/reducers/index.ts b/app/soapbox/reducers/index.ts index 7c907de75..b0d3ee03f 100644 --- a/app/soapbox/reducers/index.ts +++ b/app/soapbox/reducers/index.ts @@ -53,7 +53,7 @@ import security from './security'; import settings from './settings'; import sidebar from './sidebar'; import soapbox from './soapbox'; -import status_hover_card from './status_hover_card'; +import status_hover_card from './status-hover-card'; import status_lists from './status_lists'; import statuses from './statuses'; import suggestions from './suggestions'; diff --git a/app/soapbox/reducers/status_hover_card.ts b/app/soapbox/reducers/status-hover-card.ts similarity index 95% rename from app/soapbox/reducers/status_hover_card.ts rename to app/soapbox/reducers/status-hover-card.ts index 80169ab85..11c660592 100644 --- a/app/soapbox/reducers/status_hover_card.ts +++ b/app/soapbox/reducers/status-hover-card.ts @@ -4,7 +4,7 @@ import { STATUS_HOVER_CARD_OPEN, STATUS_HOVER_CARD_CLOSE, STATUS_HOVER_CARD_UPDATE, -} from 'soapbox/actions/status_hover_card'; +} from 'soapbox/actions/status-hover-card'; import type { AnyAction } from 'redux'; From 05ccbb9e010e15871a2bce4300dc422a492e1177 Mon Sep 17 00:00:00 2001 From: ewwwwwwww Date: Fri, 17 Jun 2022 12:48:45 -0700 Subject: [PATCH 03/15] fix hover reply locales --- app/soapbox/locales/de.json | 2 +- app/soapbox/locales/defaultMessages.json | 6 +++--- app/soapbox/locales/en-Shaw.json | 2 +- app/soapbox/locales/he.json | 2 +- app/soapbox/locales/is.json | 2 +- app/soapbox/locales/it.json | 2 +- app/soapbox/locales/pl.json | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/soapbox/locales/de.json b/app/soapbox/locales/de.json index c7527aa36..366373390 100644 --- a/app/soapbox/locales/de.json +++ b/app/soapbox/locales/de.json @@ -860,7 +860,7 @@ "reply_mentions.account.add": "Add to mentions", "reply_mentions.account.remove": "Remove from mentions", "reply_mentions.more": "{count, plural, one {einen weiteren Nutzer} other {# weitere Nutzer}}", - "reply_mentions.reply": "Antwort an {accounts}", + "reply_mentions.reply": "Antwort an {accounts}", "reply_mentions.reply_empty": "Antwort auf einen Beitrag", "report.block": "{target} blockieren.", "report.block_hint": "Soll dieses Konto zusammen mit der Meldung auch gleich blockiert werden?", diff --git a/app/soapbox/locales/defaultMessages.json b/app/soapbox/locales/defaultMessages.json index cc961bb29..1d554d379 100644 --- a/app/soapbox/locales/defaultMessages.json +++ b/app/soapbox/locales/defaultMessages.json @@ -1001,7 +1001,7 @@ "id": "reply_mentions.reply_empty" }, { - "defaultMessage": "Replying to {accounts}{more}", + "defaultMessage": "Replying to {accounts}{more}", "id": "reply_mentions.reply" }, { @@ -2470,7 +2470,7 @@ "id": "reply_mentions.reply_empty" }, { - "defaultMessage": "Replying to {accounts}{more}", + "defaultMessage": "Replying to {accounts}{more}", "id": "reply_mentions.reply" }, { @@ -5609,7 +5609,7 @@ "id": "reply_indicator.cancel" }, { - "defaultMessage": "Replying to {accounts}{more}", + "defaultMessage": "Replying to {accounts}{more}", "id": "reply_mentions.reply" }, { diff --git a/app/soapbox/locales/en-Shaw.json b/app/soapbox/locales/en-Shaw.json index a0c457f11..027855309 100644 --- a/app/soapbox/locales/en-Shaw.json +++ b/app/soapbox/locales/en-Shaw.json @@ -860,7 +860,7 @@ "reply_mentions.account.add": "๐‘จ๐‘› ๐‘‘ ๐‘ฅ๐‘ง๐‘ฏ๐‘–๐‘ฉ๐‘ฏ๐‘Ÿ", "reply_mentions.account.remove": "๐‘ฎ๐‘ฆ๐‘ฅ๐‘ต๐‘ ๐‘“๐‘ฎ๐‘ช๐‘ฅ ๐‘ฅ๐‘ง๐‘ฏ๐‘–๐‘ฉ๐‘ฏ๐‘Ÿ", "reply_mentions.more": "{count} ๐‘ฅ๐‘น", - "reply_mentions.reply": "๐‘ฎ๐‘ฆ๐‘๐‘ค๐‘ฒ๐‘ฆ๐‘™ ๐‘‘ {accounts}", + "reply_mentions.reply": "๐‘ฎ๐‘ฆ๐‘๐‘ค๐‘ฒ๐‘ฆ๐‘™ ๐‘‘ {accounts}", "reply_mentions.reply_empty": "๐‘ฎ๐‘ฆ๐‘๐‘ค๐‘ฒ๐‘ฆ๐‘™ ๐‘‘ ๐‘๐‘ด๐‘•๐‘‘", "report.block": "๐‘š๐‘ค๐‘ช๐‘’ {target}", "report.block_hint": "๐‘›๐‘ต ๐‘ฟ ๐‘ท๐‘ค๐‘•๐‘ด ๐‘ข๐‘ช๐‘ฏ๐‘‘ ๐‘‘ ๐‘š๐‘ค๐‘ช๐‘’ ๐‘ž๐‘ฆ๐‘• ๐‘ฉ๐‘’๐‘ฌ๐‘ฏ๐‘‘?", diff --git a/app/soapbox/locales/he.json b/app/soapbox/locales/he.json index 21717e89f..66d636c1d 100644 --- a/app/soapbox/locales/he.json +++ b/app/soapbox/locales/he.json @@ -860,7 +860,7 @@ "reply_mentions.account.add": "ื”ื•ืกืฃ ืœืื–ื›ื•ืจื™ื", "reply_mentions.account.remove": "ื”ืกืจ ืžื”ืื–ื›ื•ืจื™ื", "reply_mentions.more": "{count} ืขื•ื“", - "reply_mentions.reply": "ืžืฉื™ื‘ ืœ-{accounts}", + "reply_mentions.reply": "ืžืฉื™ื‘ ืœ-{accounts}", "reply_mentions.reply_empty": "ืžืฉื™ื‘ ืœืคื•ืกื˜", "report.block": "ื—ืกื•ื {target}", "report.block_hint": "ื”ืื ื’ื ืืชื” ืจื•ืฆื” ืœื—ืกื•ื ืืช ื”ื—ืฉื‘ื•ืŸ ื”ื–ื”?", diff --git a/app/soapbox/locales/is.json b/app/soapbox/locales/is.json index 49e906eb5..50e1f1b0f 100644 --- a/app/soapbox/locales/is.json +++ b/app/soapbox/locales/is.json @@ -789,7 +789,7 @@ "reply_mentions.account.add": "Bรฆta viรฐ รญ tilvรญsanirnar", "reply_mentions.account.remove": "Fjarlรฆgja รบr tilvรญsunum", "reply_mentions.more": "{count} fleirum", - "reply_mentions.reply": "Aรฐ svara {accounts}", + "reply_mentions.reply": "Aรฐ svara {accounts}", "reply_mentions.reply_empty": "Aรฐ svara fรฆrslu", "report.block": "Loka รก {target}", "report.block_hint": "Viltu lรญka loka รก รพennan reikning?", diff --git a/app/soapbox/locales/it.json b/app/soapbox/locales/it.json index e565fb5d6..6972de6c1 100644 --- a/app/soapbox/locales/it.json +++ b/app/soapbox/locales/it.json @@ -860,7 +860,7 @@ "reply_mentions.account.add": "Add to mentions", "reply_mentions.account.remove": "Remove from mentions", "reply_mentions.more": "ancora {count}", - "reply_mentions.reply": "Risponde a {accounts}", + "reply_mentions.reply": "Risponde a {accounts}", "reply_mentions.reply_empty": "Rispondendo al contenuto", "report.block": "Blocca {target}", "report.block_hint": "Vuoi anche bloccare questa persona?", diff --git a/app/soapbox/locales/pl.json b/app/soapbox/locales/pl.json index 7740949f5..1184a91f6 100644 --- a/app/soapbox/locales/pl.json +++ b/app/soapbox/locales/pl.json @@ -913,7 +913,7 @@ "reply_mentions.account.add": "Dodaj do wspomnianych", "reply_mentions.account.remove": "Usuล„ z wspomnianych", "reply_mentions.more": "{count} wiฤ™cej", - "reply_mentions.reply": "W odpowiedzi do {accounts}", + "reply_mentions.reply": "W odpowiedzi do {accounts}", "reply_mentions.reply_empty": "W odpowiedzi na wpis", "report.block": "Zablokuj {target}", "report.block_hint": "Czy chcesz teลผ zablokowaฤ‡ to konto?", From ba086918b2af3158ae0a77bf87ef7a503ecee445 Mon Sep 17 00:00:00 2001 From: ewwwwwwww Date: Fri, 17 Jun 2022 13:24:52 -0700 Subject: [PATCH 04/15] fix reply hover oopsie --- app/soapbox/components/status-hover-card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/components/status-hover-card.tsx b/app/soapbox/components/status-hover-card.tsx index 379d4b6e2..e438834b2 100644 --- a/app/soapbox/components/status-hover-card.tsx +++ b/app/soapbox/components/status-hover-card.tsx @@ -51,7 +51,7 @@ export const StatusHoverCard: React.FC = ({ visible = true }) const targetRef = useAppSelector(state => state.status_hover_card.ref?.current); useEffect(() => { - if (statusId) dispatch(fetchStatus(statusId)); + dispatch(fetchStatus(statusId)); }, [dispatch, statusId]) useEffect(() => { From 1d79b59bbcbfa5ae2d0910ecb966392d89a60246 Mon Sep 17 00:00:00 2001 From: ewwwwwwww Date: Sun, 19 Jun 2022 15:50:51 -0700 Subject: [PATCH 05/15] fix z-index --- app/soapbox/components/profile-hover-card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/components/profile-hover-card.tsx b/app/soapbox/components/profile-hover-card.tsx index 11de952f2..c64f54092 100644 --- a/app/soapbox/components/profile-hover-card.tsx +++ b/app/soapbox/components/profile-hover-card.tsx @@ -102,7 +102,7 @@ export const ProfileHoverCard: React.FC = ({ visible = true } 'opacity-0 pointer-events-none': !visible, })} ref={setPopperElement} - style={styles.popper} + style={{...styles.popper, zIndex: 100}} {...attributes.popper} onMouseEnter={handleMouseEnter(dispatch)} onMouseLeave={handleMouseLeave(dispatch)} From f1023b3f8017f5be5d4b0ecca712dc8cfac5e9a4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 20 Jun 2022 16:49:23 -0500 Subject: [PATCH 06/15] ProfileHoverCard: set z-index with Tailwind class --- app/soapbox/components/profile-hover-card.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/soapbox/components/profile-hover-card.tsx b/app/soapbox/components/profile-hover-card.tsx index c64f54092..177ff43b6 100644 --- a/app/soapbox/components/profile-hover-card.tsx +++ b/app/soapbox/components/profile-hover-card.tsx @@ -97,12 +97,12 @@ export const ProfileHoverCard: React.FC = ({ visible = true } return (
Date: Mon, 20 Jun 2022 16:51:25 -0500 Subject: [PATCH 07/15] StatusReplyMentions: linter fixes --- .../components/status-reply-mentions.tsx | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/app/soapbox/components/status-reply-mentions.tsx b/app/soapbox/components/status-reply-mentions.tsx index 3a287344a..c7d642b99 100644 --- a/app/soapbox/components/status-reply-mentions.tsx +++ b/app/soapbox/components/status-reply-mentions.tsx @@ -3,8 +3,8 @@ import { FormattedList, FormattedMessage } from 'react-intl'; import { Link } from 'react-router-dom'; import { openModal } from 'soapbox/actions/modals'; -import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper'; import HoverStatusWrapper from 'soapbox/components/hover-status-wrapper'; +import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper'; import { useAppDispatch } from 'soapbox/hooks'; import type { Account, Status } from 'soapbox/types/entities'; @@ -68,15 +68,17 @@ const StatusReplyMentions: React.FC = ({ status }) => { defaultMessage='Replying to {accounts}' values={{ accounts: , - hover: (children: any) => - - {children} - - + hover: (children: React.ReactNode) => ( + + + {children} + + + ), }} />
From 3bbc4cffe8b0e649f735638b92c5632a5b5c43b4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 20 Jun 2022 16:55:08 -0500 Subject: [PATCH 08/15] actions/status-hover-card: convert to TypeScript --- app/soapbox/actions/status-hover-card.js | 24 --------------------- app/soapbox/actions/status-hover-card.ts | 27 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 24 deletions(-) delete mode 100644 app/soapbox/actions/status-hover-card.js create mode 100644 app/soapbox/actions/status-hover-card.ts diff --git a/app/soapbox/actions/status-hover-card.js b/app/soapbox/actions/status-hover-card.js deleted file mode 100644 index 85a946e1f..000000000 --- a/app/soapbox/actions/status-hover-card.js +++ /dev/null @@ -1,24 +0,0 @@ -export const STATUS_HOVER_CARD_OPEN = 'STATUS_HOVER_CARD_OPEN'; -export const STATUS_HOVER_CARD_UPDATE = 'STATUS_HOVER_CARD_UPDATE'; -export const STATUS_HOVER_CARD_CLOSE = 'STATUS_HOVER_CARD_CLOSE'; - -export function openStatusHoverCard(ref, statusId) { - return { - type: STATUS_HOVER_CARD_OPEN, - ref, - statusId, - }; -} - -export function updateStatusHoverCard() { - return { - type: STATUS_HOVER_CARD_UPDATE, - }; -} - -export function closeStatusHoverCard(force = false) { - return { - type: STATUS_HOVER_CARD_CLOSE, - force, - }; -} diff --git a/app/soapbox/actions/status-hover-card.ts b/app/soapbox/actions/status-hover-card.ts new file mode 100644 index 000000000..2ce24a745 --- /dev/null +++ b/app/soapbox/actions/status-hover-card.ts @@ -0,0 +1,27 @@ +const STATUS_HOVER_CARD_OPEN = 'STATUS_HOVER_CARD_OPEN'; +const STATUS_HOVER_CARD_UPDATE = 'STATUS_HOVER_CARD_UPDATE'; +const STATUS_HOVER_CARD_CLOSE = 'STATUS_HOVER_CARD_CLOSE'; + +const openStatusHoverCard = (ref: React.MutableRefObject, statusId: string) => ({ + type: STATUS_HOVER_CARD_OPEN, + ref, + statusId, +}); + +const updateStatusHoverCard = () => ({ + type: STATUS_HOVER_CARD_UPDATE, +}); + +const closeStatusHoverCard = (force = false) => ({ + type: STATUS_HOVER_CARD_CLOSE, + force, +}); + +export { + STATUS_HOVER_CARD_OPEN, + STATUS_HOVER_CARD_UPDATE, + STATUS_HOVER_CARD_CLOSE, + openStatusHoverCard, + updateStatusHoverCard, + closeStatusHoverCard, +}; From 0292e4f42893958f62ec0e7e2215c744a4019a43 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 20 Jun 2022 17:04:34 -0500 Subject: [PATCH 09/15] StatusHoverCard: fix lint stuff, cleanup --- app/soapbox/components/status-hover-card.tsx | 55 +++++++++----------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/app/soapbox/components/status-hover-card.tsx b/app/soapbox/components/status-hover-card.tsx index e438834b2..c0d9c803b 100644 --- a/app/soapbox/components/status-hover-card.tsx +++ b/app/soapbox/components/status-hover-card.tsx @@ -1,40 +1,18 @@ import classNames from 'classnames'; -import React, { useEffect, useState } from 'react'; -import { FormattedMessage } from 'react-intl'; +import React, { useEffect, useState, useCallback } from 'react'; import { usePopper } from 'react-popper'; import { useHistory } from 'react-router-dom'; -import { fetchRelationships } from 'soapbox/actions/accounts'; import { closeStatusHoverCard, updateStatusHoverCard, } from 'soapbox/actions/status-hover-card'; -import ActionButton from 'soapbox/features/ui/components/action-button'; -import BundleContainer from 'soapbox/features/ui/containers/bundle_container'; -import { UserPanel } from 'soapbox/features/ui/util/async-components'; +import { fetchStatus } from 'soapbox/actions/statuses'; import StatusContainer from 'soapbox/containers/status_container'; import { useAppSelector, useAppDispatch } from 'soapbox/hooks'; -import { makeGetStatus } from 'soapbox/selectors'; -import { fetchStatus } from 'soapbox/actions/statuses'; import { showStatusHoverCard } from './hover-status-wrapper'; -import { Card, CardBody, Stack, Text } from './ui'; - -import type { AppDispatch } from 'soapbox/store'; - -const getStatus = makeGetStatus(); - -const handleMouseEnter = (dispatch: AppDispatch): React.MouseEventHandler => { - return () => { - dispatch(updateStatusHoverCard()); - }; -}; - -const handleMouseLeave = (dispatch: AppDispatch): React.MouseEventHandler => { - return () => { - dispatch(closeStatusHoverCard(true)); - }; -}; +import { Card, CardBody } from './ui'; interface IStatusHoverCard { visible: boolean, @@ -48,11 +26,14 @@ export const StatusHoverCard: React.FC = ({ visible = true }) const [popperElement, setPopperElement] = useState(null); const statusId: string | undefined = useAppSelector(state => state.status_hover_card.statusId || undefined); + const status = useAppSelector(state => state.statuses.get(statusId!)); const targetRef = useAppSelector(state => state.status_hover_card.ref?.current); useEffect(() => { - dispatch(fetchStatus(statusId)); - }, [dispatch, statusId]) + if (!status) { + dispatch(fetchStatus(statusId)); + } + }, [statusId, status]); useEffect(() => { const unlisten = history.listen(() => { @@ -66,9 +47,21 @@ export const StatusHoverCard: React.FC = ({ visible = true }) }, []); const { styles, attributes } = usePopper(targetRef, popperElement, { - placement: 'top' + placement: 'top', }); + const handleMouseEnter = useCallback((): React.MouseEventHandler => { + return () => { + dispatch(updateStatusHoverCard()); + }; + }, []); + + const handleMouseLeave = useCallback((): React.MouseEventHandler => { + return () => { + dispatch(closeStatusHoverCard(true)); + }; + }, []); + if (!statusId) return null; const renderStatus = (statusId: string) => { @@ -85,15 +78,15 @@ export const StatusHoverCard: React.FC = ({ visible = true }) return (
From f1f6892d921cf6d4ec3f92b2b38137978fb0e1c9 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 20 Jun 2022 17:06:48 -0500 Subject: [PATCH 10/15] StatusHoverCard: prefer attachment thumbs --- app/soapbox/components/status-hover-card.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/soapbox/components/status-hover-card.tsx b/app/soapbox/components/status-hover-card.tsx index c0d9c803b..b367c0311 100644 --- a/app/soapbox/components/status-hover-card.tsx +++ b/app/soapbox/components/status-hover-card.tsx @@ -71,6 +71,7 @@ export const StatusHoverCard: React.FC = ({ visible = true }) key={statusId} id={statusId} hideActionBar + muted /> ); }; From c78e398dad3b30038424c134b84c1d1becc8f769 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 20 Jun 2022 17:20:51 -0500 Subject: [PATCH 11/15] StatusHoverCard: fix useEffect conditional --- app/soapbox/components/status-hover-card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/components/status-hover-card.tsx b/app/soapbox/components/status-hover-card.tsx index b367c0311..c52589a36 100644 --- a/app/soapbox/components/status-hover-card.tsx +++ b/app/soapbox/components/status-hover-card.tsx @@ -30,7 +30,7 @@ export const StatusHoverCard: React.FC = ({ visible = true }) const targetRef = useAppSelector(state => state.status_hover_card.ref?.current); useEffect(() => { - if (!status) { + if (statusId && !status) { dispatch(fetchStatus(statusId)); } }, [statusId, status]); From 01e643e4f6d99e719884bec4d09de91e014069f2 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 20 Jun 2022 17:28:59 -0500 Subject: [PATCH 12/15] StatusHoverCard: ahh, I understand the z-index issue --- app/soapbox/components/status-hover-card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/components/status-hover-card.tsx b/app/soapbox/components/status-hover-card.tsx index c52589a36..7a9685b85 100644 --- a/app/soapbox/components/status-hover-card.tsx +++ b/app/soapbox/components/status-hover-card.tsx @@ -79,7 +79,7 @@ export const StatusHoverCard: React.FC = ({ visible = true }) return (
Date: Mon, 20 Jun 2022 17:37:20 -0500 Subject: [PATCH 13/15] Disallow status card nested hovering --- app/soapbox/components/status-hover-card.tsx | 1 + .../components/status-reply-mentions.tsx | 49 +++++++++++++------ app/soapbox/components/status.tsx | 8 ++- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/app/soapbox/components/status-hover-card.tsx b/app/soapbox/components/status-hover-card.tsx index 7a9685b85..dc02af3f4 100644 --- a/app/soapbox/components/status-hover-card.tsx +++ b/app/soapbox/components/status-hover-card.tsx @@ -70,6 +70,7 @@ export const StatusHoverCard: React.FC = ({ visible = true }) diff --git a/app/soapbox/components/status-reply-mentions.tsx b/app/soapbox/components/status-reply-mentions.tsx index c7d642b99..ce0451fda 100644 --- a/app/soapbox/components/status-reply-mentions.tsx +++ b/app/soapbox/components/status-reply-mentions.tsx @@ -11,9 +11,10 @@ import type { Account, Status } from 'soapbox/types/entities'; interface IStatusReplyMentions { status: Status, + hoverable?: boolean, } -const StatusReplyMentions: React.FC = ({ status }) => { +const StatusReplyMentions: React.FC = ({ status, hoverable = true }) => { const dispatch = useAppDispatch(); const handleOpenMentionsModal: React.MouseEventHandler = (e) => { @@ -47,11 +48,21 @@ const StatusReplyMentions: React.FC = ({ status }) => { } // The typical case with a reply-to and a list of mentions. - const accounts = to.slice(0, 2).map(account => ( - + const accounts = to.slice(0, 2).map(account => { + const link = ( @{account.username} - - )).toArray(); + ); + + if (hoverable) { + return ( + + {link} + + ); + } else { + return link; + } + }).toArray(); if (to.size > 2) { accounts.push( @@ -68,17 +79,23 @@ const StatusReplyMentions: React.FC = ({ status }) => { defaultMessage='Replying to {accounts}' values={{ accounts: , - hover: (children: React.ReactNode) => ( - - - {children} - - - ), + hover: (children: React.ReactNode) => { + if (hoverable) { + return ( + + + {children} + + + ); + } else { + return children; + } + }, }} />
diff --git a/app/soapbox/components/status.tsx b/app/soapbox/components/status.tsx index 73a59519c..f4bd15495 100644 --- a/app/soapbox/components/status.tsx +++ b/app/soapbox/components/status.tsx @@ -94,6 +94,7 @@ interface IStatus extends RouteComponentProps { featured?: boolean, withDismiss?: boolean, hideActionBar?: boolean, + hoverable?: boolean, } interface IStatusState { @@ -106,6 +107,7 @@ class Status extends ImmutablePureComponent { static defaultProps = { focusable: true, + hoverable: true, }; didShowCard = false; @@ -481,6 +483,7 @@ class Status extends ImmutablePureComponent { action={reblogElement} hideActions={!reblogElement} showEdit={!!status.edited_at} + showProfileHoverCard={this.props.hoverable} />
@@ -492,7 +495,10 @@ class Status extends ImmutablePureComponent { )} - + Date: Mon, 20 Jun 2022 17:37:56 -0500 Subject: [PATCH 14/15] ProfileHoverCard: z-[100] no longer necessary --- app/soapbox/components/profile-hover-card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/components/profile-hover-card.tsx b/app/soapbox/components/profile-hover-card.tsx index 177ff43b6..11de952f2 100644 --- a/app/soapbox/components/profile-hover-card.tsx +++ b/app/soapbox/components/profile-hover-card.tsx @@ -97,7 +97,7 @@ export const ProfileHoverCard: React.FC = ({ visible = true } return (
Date: Tue, 21 Jun 2022 13:36:14 -0500 Subject: [PATCH 15/15] reducers/status-hover-card: add tests --- .../__tests__/status-hover-card.test.tsx | 72 +++++++++++++++++++ app/soapbox/reducers/status-hover-card.ts | 5 +- 2 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 app/soapbox/reducers/__tests__/status-hover-card.test.tsx diff --git a/app/soapbox/reducers/__tests__/status-hover-card.test.tsx b/app/soapbox/reducers/__tests__/status-hover-card.test.tsx new file mode 100644 index 000000000..9983b5b14 --- /dev/null +++ b/app/soapbox/reducers/__tests__/status-hover-card.test.tsx @@ -0,0 +1,72 @@ +import { + STATUS_HOVER_CARD_OPEN, + STATUS_HOVER_CARD_CLOSE, + STATUS_HOVER_CARD_UPDATE, +} from 'soapbox/actions/status-hover-card'; + +import reducer, { ReducerRecord } from '../status-hover-card'; + +describe(STATUS_HOVER_CARD_OPEN, () => { + it('sets the ref and statusId', () => { + const ref = { current: document.createElement('div') }; + + const action = { + type: STATUS_HOVER_CARD_OPEN, + ref, + statusId: '1234', + }; + + const result = reducer(undefined, action); + expect(result.ref).toBe(ref); + expect(result.statusId).toBe('1234'); + }); +}); + +describe(STATUS_HOVER_CARD_CLOSE, () => { + it('flushes the state', () => { + const state = ReducerRecord({ + ref: { current: document.createElement('div') }, + statusId: '1234', + }); + + const action = { type: STATUS_HOVER_CARD_CLOSE }; + + const result = reducer(state, action); + expect(result.ref).toBe(null); + expect(result.statusId).toBe(''); + }); + + it('leaves the state alone if hovered', () => { + const state = ReducerRecord({ + ref: { current: document.createElement('div') }, + statusId: '1234', + hovered: true, + }); + + const action = { type: STATUS_HOVER_CARD_CLOSE }; + const result = reducer(state, action); + expect(result).toEqual(state); + }); + + it('action.force flushes the state even if hovered', () => { + const state = ReducerRecord({ + ref: { current: document.createElement('div') }, + statusId: '1234', + hovered: true, + }); + + const action = { type: STATUS_HOVER_CARD_CLOSE, force: true }; + const result = reducer(state, action); + expect(result.ref).toBe(null); + expect(result.statusId).toBe(''); + }); +}); + +describe(STATUS_HOVER_CARD_UPDATE, () => { + it('sets hovered', () => { + const state = ReducerRecord(); + const action = { type: STATUS_HOVER_CARD_UPDATE }; + const result = reducer(state, action); + expect(result.hovered).toBe(true); + }); +}); diff --git a/app/soapbox/reducers/status-hover-card.ts b/app/soapbox/reducers/status-hover-card.ts index 11c660592..1d3d1da8d 100644 --- a/app/soapbox/reducers/status-hover-card.ts +++ b/app/soapbox/reducers/status-hover-card.ts @@ -8,7 +8,7 @@ import { import type { AnyAction } from 'redux'; -const ReducerRecord = ImmutableRecord({ +export const ReducerRecord = ImmutableRecord({ ref: null as React.MutableRefObject | null, statusId: '', hovered: false, @@ -26,7 +26,7 @@ export default function statusHoverCard(state: State = ReducerRecord(), action: case STATUS_HOVER_CARD_UPDATE: return state.set('hovered', true); case STATUS_HOVER_CARD_CLOSE: - if (state.get('hovered') === true && !action.force) + if (state.hovered === true && !action.force) return state; else return ReducerRecord(); @@ -34,4 +34,3 @@ export default function statusHoverCard(state: State = ReducerRecord(), action: return state; } } -