Show hint explaining post visibility when quoting a "quiet public" post (#36065)

This commit is contained in:
diondiondion 2025-09-09 14:43:23 +02:00 committed by GitHub
parent 3c79c512fe
commit cf20c5db9c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 112 additions and 10 deletions

View File

@ -16,6 +16,7 @@ import type { Status } from '../models/status';
import { showAlert } from './alerts';
import { focusCompose } from './compose';
import { openModal } from './modal';
const messages = defineMessages({
quoteErrorUpload: {
@ -110,8 +111,16 @@ export const quoteCompose = createAppThunk(
export const quoteComposeByStatus = createAppThunk(
(status: Status, { dispatch, getState }) => {
const composeState = getState().compose;
const state = getState();
const composeState = state.compose;
const mediaAttachments = composeState.get('media_attachments');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const wasQuietPostHintModalDismissed: boolean =
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
state.settings.getIn(
['dismissed_banners', 'quote/quiet_post_hint'],
false,
);
if (composeState.get('poll')) {
dispatch(showAlert({ message: messages.quoteErrorPoll }));
@ -131,6 +140,16 @@ export const quoteComposeByStatus = createAppThunk(
status.getIn(['quote_approval', 'current_user']) !== 'manual'
) {
dispatch(showAlert({ message: messages.quoteErrorUnauthorized }));
} else if (
status.get('visibility') === 'unlisted' &&
!wasQuietPostHintModalDismissed
) {
dispatch(
openModal({
modalType: 'CONFIRM_QUIET_QUOTE',
modalProps: { status },
}),
);
} else {
dispatch(quoteCompose(status));
}

View File

@ -79,10 +79,12 @@ const visibilityOptions = {
const PrivacyModalButton: FC<PrivacyDropdownProps> = ({ disabled = false }) => {
const intl = useIntl();
const { visibility, quotePolicy } = useAppSelector((state) => ({
visibility: state.compose.get('privacy') as StatusVisibility,
quotePolicy: state.compose.get('quote_policy') as ApiQuotePolicy,
}));
const quotePolicy = useAppSelector(
(state) => state.compose.get('quote_policy') as ApiQuotePolicy,
);
const visibility = useAppSelector(
(state) => state.compose.get('privacy') as StatusVisibility,
);
const { icon, iconComponent } = useMemo(() => {
const option = visibilityOptions[visibility];

View File

@ -43,10 +43,6 @@ export const ConfirmationModal: React.FC<
onSecondary?.();
}, [onClose, onSecondary]);
const handleCancel = useCallback(() => {
onClose();
}, [onClose]);
return (
<div className='modal-root__modal safety-action-modal'>
<div className='safety-action-modal__top'>
@ -58,7 +54,7 @@ export const ConfirmationModal: React.FC<
<div className='safety-action-modal__bottom'>
<div className='safety-action-modal__actions'>
<button onClick={handleCancel} className='link-button'>
<button onClick={onClose} className='link-button'>
{cancel ?? (
<FormattedMessage
id='confirmation_modal.cancel'

View File

@ -11,3 +11,4 @@ export { ConfirmLogOutModal } from './log_out';
export { ConfirmFollowToListModal } from './follow_to_list';
export { ConfirmMissingAltTextModal } from './missing_alt_text';
export { ConfirmRevokeQuoteModal } from './revoke_quote';
export { QuietPostQuoteInfoModal } from './quiet_post_quote_info';

View File

@ -0,0 +1,77 @@
import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { quoteCompose } from '@/mastodon/actions/compose_typed';
import { closeModal } from '@/mastodon/actions/modal';
import { changeSetting } from '@/mastodon/actions/settings';
import type { Status } from '@/mastodon/models/status';
import { useAppDispatch } from '@/mastodon/store';
import { ConfirmationModal } from './confirmation_modal';
const messages = defineMessages({
title: {
id: 'confirmations.quiet_post_quote_info.title',
defaultMessage: 'Quoting quiet public posts',
},
message: {
id: 'confirmations.quiet_post_quote_info.message',
defaultMessage:
'When quoting a quiet public post, your post will be hidden from trending timelines.',
},
got_it: {
id: 'confirmations.quiet_post_quote_info.got_it',
defaultMessage: 'Got it',
},
dismiss: {
id: 'confirmations.quiet_post_quote_info.dismiss',
defaultMessage: "Don't remind me again",
},
});
/**
* [1] Since we only want this modal to have two buttons "Don't ask again" and
* "Got it" , we have to use the `onClose` handler to handle the "Don't ask again"
* functionality. Because of this, we need to set `closeWhenConfirm` to false and
* close the modal manually.
* This prevents the modal from being dismissed permanently when just confirming.
*/
export const QuietPostQuoteInfoModal: React.FC<{ status: Status }> = ({
status,
}) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const confirm = useCallback(() => {
dispatch(quoteCompose(status));
// [1]
dispatch(
closeModal({ modalType: 'CONFIRM_QUIET_QUOTE', ignoreFocus: true }),
);
}, [dispatch, status]);
const dismiss = useCallback(() => {
dispatch(quoteCompose(status));
dispatch(
changeSetting(['dismissed_banners', 'quote/quiet_post_hint'], true),
);
// [1]
dispatch(
closeModal({ modalType: 'CONFIRM_QUIET_QUOTE', ignoreFocus: true }),
);
}, [dispatch, status]);
return (
<ConfirmationModal
closeWhenConfirm={false} // [1]
title={intl.formatMessage(messages.title)}
message={intl.formatMessage(messages.message)}
confirm={intl.formatMessage(messages.got_it)}
cancel={intl.formatMessage(messages.dismiss)}
onConfirm={confirm}
onClose={dismiss}
/>
);
};

View File

@ -38,6 +38,7 @@ import {
ConfirmFollowToListModal,
ConfirmMissingAltTextModal,
ConfirmRevokeQuoteModal,
QuietPostQuoteInfoModal,
} from './confirmation_modals';
import { ImageModal } from './image_modal';
import MediaModal from './media_modal';
@ -62,6 +63,7 @@ export const MODAL_COMPONENTS = {
'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }),
'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }),
'CONFIRM_REVOKE_QUOTE': () => Promise.resolve({ default: ConfirmRevokeQuoteModal }),
'CONFIRM_QUIET_QUOTE': () => Promise.resolve({ default: QuietPostQuoteInfoModal }),
'MUTE': MuteModal,
'BLOCK': BlockModal,
'DOMAIN_BLOCK': DomainBlockModal,

View File

@ -239,6 +239,10 @@
"confirmations.missing_alt_text.secondary": "Post anyway",
"confirmations.missing_alt_text.title": "Add alt text?",
"confirmations.mute.confirm": "Mute",
"confirmations.quiet_post_quote_info.dismiss": "Don't remind me again",
"confirmations.quiet_post_quote_info.got_it": "Got it",
"confirmations.quiet_post_quote_info.message": "When quoting a quiet public post, your post will be hidden from trending timelines.",
"confirmations.quiet_post_quote_info.title": "Quoting quiet public posts",
"confirmations.redraft.confirm": "Delete & redraft",
"confirmations.redraft.message": "Are you sure you want to delete this post and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.",
"confirmations.redraft.title": "Delete & redraft post?",

View File

@ -118,6 +118,7 @@ const initialState = ImmutableMap({
'explore/statuses': false,
'explore/tags': false,
'notifications/remove_quote_hint': false,
'quote/quiet_post_hint': false,
}),
});