From 38fa0102c111f1c0cff5a42eba6882ffae4ca109 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Mon, 15 Sep 2025 17:38:11 +0200 Subject: [PATCH] Fix logged-out quote menu UX, simplify Interaction dialog copy (#36124) --- .../mastodon/components/follow_button.tsx | 1 - app/javascript/mastodon/components/poll.tsx | 1 - .../components/status/boost_button.tsx | 46 +++++--- .../components/status/boost_button_utils.ts | 4 +- .../components/status_action_bar/index.jsx | 4 +- .../mastodon/containers/status_container.jsx | 3 +- .../features/interaction_modal/index.tsx | 109 ++---------------- .../picture_in_picture/components/footer.tsx | 3 - .../mastodon/features/status/index.jsx | 3 - app/javascript/mastodon/locales/en.json | 12 +- .../styles/mastodon/components.scss | 7 -- 11 files changed, 51 insertions(+), 142 deletions(-) diff --git a/app/javascript/mastodon/components/follow_button.tsx b/app/javascript/mastodon/components/follow_button.tsx index 4a22bb1c3f..98ef3ba3f1 100644 --- a/app/javascript/mastodon/components/follow_button.tsx +++ b/app/javascript/mastodon/components/follow_button.tsx @@ -46,7 +46,6 @@ export const FollowButton: React.FC<{ openModal({ modalType: 'INTERACTION', modalProps: { - type: 'follow', accountId: accountId, url: account?.url, }, diff --git a/app/javascript/mastodon/components/poll.tsx b/app/javascript/mastodon/components/poll.tsx index d9e76617d0..80444f6406 100644 --- a/app/javascript/mastodon/components/poll.tsx +++ b/app/javascript/mastodon/components/poll.tsx @@ -109,7 +109,6 @@ export const Poll: React.FC = ({ pollId, disabled, status }) => { openModal({ modalType: 'INTERACTION', modalProps: { - type: 'vote', accountId: status.getIn(['account', 'id']), url: status.get('uri'), }, diff --git a/app/javascript/mastodon/components/status/boost_button.tsx b/app/javascript/mastodon/components/status/boost_button.tsx index 7352c0c535..33f4b1a728 100644 --- a/app/javascript/mastodon/components/status/boost_button.tsx +++ b/app/javascript/mastodon/components/status/boost_button.tsx @@ -72,6 +72,18 @@ export const StatusBoostButton: FC = ({ const statusId = status.get('id') as string; const wasBoosted = !!status.get('reblogged'); + const showLoginPrompt = useCallback(() => { + dispatch( + openModal({ + modalType: 'INTERACTION', + modalProps: { + accountId: status.getIn(['account', 'id']), + url: status.get('uri'), + }, + }), + ); + }, [dispatch, status]); + const items = useMemo(() => { const boostItem = boostItemState(statusState); const quoteItem = quoteItemState(statusState); @@ -87,6 +99,8 @@ export const StatusBoostButton: FC = ({ action: (event) => { if (isLoggedIn) { dispatch(toggleReblog(statusId, event.shiftKey)); + } else { + showLoginPrompt(); } }, }, @@ -100,34 +114,37 @@ export const StatusBoostButton: FC = ({ action: () => { if (isLoggedIn) { dispatch(quoteComposeById(statusId)); + } else { + showLoginPrompt(); } }, }, ] satisfies [ActionMenuItemWithIcon, ActionMenuItemWithIcon]; - }, [dispatch, intl, isLoggedIn, statusId, statusState, wasBoosted]); + }, [ + dispatch, + intl, + isLoggedIn, + showLoginPrompt, + statusId, + statusState, + wasBoosted, + ]); const boostIcon = items[0].icon; const handleDropdownOpen = useCallback( (event: MouseEvent | KeyboardEvent) => { - if (!isLoggedIn) { - dispatch( - openModal({ - modalType: 'INTERACTION', - modalProps: { - type: 'reblog', - accountId: status.getIn(['account', 'id']), - url: status.get('uri'), - }, - }), - ); - } else if (event.shiftKey) { + if (event.shiftKey) { + if (!isLoggedIn) { + showLoginPrompt(); + return false; + } dispatch(toggleReblog(status.get('id'), true)); return false; } return true; }, - [dispatch, isLoggedIn, status], + [dispatch, isLoggedIn, showLoginPrompt, status], ); return ( @@ -223,7 +240,6 @@ export const LegacyReblogButton: FC = ({ openModal({ modalType: 'INTERACTION', modalProps: { - type: 'reblog', accountId: status.getIn(['account', 'id']), url: status.get('uri'), }, diff --git a/app/javascript/mastodon/components/status/boost_button_utils.ts b/app/javascript/mastodon/components/status/boost_button_utils.ts index 34fa26acea..2c40cb2b28 100644 --- a/app/javascript/mastodon/components/status/boost_button_utils.ts +++ b/app/javascript/mastodon/components/status/boost_button_utils.ts @@ -129,6 +129,7 @@ export function boostItemState({ } export function quoteItemState({ + isLoggedIn, isMine, isQuoteAutomaticallyAccepted, isQuoteManuallyAccepted, @@ -149,7 +150,8 @@ export function quoteItemState({ } else if (isQuoteManuallyAccepted) { iconText.title = messages.request_quote; iconText.meta = messages.quote_manual_review; - } else { + // We don't show the disabled state when logged out + } else if (isLoggedIn) { iconText.disabled = true; iconText.iconComponent = FormatQuoteOff; iconText.meta = isQuoteFollowersOnly diff --git a/app/javascript/mastodon/components/status_action_bar/index.jsx b/app/javascript/mastodon/components/status_action_bar/index.jsx index 3aff359c10..0e72a8cefe 100644 --- a/app/javascript/mastodon/components/status_action_bar/index.jsx +++ b/app/javascript/mastodon/components/status_action_bar/index.jsx @@ -122,7 +122,7 @@ class StatusActionBar extends ImmutablePureComponent { if (signedIn) { this.props.onReply(this.props.status); } else { - this.props.onInteractionModal('reply', this.props.status); + this.props.onInteractionModal(this.props.status); } }; @@ -140,7 +140,7 @@ class StatusActionBar extends ImmutablePureComponent { if (signedIn) { this.props.onFavourite(this.props.status); } else { - this.props.onInteractionModal('favourite', this.props.status); + this.props.onInteractionModal(this.props.status); } }; diff --git a/app/javascript/mastodon/containers/status_container.jsx b/app/javascript/mastodon/containers/status_container.jsx index e73ef5b96e..ba280ffd23 100644 --- a/app/javascript/mastodon/containers/status_container.jsx +++ b/app/javascript/mastodon/containers/status_container.jsx @@ -235,11 +235,10 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({ dispatch(deployPictureInPicture({statusId: status.get('id'), accountId: status.getIn(['account', 'id']), playerType: type, props: mediaProps})); }, - onInteractionModal (type, status) { + onInteractionModal (status) { dispatch(openModal({ modalType: 'INTERACTION', modalProps: { - type, accountId: status.getIn(['account', 'id']), url: status.get('uri'), }, diff --git a/app/javascript/mastodon/features/interaction_modal/index.tsx b/app/javascript/mastodon/features/interaction_modal/index.tsx index 40d498d1ae..2abfceaa0b 100644 --- a/app/javascript/mastodon/features/interaction_modal/index.tsx +++ b/app/javascript/mastodon/features/interaction_modal/index.tsx @@ -7,15 +7,9 @@ import classNames from 'classnames'; import { escapeRegExp } from 'lodash'; import { useDebouncedCallback } from 'use-debounce'; -import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react'; -import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; -import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; -import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; -import StarIcon from '@/material-icons/400-24px/star.svg?react'; import { openModal, closeModal } from 'mastodon/actions/modal'; import { apiRequest } from 'mastodon/api'; import { Button } from 'mastodon/components/button'; -import { Icon } from 'mastodon/components/icon'; import { domain as localDomain, registrationsOpen, @@ -408,8 +402,7 @@ const LoginForm: React.FC<{ const InteractionModal: React.FC<{ accountId: string; url: string; - type: 'reply' | 'reblog' | 'favourite' | 'follow' | 'vote'; -}> = ({ accountId, url, type }) => { +}> = ({ accountId, url }) => { const dispatch = useAppDispatch(); const displayNameHtml = useAppSelector( (state) => state.accounts.get(accountId)?.display_name_html ?? '', @@ -437,93 +430,6 @@ const InteractionModal: React.FC<{ ); }, [dispatch]); - let title: React.ReactNode, - icon: React.ReactNode, - actionPrompt: React.ReactNode; - - switch (type) { - case 'reply': - icon = ; - title = ( - - ); - actionPrompt = ( - - ); - break; - case 'reblog': - icon = ; - title = ( - - ); - actionPrompt = ( - - ); - break; - case 'favourite': - icon = ; - title = ( - - ); - actionPrompt = ( - - ); - break; - case 'follow': - icon = ; - title = ( - - ); - actionPrompt = ( - - ); - break; - case 'vote': - icon = ; - title = ( - - ); - actionPrompt = ( - - ); - break; - } - let signupButton; if (sso_redirect) { @@ -559,9 +465,18 @@ const InteractionModal: React.FC<{

- {icon} {title} +

-

{actionPrompt}

+

+ +

diff --git a/app/javascript/mastodon/features/picture_in_picture/components/footer.tsx b/app/javascript/mastodon/features/picture_in_picture/components/footer.tsx index ddcc386ad8..d1671b4bda 100644 --- a/app/javascript/mastodon/features/picture_in_picture/components/footer.tsx +++ b/app/javascript/mastodon/features/picture_in_picture/components/footer.tsx @@ -92,7 +92,6 @@ export const Footer: React.FC<{ openModal({ modalType: 'INTERACTION', modalProps: { - type: 'reply', accountId: status.getIn(['account', 'id']), url: status.get('uri'), }, @@ -113,7 +112,6 @@ export const Footer: React.FC<{ openModal({ modalType: 'INTERACTION', modalProps: { - type: 'favourite', accountId: status.getIn(['account', 'id']), url: status.get('uri'), }, @@ -135,7 +133,6 @@ export const Footer: React.FC<{ openModal({ modalType: 'INTERACTION', modalProps: { - type: 'reblog', accountId: status.getIn(['account', 'id']), url: status.get('uri'), }, diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index 362ad2c5e2..b17e982354 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -186,7 +186,6 @@ class Status extends ImmutablePureComponent { dispatch(openModal({ modalType: 'INTERACTION', modalProps: { - type: 'favourite', accountId: status.getIn(['account', 'id']), url: status.get('uri'), }, @@ -216,7 +215,6 @@ class Status extends ImmutablePureComponent { dispatch(openModal({ modalType: 'INTERACTION', modalProps: { - type: 'reply', accountId: status.getIn(['account', 'id']), url: status.get('uri'), }, @@ -234,7 +232,6 @@ class Status extends ImmutablePureComponent { dispatch(openModal({ modalType: 'INTERACTION', modalProps: { - type: 'reblog', accountId: status.getIn(['account', 'id']), url: status.get('uri'), }, diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index ec67ba1d55..89de5db5ea 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -453,20 +453,12 @@ "ignore_notifications_modal.private_mentions_title": "Ignore notifications from unsolicited Private Mentions?", "info_button.label": "Help", "info_button.what_is_alt_text": "

What is alt text?

Alt text provides image descriptions for people with vision impairments, low-bandwidth connections, or those seeking extra context.

You can improve accessibility and understanding for everyone by writing clear, concise, and objective alt text.

  • Capture important elements
  • Summarize text in images
  • Use regular sentence structure
  • Avoid redundant information
  • Focus on trends and key findings in complex visuals (like diagrams or maps)
", - "interaction_modal.action.favourite": "To continue, you need to favorite from your account.", - "interaction_modal.action.follow": "To continue, you need to follow from your account.", - "interaction_modal.action.reblog": "To continue, you need to reblog from your account.", - "interaction_modal.action.reply": "To continue, you need to reply from your account.", - "interaction_modal.action.vote": "To continue, you need to vote from your account.", + "interaction_modal.action": "To interact with {name}'s post, you need to sign into your account on whatever Mastodon server you use.", "interaction_modal.go": "Go", "interaction_modal.no_account_yet": "Don't have an account yet?", "interaction_modal.on_another_server": "On a different server", "interaction_modal.on_this_server": "On this server", - "interaction_modal.title.favourite": "Favorite {name}'s post", - "interaction_modal.title.follow": "Follow {name}", - "interaction_modal.title.reblog": "Boost {name}'s post", - "interaction_modal.title.reply": "Reply to {name}'s post", - "interaction_modal.title.vote": "Vote in {name}'s poll", + "interaction_modal.title": "Sign in to continue", "interaction_modal.username_prompt": "E.g. {example}", "intervals.full.days": "{number, plural, one {# day} other {# days}}", "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}", diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 4c33244c43..bf810fe6e2 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -9533,13 +9533,6 @@ noscript { font-size: 14px; } - &__icon { - color: $highlight-text-color; - display: flex; - align-items: center; - justify-content: center; - } - &__lead { margin-bottom: 20px;