diff --git a/app/javascript/mastodon/components/button/redesign.module.scss b/app/javascript/mastodon/components/button/redesign.module.scss index f9e024ebce7..2add9c478a9 100644 --- a/app/javascript/mastodon/components/button/redesign.module.scss +++ b/app/javascript/mastodon/components/button/redesign.module.scss @@ -28,6 +28,7 @@ &:hover:not( :active, :disabled, + [aria-disabled='true'], [aria-pressed='true'], [aria-expanded='true'] ) { @@ -40,7 +41,8 @@ outline-offset: 2px; } - &:disabled { + &:disabled, + &[aria-disabled='true'] { cursor: not-allowed; opacity: 0.5; } diff --git a/app/javascript/mastodon/components/tooltip/index.tsx b/app/javascript/mastodon/components/tooltip/index.tsx index 2262f991666..2342e99595f 100644 --- a/app/javascript/mastodon/components/tooltip/index.tsx +++ b/app/javascript/mastodon/components/tooltip/index.tsx @@ -22,8 +22,8 @@ interface ForwardedReferenceProps { onClick: React.MouseEventHandler; onMouseEnter: React.MouseEventHandler; onMouseLeave: React.MouseEventHandler; - onFocus: React.KeyboardEventHandler; - onBlur: React.KeyboardEventHandler; + onFocus: React.FocusEventHandler; + onBlur: React.FocusEventHandler; } type GetTooltipProps = >( @@ -96,7 +96,7 @@ export const Tooltip: React.FC = ({ setIsOpen(false); }, [clearOpenTimeout, isOpen]); - const handleFocus = useCallback( + const handleFocus: React.FocusEventHandler = useCallback( (e) => { if ( e.target instanceof HTMLElement && diff --git a/app/javascript/mastodon/features/compose/containers/spoiler_button_container.js b/app/javascript/mastodon/features/compose/containers/spoiler_button_container.js index b56f0dd8b90..de1a29a1851 100644 --- a/app/javascript/mastodon/features/compose/containers/spoiler_button_container.js +++ b/app/javascript/mastodon/features/compose/containers/spoiler_button_container.js @@ -9,8 +9,8 @@ import { IconButton } from 'mastodon/components/icon_button'; import { changeComposeSpoilerness } from '../../../actions/compose'; const messages = defineMessages({ - marked: { id: 'compose_form.spoiler.marked', defaultMessage: 'Text is hidden behind warning' }, - unmarked: { id: 'compose_form.spoiler.unmarked', defaultMessage: 'Text is not hidden' }, + marked: { id: 'compose_form.spoiler.marked', defaultMessage: 'Remove content warning' }, + unmarked: { id: 'compose_form.spoiler.unmarked', defaultMessage: 'Add content warning' }, }); const mapStateToProps = (state, { intl }) => ({ diff --git a/app/javascript/mastodon/features/compose/redesign/index.tsx b/app/javascript/mastodon/features/compose/redesign/index.tsx index 2642553fe9a..2221fc7f644 100644 --- a/app/javascript/mastodon/features/compose/redesign/index.tsx +++ b/app/javascript/mastodon/features/compose/redesign/index.tsx @@ -1,19 +1,10 @@ import type React from 'react'; import { useCallback, useEffect, useId } from 'react'; -import { FormattedMessage } from 'react-intl'; - import classNames from 'classnames'; -import { FlagIcon, LockSimpleOpenIcon } from '@phosphor-icons/react'; - -import { - changeComposeSpoilerness, - insertEmojiCompose, -} from '@/mastodon/actions/compose'; -import { ToggleButton } from '@/mastodon/components/button/redesign'; +import { insertEmojiCompose } from '@/mastodon/actions/compose'; import { normalizeKey } from '@/mastodon/components/hotkeys/utils'; -import { Icon, useIconWeight } from '@/mastodon/components/icon'; import { closeComposer, getComposerTextarea, @@ -28,8 +19,9 @@ import { ComposeFormHeader } from './header'; import { ComposeHints } from './hints'; import { LanguageButton } from './language'; import { ComposeReply } from './reply'; -import { selectComposeSensitive, selectComposeType } from './selectors'; +import { selectComposeType } from './selectors'; import { ComposeSensitiveField } from './sensitive'; +import { ComposeSettingsMenu } from './settings'; import classes from './styles.module.scss'; import { ComposeTextarea } from './textarea'; import { ComposeVisibility } from './visibility'; @@ -45,15 +37,11 @@ export const RedesignComposeForm: React.FC< RedesignComposeFormProps & React.ComponentPropsWithRef<'form'> > = ({ autoFocus, className, noMinimize, redirectOnSuccess, ...props }) => { const type = useAppSelector(selectComposeType); - const { sensitive } = useAppSelector(selectComposeSensitive); - const { onSensitiveChange, onEmojiPick, onSubmit } = - useComposeHandlers(redirectOnSuccess); + const { onEmojiPick, onSubmit } = useComposeHandlers(redirectOnSuccess); const titleId = useId(); - const sensitiveIcon = useIconWeight(FlagIcon, sensitive && 'fill'); - return (
- - - + - {type === 'message' && ( -

- - -

- )} - { - dispatch(changeComposeSpoilerness()); - }, [dispatch]); - const onEmojiPick: OnEmojiPick = useCallback( (emoji) => { const position = getComposerTextarea()?.selectionStart ?? 0; @@ -179,6 +145,5 @@ function useComposeHandlers(redirectOnSuccess?: boolean) { return { onSubmit, onEmojiPick, - onSensitiveChange, }; } diff --git a/app/javascript/mastodon/features/compose/redesign/language.tsx b/app/javascript/mastodon/features/compose/redesign/language.tsx index 0db27911d75..5100cd8711b 100644 --- a/app/javascript/mastodon/features/compose/redesign/language.tsx +++ b/app/javascript/mastodon/features/compose/redesign/language.tsx @@ -3,10 +3,9 @@ import { useCallback } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { MagnifyingGlassIcon } from '@phosphor-icons/react'; +import { MagnifyingGlassIcon, TranslateIcon } from '@phosphor-icons/react'; import { changeComposeLanguage } from '@/mastodon/actions/compose'; -import { CaretIcon } from '@/mastodon/components/button/redesign'; import { TextInput } from '@/mastodon/components/form_fields/redesign'; import { Menu, @@ -33,7 +32,7 @@ export const LanguageButton: React.FC = () => { return ( - + {langCode.toLocaleUpperCase()} diff --git a/app/javascript/mastodon/features/compose/redesign/modal_sensitive.tsx b/app/javascript/mastodon/features/compose/redesign/modal_sensitive.tsx new file mode 100644 index 00000000000..6ea32b3220a --- /dev/null +++ b/app/javascript/mastodon/features/compose/redesign/modal_sensitive.tsx @@ -0,0 +1,75 @@ +import { useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { changeComposeSpoilerness } from '@/mastodon/actions/compose'; +import { closeModal } from '@/mastodon/actions/modal'; +import { Button } from '@/mastodon/components/button/redesign'; +import { + ModalShell, + ModalActions, + ModalTitle, +} from '@/mastodon/components/modal_shell/redesign'; +import { + requestComposerFocus, + submitComposer, +} from '@/mastodon/reducers/slices/composer'; +import { useAppDispatch } from '@/mastodon/store'; + +const ComposerModalSensitive: React.FC<{ redirectOnSuccess?: boolean }> = ({ + redirectOnSuccess, +}) => { + const dispatch = useAppDispatch(); + const onCancel = useCallback(() => { + dispatch(requestComposerFocus()); + dispatch( + closeModal({ + modalType: 'COMPOSER_ADD_CONTENT_WARNING', + ignoreFocus: false, + }), + ); + }, [dispatch]); + const onContinue = useCallback(() => { + dispatch(changeComposeSpoilerness()); + dispatch(submitComposer({ redirectOnSuccess })); + dispatch( + closeModal({ + modalType: 'COMPOSER_ADD_CONTENT_WARNING', + ignoreFocus: false, + }), + ); + }, [dispatch, redirectOnSuccess]); + + return ( + + + + + + + + + + + + + ); +}; + +export default ComposerModalSensitive; diff --git a/app/javascript/mastodon/features/compose/redesign/selectors.ts b/app/javascript/mastodon/features/compose/redesign/selectors.ts index 6fa6068c402..4afa26546f6 100644 --- a/app/javascript/mastodon/features/compose/redesign/selectors.ts +++ b/app/javascript/mastodon/features/compose/redesign/selectors.ts @@ -1,6 +1,7 @@ import { length } from 'stringz'; import type { ApiMediaAttachmentJSON } from '@/mastodon/api_types/media_attachments'; +import type { ApiQuotePolicy } from '@/mastodon/api_types/quotes'; import { immutableListToSuggestions } from '@/mastodon/components/autosuggest/utils'; import type { StatusVisibility } from '@/mastodon/models/status'; import type { ComposeType } from '@/mastodon/reducers/slices/composer'; @@ -17,6 +18,20 @@ export const selectComposePrivacy = createAppSelector( (privacy, defaultPrivacy) => privacy ?? defaultPrivacy, ); +export const selectComposeQuotePolicy = createAppSelector( + [ + (state) => state.compose.get('quote_policy') as ApiQuotePolicy | undefined, + (state) => state.compose.get('default_quote_policy') as ApiQuotePolicy, + selectComposePrivacy, + ], + (quotePolicy, defaultQuotePolicy, privacy) => { + if (privacy === 'private' || privacy === 'direct') { + return 'nobody'; + } + return quotePolicy ?? defaultQuotePolicy; + }, +); + export const selectComposeType = createAppSelector( [ (state) => state.compose.get('in_reply_to') as string | null, @@ -131,10 +146,12 @@ export const selectComposeSensitive = createAppSelector( [ (state) => !!state.compose.get('spoiler'), (state) => state.compose.get('spoiler_text'), + (state) => !!state.compose.get('sensitive'), ], - (sensitive, text) => ({ - sensitive, + (spoiler, text, sensitive) => ({ + sensitive: spoiler, sensitiveText: typeof text === 'string' ? text : '', + mediaSensitive: sensitive || spoiler, }), ); diff --git a/app/javascript/mastodon/features/compose/redesign/settings.tsx b/app/javascript/mastodon/features/compose/redesign/settings.tsx new file mode 100644 index 00000000000..f9a5b958cc4 --- /dev/null +++ b/app/javascript/mastodon/features/compose/redesign/settings.tsx @@ -0,0 +1,183 @@ +import { useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { EyeSlashIcon, GearIcon, WarningIcon } from '@phosphor-icons/react'; + +import { + changeComposeSensitivity, + changeComposeSpoilerness, +} from '@/mastodon/actions/compose'; +import { setComposeQuotePolicy } from '@/mastodon/actions/compose_typed'; +import type { ApiQuotePolicy } from '@/mastodon/api_types/quotes'; +import { IconButton } from '@/mastodon/components/button/redesign'; +import { + Menu, + MenuItemCheckbox, + MenuItemDivider, + MenuItemGroup, + MenuItemRadio, + MenuList, + MenuTrigger, +} from '@/mastodon/components/menu'; +import { + useAppDispatch, + useAppSelector, +} from '@/mastodon/store/typed_functions'; + +import { + selectComposePrivacy, + selectComposeQuotePolicy, + selectComposeSensitive, +} from './selectors'; + +export const ComposeSettingsMenu: React.FC = () => { + return ( + + + + + + + + + + ); +}; + +const ComposeSettingsInnerMenu: React.FC = () => { + // Quote policy + const quotePolicy = useAppSelector(selectComposeQuotePolicy); + const privacy = useAppSelector(selectComposePrivacy); + const disablePublicQuotes = privacy === 'private'; + + const dispatch = useAppDispatch(); + const onQuotePolicyChange = useCallback( + ({ value }: { value: string }) => { + let newQuotePolicy: ApiQuotePolicy = 'nobody'; + switch (value) { + case 'public': + newQuotePolicy = 'public'; + break; + case 'followers': + newQuotePolicy = 'followers'; + break; + } + dispatch(setComposeQuotePolicy(newQuotePolicy)); + }, + [dispatch], + ); + + // Sensitive content + const { sensitive, mediaSensitive } = useAppSelector(selectComposeSensitive); + + const onSensitiveChange = useCallback(() => { + dispatch(changeComposeSpoilerness()); + }, [dispatch]); + const onMediaSensitiveChange = useCallback(() => { + dispatch(changeComposeSensitivity()); + }, [dispatch]); + + return ( + <> + {privacy !== 'direct' && ( + + } + > + + + + + + + + + + ) + } + keepMenuOpenOnClick + > + + + + )} + + {privacy !== 'direct' && } + + + } + > + + + + + + + + + ); +}; diff --git a/app/javascript/mastodon/features/compose/redesign/styles.module.scss b/app/javascript/mastodon/features/compose/redesign/styles.module.scss index b2d09eb1056..8bdbbcb7408 100644 --- a/app/javascript/mastodon/features/compose/redesign/styles.module.scss +++ b/app/javascript/mastodon/features/compose/redesign/styles.module.scss @@ -119,7 +119,7 @@ .textareaMirror { @include mixins.type-body; - padding: var(--space-2); + padding: var(--space-1); // Prevent automatic page zoom in Safari when focusing // text fields with font-sizes below 16px diff --git a/app/javascript/mastodon/features/compose/redesign/visibility.tsx b/app/javascript/mastodon/features/compose/redesign/visibility.tsx index 38bd1c6552d..16074447548 100644 --- a/app/javascript/mastodon/features/compose/redesign/visibility.tsx +++ b/app/javascript/mastodon/features/compose/redesign/visibility.tsx @@ -1,23 +1,15 @@ -import { useCallback, useState } from 'react'; +import { useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; -import { - ChatCircleDotsIcon, - MagnifyingGlassIcon, - NewspaperIcon, - QuotesIcon, -} from '@phosphor-icons/react'; +import classNames from 'classnames'; -import { - changeComposeVisibility, - setComposeQuotePolicy, -} from '@/mastodon/actions/compose_typed'; -import { openModal } from '@/mastodon/actions/modal'; -import type { ApiQuotePolicy } from '@/mastodon/api_types/quotes'; +import { ChatCircleDotsIcon, EyeIcon } from '@phosphor-icons/react'; + +import { changeComposeVisibility } from '@/mastodon/actions/compose_typed'; import type { StatusVisibility } from '@/mastodon/api_types/statuses'; import { Button, CaretIcon } from '@/mastodon/components/button/redesign'; -import { DisplayNameSimple } from '@/mastodon/components/display_name/simple'; +import { Icon } from '@/mastodon/components/icon'; import { Menu, MenuList, @@ -28,10 +20,11 @@ import { MenuItemRadio, MenuItemCheckbox, } from '@/mastodon/components/menu'; -import { selectPlainAccount } from '@/mastodon/selectors/accounts'; +import { Tooltip } from '@/mastodon/components/tooltip'; import { useAppDispatch, useAppSelector } from '@/mastodon/store'; -import { selectComposeMentions, selectComposePrivacy } from './selectors'; +import { selectComposePrivacy } from './selectors'; +import classes from './styles.module.scss'; export const ComposeVisibility: React.FC<{ className?: string }> = ({ className, @@ -39,28 +32,53 @@ export const ComposeVisibility: React.FC<{ className?: string }> = ({ const privacy = useAppSelector(selectComposePrivacy); const isEditing = useAppSelector((state) => !!state.compose.get('id')); + if (privacy === 'direct') { + return ( +
+ + +
+ ); + } + + if (isEditing) { + return ( +
+ + } + > + {({ getTooltipProps, tooltipId }) => ( + + )} + +
+ ); + } + return (
- - + - {privacy !== 'direct' ? ( - - ) : ( - - )} +
); @@ -69,34 +87,22 @@ export const ComposeVisibility: React.FC<{ className?: string }> = ({ const ComposeVisibilityButtonText: React.FC<{ privacy: StatusVisibility; }> = ({ privacy }) => { - const mentions = useAppSelector(selectComposeMentions); - const firstMentionedAccount = useAppSelector((state) => - selectPlainAccount(state, mentions.at(0)), - ); - - if (privacy === 'public' || privacy === 'unlisted') { + if (privacy === 'public') { return ( ); + } else if (privacy === 'unlisted') { + return ( + + ); } else if (privacy === 'private') { return ( - ); - } else if (mentions.length > 0) { - return ( - , - count: mentions.length - 1, - }} + id='compose.privacy.followers' + defaultMessage='Followers (+ mentions)' /> ); } @@ -109,18 +115,6 @@ const ComposeVisibilityMenu: React.FC = () => { const defaultPrivacy = useAppSelector( (state) => state.compose.get('default_privacy') as StatusVisibility, ); - const currentQuotePolicy = useAppSelector( - (state) => state.compose.get('quote_policy') as ApiQuotePolicy | undefined, - ); - const defaultQuotePolicy = useAppSelector( - (state) => state.compose.get('default_quote_policy') as ApiQuotePolicy, - ); - - // Track the last public quote policy, so the picker remembers what was last used before quoting was disabled. - const [lastQuotePolicy, setLastQuotePolicy] = useState( - defaultQuotePolicy !== 'nobody' ? defaultQuotePolicy : 'public', - ); - const quotePolicy = currentQuotePolicy ?? defaultQuotePolicy; const isReply = useAppSelector((state) => !!state.compose.get('in_reply_to')); @@ -144,31 +138,6 @@ const ComposeVisibilityMenu: React.FC = () => { [defaultPrivacy, dispatch, privacy], ); - const handleQuotePolicyChange = useCallback( - ({ value, checked }: { value: string; checked?: boolean }) => { - let newQuotePolicy: ApiQuotePolicy = 'nobody'; - switch (value) { - case 'public': - newQuotePolicy = 'public'; - setLastQuotePolicy(newQuotePolicy); - break; - case 'followers': - newQuotePolicy = 'followers'; - setLastQuotePolicy(newQuotePolicy); - break; - case 'others': - // If it's not checked, then it's nobody. - if (checked) { - // Only use the default if it's not nobody, as then it'll never be enabled. - newQuotePolicy = lastQuotePolicy; - } - break; - } - dispatch(setComposeQuotePolicy(newQuotePolicy)); - }, - [dispatch, lastQuotePolicy], - ); - const handleSwitchToMessage: React.MouseEventHandler = useCallback(() => { dispatch(changeComposeVisibility('direct')); @@ -202,8 +171,8 @@ const ComposeVisibilityMenu: React.FC = () => { keepMenuOpenOnClick > @@ -212,68 +181,22 @@ const ComposeVisibilityMenu: React.FC = () => { - - - - - - - - - {quotePolicy !== 'nobody' && privacy !== 'private' && ( - } > - - - - - - - - - )} + + + @@ -286,7 +209,7 @@ const ComposeVisibilityMenu: React.FC = () => { ) : ( )} @@ -294,51 +217,3 @@ const ComposeVisibilityMenu: React.FC = () => { ); }; - -const ComposeDirectMenu: React.FC = () => { - const dispatch = useAppDispatch(); - const handleSwitchToPost: React.MouseEventHandler = - useCallback(() => { - dispatch( - openModal({ modalType: 'COMPOSER_SWITCH_TO_POST', modalProps: {} }), - ); - }, [dispatch]); - - const isReply = useAppSelector((state) => !!state.compose.get('in_reply_to')); - - return ( - - - } - > - - - - - - - - - {isReply ? ( - - ) : ( - - )} - - - ); -}; diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx index a5f1c213a63..97e794a94d7 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -110,6 +110,7 @@ export const MODAL_COMPONENTS = { 'COMPOSER_REARRANGE': () => import('@/mastodon/features/compose/redesign/modal_rearrange'), 'COMPOSER_SWITCH_TO_POST': () => import('@/mastodon/features/compose/redesign/modal_switch'), 'COMPOSER_ADD_QUOTE': () => import('@/mastodon/features/compose/redesign/modal_quote'), + 'COMPOSER_ADD_CONTENT_WARNING': () => import('@/mastodon/features/compose/redesign/modal_sensitive'), 'NOTIFICATION_SETTINGS': () => import('@/mastodon/features/notifications_v2/components/notification_settings_modal'), 'NOTIFICATION_REQUESTS_SETTINGS': () => import('@/mastodon/features/notifications/components/notification_requests_settings').then(module => ({default: module.NotificationRequestsSettingsModal})), 'HASHTAG_SETTINGS': () => import('@/mastodon/features/hashtag_timeline/components/column_settings_modal'), diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 14e20027310..9dc24599273 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -507,6 +507,7 @@ "community.column_settings.local_only": "Local only", "community.column_settings.media_only": "Media Only", "community.column_settings.remote_only": "Remote only", + "compose.blur_media": "Blur media", "compose.cancel_modal.body": "You have a draft already in progress. What would you like to do?", "compose.cancel_modal.continue": "Continue draft", "compose.cancel_modal.delete": "Delete draft", @@ -516,7 +517,8 @@ "compose.cancel_modal.edit.title": "Unsaved changes", "compose.cancel_modal.title": "Discard draft", "compose.counter": "{current, number}/{max, number}", - "compose.discoverable": "Discoverable in public feeds & search results", + "compose.discoverable": "Hide from search results", + "compose.discoverable.hint": "Also applies to discovery feeds", "compose.error.blank_post": "Post can't be blank.", "compose.expand": "Show composer", "compose.hints.empty": "Post can't be blank", @@ -530,8 +532,6 @@ "compose.language.change": "Change language", "compose.language.not-found": "No language found", "compose.language.search": "Search languages...", - "compose.message.direct.followers": "{name} {count, plural, =0 {} one {+ # other} other {+ # others}}", - "compose.message.notice": "Messages are not end-to-end encrypted", "compose.message.placeholder": "Add your recipients and your message.", "compose.message.publish": "Send", "compose.message.published.body": "Message sent", @@ -542,16 +542,17 @@ "compose.poll.duration": "Duration:", "compose.poll.multiple": "Allow multiple selections", "compose.post.placeholder": "What would you like to say?", - "compose.post.privacy.followers": "Followers {count, plural, =0 {} one {+ # other} other {+ # others}}", "compose.post.title.edit": "Edit post", "compose.post.title.new": "New post", - "compose.post.to": "To:", - "compose.post.to_message": "Compose a message instead", + "compose.post.to_message": "Convert to private message", "compose.post.to_private_reply": "Reply privately instead", + "compose.privacy.direct.hint": "Visible to everyone mentioned. Not encrypted.", + "compose.privacy.editing": "Visibility can’t be edited after a post has been published.", + "compose.privacy.followers": "Followers (+ mentions)", + "compose.privacy.unlisted": "Public, hidden from search", "compose.publish": "Publish", "compose.published.body": "Post published.", "compose.published.open": "Open", - "compose.quotable": "Allow others to quote", "compose.quote.audio": "Audio file {duration}", "compose.quote.poll": "Poll {sep} {isOpen, select, open {Accepting responses} other {Closed}}", "compose.quote.spoiler": "Content:", @@ -574,8 +575,13 @@ "compose.reply_private.title.edit": "Edit private reply", "compose.reply_private.title.new": "New private reply", "compose.saved.body": "Post saved.", - "compose.sensitive": "Sensitive", + "compose.sensitive.label": "Sensitive content", "compose.sensitive.text": "Content warning", + "compose.sensitive_modal.body": "You haven't added content warning text. If you publish now, your post will not be hidden behind a content warning.", + "compose.sensitive_modal.cancel": "Continue editing", + "compose.sensitive_modal.continue": "Publish without warning", + "compose.sensitive_modal.title": "Publish without content warning?", + "compose.settings": "Settings", "compose.switch_modal.back": "Back", "compose.switch_modal.body": "Your message has limited visibility. If you convert to a post, it will switch to your default post visibility.", "compose.switch_modal.continue": "Continue", @@ -587,13 +593,10 @@ "compose.upload.menu.delete": "Remove image", "compose.upload.menu.edit_alt": "Edit alt text", "compose.upload.menu.rearrange": "Rearrange…", - "compose.visibility.direct_note": "Everyone mentioned", "compose.visibility.quote_policy": "Who can quote", - "compose.visibility.quote_policy.anyone": "Anyone", "compose.visibility.quote_policy.followers": "Followers", + "compose.visibility.quote_policy.only_me_hint": "When your post's visibility is set to Followers, it can only be quoted by you.", "compose.visibility.title": "Visibility", - "compose.visibility.to_post": "Compose a post instead", - "compose.visibility.to_reply": "Reply publicly instead", "compose_form.direct_message_warning_learn_more": "Learn more", "compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any sensitive information over Mastodon.", "compose_form.encryption_warning_messages": "Messages on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.", diff --git a/app/javascript/mastodon/reducers/slices/composer.ts b/app/javascript/mastodon/reducers/slices/composer.ts index 16b247e5021..355cee8f61d 100644 --- a/app/javascript/mastodon/reducers/slices/composer.ts +++ b/app/javascript/mastodon/reducers/slices/composer.ts @@ -314,6 +314,15 @@ export const submitComposer = createAppThunk( modalProps: {}, }), ); + } else if (!!compose.get('spoiler') && !compose.get('spoiler_text')) { + dispatch( + openModal({ + modalType: 'COMPOSER_ADD_CONTENT_WARNING', + modalProps: { + redirectOnSuccess, + }, + }), + ); } else { dispatch( submitCompose((status: ApiStatusJSON) => {