Add reminder when about to post without alt text in web UI (#33760)
This commit is contained in:
		
							parent
							
								
									2beab34ca4
								
							
						
					
					
						commit
						1e70da5e3c
					
				|  | @ -10,6 +10,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; | |||
| 
 | ||||
| import { length } from 'stringz'; | ||||
| 
 | ||||
| import { missingAltTextModal } from 'mastodon/initial_state'; | ||||
| 
 | ||||
| import AutosuggestInput from '../../../components/autosuggest_input'; | ||||
| import AutosuggestTextarea from '../../../components/autosuggest_textarea'; | ||||
| import { Button } from '../../../components/button'; | ||||
|  | @ -65,6 +67,7 @@ class ComposeForm extends ImmutablePureComponent { | |||
|     autoFocus: PropTypes.bool, | ||||
|     withoutNavigation: PropTypes.bool, | ||||
|     anyMedia: PropTypes.bool, | ||||
|     missingAltText: PropTypes.bool, | ||||
|     isInReply: PropTypes.bool, | ||||
|     singleColumn: PropTypes.bool, | ||||
|     lang: PropTypes.string, | ||||
|  | @ -117,7 +120,7 @@ class ComposeForm extends ImmutablePureComponent { | |||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     this.props.onSubmit(); | ||||
|     this.props.onSubmit(missingAltTextModal && this.props.missingAltText); | ||||
| 
 | ||||
|     if (e) { | ||||
|       e.preventDefault(); | ||||
|  |  | |||
|  | @ -9,7 +9,9 @@ import { | |||
|   changeComposeSpoilerText, | ||||
|   insertEmojiCompose, | ||||
|   uploadCompose, | ||||
| } from '../../../actions/compose'; | ||||
| } from 'mastodon/actions/compose'; | ||||
| import { openModal } from 'mastodon/actions/modal'; | ||||
| 
 | ||||
| import ComposeForm from '../components/compose_form'; | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|  | @ -26,6 +28,7 @@ const mapStateToProps = state => ({ | |||
|   isChangingUpload: state.getIn(['compose', 'is_changing_upload']), | ||||
|   isUploading: state.getIn(['compose', 'is_uploading']), | ||||
|   anyMedia: state.getIn(['compose', 'media_attachments']).size > 0, | ||||
|   missingAltText: state.getIn(['compose', 'media_attachments']).some(media => ['image', 'gifv'].includes(media.get('type')) && (media.get('description') ?? '').length === 0), | ||||
|   isInReply: state.getIn(['compose', 'in_reply_to']) !== null, | ||||
|   lang: state.getIn(['compose', 'language']), | ||||
|   maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500), | ||||
|  | @ -37,8 +40,15 @@ const mapDispatchToProps = (dispatch) => ({ | |||
|     dispatch(changeCompose(text)); | ||||
|   }, | ||||
| 
 | ||||
|   onSubmit () { | ||||
|   onSubmit (missingAltText) { | ||||
|     if (missingAltText) { | ||||
|       dispatch(openModal({ | ||||
|         modalType: 'CONFIRM_MISSING_ALT_TEXT', | ||||
|         modalProps: {}, | ||||
|       })); | ||||
|     } else { | ||||
|       dispatch(submitCompose()); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   onClearSuggestions () { | ||||
|  |  | |||
|  | @ -56,14 +56,6 @@ export const ConfirmationModal: React.FC< | |||
| 
 | ||||
|       <div className='safety-action-modal__bottom'> | ||||
|         <div className='safety-action-modal__actions'> | ||||
|           {secondary && ( | ||||
|             <> | ||||
|               <Button onClick={handleSecondary}>{secondary}</Button> | ||||
| 
 | ||||
|               <div className='spacer' /> | ||||
|             </> | ||||
|           )} | ||||
| 
 | ||||
|           <button onClick={handleCancel} className='link-button'> | ||||
|             <FormattedMessage | ||||
|               id='confirmation_modal.cancel' | ||||
|  | @ -71,6 +63,15 @@ export const ConfirmationModal: React.FC< | |||
|             /> | ||||
|           </button> | ||||
| 
 | ||||
|           {secondary && ( | ||||
|             <> | ||||
|               <div className='spacer' /> | ||||
|               <button onClick={handleSecondary} className='link-button'> | ||||
|                 {secondary} | ||||
|               </button> | ||||
|             </> | ||||
|           )} | ||||
| 
 | ||||
|           {/* eslint-disable-next-line jsx-a11y/no-autofocus -- we are in a modal and thus autofocusing is justified */} | ||||
|           <Button onClick={handleClick} autoFocus> | ||||
|             {confirm} | ||||
|  |  | |||
|  | @ -7,3 +7,4 @@ export { ConfirmUnfollowModal } from './unfollow'; | |||
| export { ConfirmClearNotificationsModal } from './clear_notifications'; | ||||
| export { ConfirmLogOutModal } from './log_out'; | ||||
| export { ConfirmFollowToListModal } from './follow_to_list'; | ||||
| export { ConfirmMissingAltTextModal } from './missing_alt_text'; | ||||
|  |  | |||
|  | @ -0,0 +1,81 @@ | |||
| import { useCallback } from 'react'; | ||||
| 
 | ||||
| import { defineMessages, useIntl } from 'react-intl'; | ||||
| 
 | ||||
| import type { Map as ImmutableMap, List as ImmutableList } from 'immutable'; | ||||
| 
 | ||||
| import { submitCompose } from 'mastodon/actions/compose'; | ||||
| import { openModal } from 'mastodon/actions/modal'; | ||||
| import type { MediaAttachment } from 'mastodon/models/media_attachment'; | ||||
| import { useAppDispatch, useAppSelector } from 'mastodon/store'; | ||||
| 
 | ||||
| import type { BaseConfirmationModalProps } from './confirmation_modal'; | ||||
| import { ConfirmationModal } from './confirmation_modal'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   title: { | ||||
|     id: 'confirmations.missing_alt_text.title', | ||||
|     defaultMessage: 'Add alt text?', | ||||
|   }, | ||||
|   confirm: { | ||||
|     id: 'confirmations.missing_alt_text.confirm', | ||||
|     defaultMessage: 'Add alt text', | ||||
|   }, | ||||
|   message: { | ||||
|     id: 'confirmations.missing_alt_text.message', | ||||
|     defaultMessage: | ||||
|       'Your post contains media without alt text. Adding descriptions helps make your content accessible to more people.', | ||||
|   }, | ||||
|   secondary: { | ||||
|     id: 'confirmations.missing_alt_text.secondary', | ||||
|     defaultMessage: 'Post anyway', | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| export const ConfirmMissingAltTextModal: React.FC< | ||||
|   BaseConfirmationModalProps | ||||
| > = ({ onClose }) => { | ||||
|   const intl = useIntl(); | ||||
|   const dispatch = useAppDispatch(); | ||||
|   const mediaId = useAppSelector( | ||||
|     (state) => | ||||
|       ( | ||||
|         (state.compose as ImmutableMap<string, unknown>).get( | ||||
|           'media_attachments', | ||||
|         ) as ImmutableList<MediaAttachment> | ||||
|       ) | ||||
|         .find( | ||||
|           (media) => | ||||
|             ['image', 'gifv'].includes(media.get('type') as string) && | ||||
|             ((media.get('description') ?? '') as string).length === 0, | ||||
|         ) | ||||
|         ?.get('id') as string, | ||||
|   ); | ||||
| 
 | ||||
|   const handleConfirm = useCallback(() => { | ||||
|     dispatch( | ||||
|       openModal({ | ||||
|         modalType: 'FOCAL_POINT', | ||||
|         modalProps: { | ||||
|           mediaId, | ||||
|         }, | ||||
|       }), | ||||
|     ); | ||||
|   }, [dispatch, mediaId]); | ||||
| 
 | ||||
|   const handleSecondary = useCallback(() => { | ||||
|     dispatch(submitCompose()); | ||||
|   }, [dispatch]); | ||||
| 
 | ||||
|   return ( | ||||
|     <ConfirmationModal | ||||
|       title={intl.formatMessage(messages.title)} | ||||
|       message={intl.formatMessage(messages.message)} | ||||
|       confirm={intl.formatMessage(messages.confirm)} | ||||
|       secondary={intl.formatMessage(messages.secondary)} | ||||
|       onConfirm={handleConfirm} | ||||
|       onSecondary={handleSecondary} | ||||
|       onClose={onClose} | ||||
|     /> | ||||
|   ); | ||||
| }; | ||||
|  | @ -37,6 +37,7 @@ import { | |||
|   ConfirmClearNotificationsModal, | ||||
|   ConfirmLogOutModal, | ||||
|   ConfirmFollowToListModal, | ||||
|   ConfirmMissingAltTextModal, | ||||
| } from './confirmation_modals'; | ||||
| import ImageModal from './image_modal'; | ||||
| import MediaModal from './media_modal'; | ||||
|  | @ -58,6 +59,7 @@ export const MODAL_COMPONENTS = { | |||
|   'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }), | ||||
|   'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }), | ||||
|   'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }), | ||||
|   'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }), | ||||
|   'MUTE': MuteModal, | ||||
|   'BLOCK': BlockModal, | ||||
|   'DOMAIN_BLOCK': DomainBlockModal, | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ | |||
|  * @property {string} admin | ||||
|  * @property {boolean=} boost_modal | ||||
|  * @property {boolean=} delete_modal | ||||
|  * @property {boolean=} missing_alt_text_modal | ||||
|  * @property {boolean=} disable_swiping | ||||
|  * @property {boolean=} disable_hover_cards | ||||
|  * @property {string=} disabled_account_id | ||||
|  | @ -88,6 +89,7 @@ export const activityApiEnabled = getMeta('activity_api_enabled'); | |||
| export const autoPlayGif = getMeta('auto_play_gif'); | ||||
| export const boostModal = getMeta('boost_modal'); | ||||
| export const deleteModal = getMeta('delete_modal'); | ||||
| export const missingAltTextModal = getMeta('missing_alt_text_modal'); | ||||
| export const disableSwiping = getMeta('disable_swiping'); | ||||
| export const disableHoverCards = getMeta('disable_hover_cards'); | ||||
| export const disabledAccountId = getMeta('disabled_account_id'); | ||||
|  |  | |||
|  | @ -218,6 +218,10 @@ | |||
|   "confirmations.logout.confirm": "Log out", | ||||
|   "confirmations.logout.message": "Are you sure you want to log out?", | ||||
|   "confirmations.logout.title": "Log out?", | ||||
|   "confirmations.missing_alt_text.confirm": "Add alt text", | ||||
|   "confirmations.missing_alt_text.message": "Your post contains media without alt text. Adding descriptions helps make your content accessible to more people.", | ||||
|   "confirmations.missing_alt_text.secondary": "Post anyway", | ||||
|   "confirmations.missing_alt_text.title": "Add alt text?", | ||||
|   "confirmations.mute.confirm": "Mute", | ||||
|   "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.", | ||||
|  |  | |||
|  | @ -29,6 +29,7 @@ class UserSettings | |||
|     setting :disable_hover_cards, default: false | ||||
|     setting :delete_modal, default: true | ||||
|     setting :reblog_modal, default: false | ||||
|     setting :missing_alt_text_modal, default: true | ||||
|     setting :reduce_motion, default: false | ||||
|     setting :expand_content_warnings, default: false | ||||
|     setting :display_media, default: 'default', in: %w(default show_all hide_all) | ||||
|  |  | |||
|  | @ -12,13 +12,14 @@ class InitialStateSerializer < ActiveModel::Serializer | |||
|   has_one :push_subscription, serializer: REST::WebPushSubscriptionSerializer | ||||
|   has_one :role, serializer: REST::RoleSerializer | ||||
| 
 | ||||
|   def meta | ||||
|   def meta # rubocop:disable Metrics/AbcSize | ||||
|     store = default_meta_store | ||||
| 
 | ||||
|     if object.current_account | ||||
|       store[:me]                = object.current_account.id.to_s | ||||
|       store[:boost_modal]       = object_account_user.setting_boost_modal | ||||
|       store[:delete_modal]      = object_account_user.setting_delete_modal | ||||
|       store[:missing_alt_text_modal] = object_account_user.settings['web.missing_alt_text_modal'] | ||||
|       store[:auto_play_gif]     = object_account_user.setting_auto_play_gif | ||||
|       store[:display_media]     = object_account_user.setting_display_media | ||||
|       store[:expand_spoilers]   = object_account_user.setting_expand_spoilers | ||||
|  |  | |||
|  | @ -71,6 +71,7 @@ | |||
|     .fields-group | ||||
|       = ff.input :'web.reblog_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_boost_modal') | ||||
|       = ff.input :'web.delete_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_delete_modal') | ||||
|       = ff.input :'web.missing_alt_text_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_missing_alt_text_modal') | ||||
| 
 | ||||
|     %h4= t 'appearance.sensitive_content' | ||||
| 
 | ||||
|  |  | |||
|  | @ -233,6 +233,7 @@ en: | |||
|         setting_display_media_show_all: Show all | ||||
|         setting_expand_spoilers: Always expand posts marked with content warnings | ||||
|         setting_hide_network: Hide your social graph | ||||
|         setting_missing_alt_text_modal: Show confirmation dialog before posting media without alt text | ||||
|         setting_reduce_motion: Reduce motion in animations | ||||
|         setting_system_font_ui: Use system's default font | ||||
|         setting_system_scrollbars_ui: Use system's default scrollbar | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue