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 { length } from 'stringz'; | ||||||
| 
 | 
 | ||||||
|  | import { missingAltTextModal } from 'mastodon/initial_state'; | ||||||
|  | 
 | ||||||
| import AutosuggestInput from '../../../components/autosuggest_input'; | import AutosuggestInput from '../../../components/autosuggest_input'; | ||||||
| import AutosuggestTextarea from '../../../components/autosuggest_textarea'; | import AutosuggestTextarea from '../../../components/autosuggest_textarea'; | ||||||
| import { Button } from '../../../components/button'; | import { Button } from '../../../components/button'; | ||||||
|  | @ -65,6 +67,7 @@ class ComposeForm extends ImmutablePureComponent { | ||||||
|     autoFocus: PropTypes.bool, |     autoFocus: PropTypes.bool, | ||||||
|     withoutNavigation: PropTypes.bool, |     withoutNavigation: PropTypes.bool, | ||||||
|     anyMedia: PropTypes.bool, |     anyMedia: PropTypes.bool, | ||||||
|  |     missingAltText: PropTypes.bool, | ||||||
|     isInReply: PropTypes.bool, |     isInReply: PropTypes.bool, | ||||||
|     singleColumn: PropTypes.bool, |     singleColumn: PropTypes.bool, | ||||||
|     lang: PropTypes.string, |     lang: PropTypes.string, | ||||||
|  | @ -117,7 +120,7 @@ class ComposeForm extends ImmutablePureComponent { | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     this.props.onSubmit(); |     this.props.onSubmit(missingAltTextModal && this.props.missingAltText); | ||||||
| 
 | 
 | ||||||
|     if (e) { |     if (e) { | ||||||
|       e.preventDefault(); |       e.preventDefault(); | ||||||
|  |  | ||||||
|  | @ -9,7 +9,9 @@ import { | ||||||
|   changeComposeSpoilerText, |   changeComposeSpoilerText, | ||||||
|   insertEmojiCompose, |   insertEmojiCompose, | ||||||
|   uploadCompose, |   uploadCompose, | ||||||
| } from '../../../actions/compose'; | } from 'mastodon/actions/compose'; | ||||||
|  | import { openModal } from 'mastodon/actions/modal'; | ||||||
|  | 
 | ||||||
| import ComposeForm from '../components/compose_form'; | import ComposeForm from '../components/compose_form'; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = state => ({ | const mapStateToProps = state => ({ | ||||||
|  | @ -26,6 +28,7 @@ const mapStateToProps = state => ({ | ||||||
|   isChangingUpload: state.getIn(['compose', 'is_changing_upload']), |   isChangingUpload: state.getIn(['compose', 'is_changing_upload']), | ||||||
|   isUploading: state.getIn(['compose', 'is_uploading']), |   isUploading: state.getIn(['compose', 'is_uploading']), | ||||||
|   anyMedia: state.getIn(['compose', 'media_attachments']).size > 0, |   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, |   isInReply: state.getIn(['compose', 'in_reply_to']) !== null, | ||||||
|   lang: state.getIn(['compose', 'language']), |   lang: state.getIn(['compose', 'language']), | ||||||
|   maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500), |   maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500), | ||||||
|  | @ -37,8 +40,15 @@ const mapDispatchToProps = (dispatch) => ({ | ||||||
|     dispatch(changeCompose(text)); |     dispatch(changeCompose(text)); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   onSubmit () { |   onSubmit (missingAltText) { | ||||||
|     dispatch(submitCompose()); |     if (missingAltText) { | ||||||
|  |       dispatch(openModal({ | ||||||
|  |         modalType: 'CONFIRM_MISSING_ALT_TEXT', | ||||||
|  |         modalProps: {}, | ||||||
|  |       })); | ||||||
|  |     } else { | ||||||
|  |       dispatch(submitCompose()); | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   onClearSuggestions () { |   onClearSuggestions () { | ||||||
|  |  | ||||||
|  | @ -56,14 +56,6 @@ export const ConfirmationModal: React.FC< | ||||||
| 
 | 
 | ||||||
|       <div className='safety-action-modal__bottom'> |       <div className='safety-action-modal__bottom'> | ||||||
|         <div className='safety-action-modal__actions'> |         <div className='safety-action-modal__actions'> | ||||||
|           {secondary && ( |  | ||||||
|             <> |  | ||||||
|               <Button onClick={handleSecondary}>{secondary}</Button> |  | ||||||
| 
 |  | ||||||
|               <div className='spacer' /> |  | ||||||
|             </> |  | ||||||
|           )} |  | ||||||
| 
 |  | ||||||
|           <button onClick={handleCancel} className='link-button'> |           <button onClick={handleCancel} className='link-button'> | ||||||
|             <FormattedMessage |             <FormattedMessage | ||||||
|               id='confirmation_modal.cancel' |               id='confirmation_modal.cancel' | ||||||
|  | @ -71,6 +63,15 @@ export const ConfirmationModal: React.FC< | ||||||
|             /> |             /> | ||||||
|           </button> |           </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 */} |           {/* eslint-disable-next-line jsx-a11y/no-autofocus -- we are in a modal and thus autofocusing is justified */} | ||||||
|           <Button onClick={handleClick} autoFocus> |           <Button onClick={handleClick} autoFocus> | ||||||
|             {confirm} |             {confirm} | ||||||
|  |  | ||||||
|  | @ -7,3 +7,4 @@ export { ConfirmUnfollowModal } from './unfollow'; | ||||||
| export { ConfirmClearNotificationsModal } from './clear_notifications'; | export { ConfirmClearNotificationsModal } from './clear_notifications'; | ||||||
| export { ConfirmLogOutModal } from './log_out'; | export { ConfirmLogOutModal } from './log_out'; | ||||||
| export { ConfirmFollowToListModal } from './follow_to_list'; | 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, |   ConfirmClearNotificationsModal, | ||||||
|   ConfirmLogOutModal, |   ConfirmLogOutModal, | ||||||
|   ConfirmFollowToListModal, |   ConfirmFollowToListModal, | ||||||
|  |   ConfirmMissingAltTextModal, | ||||||
| } from './confirmation_modals'; | } from './confirmation_modals'; | ||||||
| import ImageModal from './image_modal'; | import ImageModal from './image_modal'; | ||||||
| import MediaModal from './media_modal'; | import MediaModal from './media_modal'; | ||||||
|  | @ -58,6 +59,7 @@ export const MODAL_COMPONENTS = { | ||||||
|   'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }), |   'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }), | ||||||
|   'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }), |   'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }), | ||||||
|   'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }), |   'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }), | ||||||
|  |   'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }), | ||||||
|   'MUTE': MuteModal, |   'MUTE': MuteModal, | ||||||
|   'BLOCK': BlockModal, |   'BLOCK': BlockModal, | ||||||
|   'DOMAIN_BLOCK': DomainBlockModal, |   'DOMAIN_BLOCK': DomainBlockModal, | ||||||
|  |  | ||||||
|  | @ -14,6 +14,7 @@ | ||||||
|  * @property {string} admin |  * @property {string} admin | ||||||
|  * @property {boolean=} boost_modal |  * @property {boolean=} boost_modal | ||||||
|  * @property {boolean=} delete_modal |  * @property {boolean=} delete_modal | ||||||
|  |  * @property {boolean=} missing_alt_text_modal | ||||||
|  * @property {boolean=} disable_swiping |  * @property {boolean=} disable_swiping | ||||||
|  * @property {boolean=} disable_hover_cards |  * @property {boolean=} disable_hover_cards | ||||||
|  * @property {string=} disabled_account_id |  * @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 autoPlayGif = getMeta('auto_play_gif'); | ||||||
| export const boostModal = getMeta('boost_modal'); | export const boostModal = getMeta('boost_modal'); | ||||||
| export const deleteModal = getMeta('delete_modal'); | export const deleteModal = getMeta('delete_modal'); | ||||||
|  | export const missingAltTextModal = getMeta('missing_alt_text_modal'); | ||||||
| export const disableSwiping = getMeta('disable_swiping'); | export const disableSwiping = getMeta('disable_swiping'); | ||||||
| export const disableHoverCards = getMeta('disable_hover_cards'); | export const disableHoverCards = getMeta('disable_hover_cards'); | ||||||
| export const disabledAccountId = getMeta('disabled_account_id'); | export const disabledAccountId = getMeta('disabled_account_id'); | ||||||
|  |  | ||||||
|  | @ -218,6 +218,10 @@ | ||||||
|   "confirmations.logout.confirm": "Log out", |   "confirmations.logout.confirm": "Log out", | ||||||
|   "confirmations.logout.message": "Are you sure you want to log out?", |   "confirmations.logout.message": "Are you sure you want to log out?", | ||||||
|   "confirmations.logout.title": "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.mute.confirm": "Mute", | ||||||
|   "confirmations.redraft.confirm": "Delete & redraft", |   "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.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 :disable_hover_cards, default: false | ||||||
|     setting :delete_modal, default: true |     setting :delete_modal, default: true | ||||||
|     setting :reblog_modal, default: false |     setting :reblog_modal, default: false | ||||||
|  |     setting :missing_alt_text_modal, default: true | ||||||
|     setting :reduce_motion, default: false |     setting :reduce_motion, default: false | ||||||
|     setting :expand_content_warnings, default: false |     setting :expand_content_warnings, default: false | ||||||
|     setting :display_media, default: 'default', in: %w(default show_all hide_all) |     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 :push_subscription, serializer: REST::WebPushSubscriptionSerializer | ||||||
|   has_one :role, serializer: REST::RoleSerializer |   has_one :role, serializer: REST::RoleSerializer | ||||||
| 
 | 
 | ||||||
|   def meta |   def meta # rubocop:disable Metrics/AbcSize | ||||||
|     store = default_meta_store |     store = default_meta_store | ||||||
| 
 | 
 | ||||||
|     if object.current_account |     if object.current_account | ||||||
|       store[:me]                = object.current_account.id.to_s |       store[:me]                = object.current_account.id.to_s | ||||||
|       store[:boost_modal]       = object_account_user.setting_boost_modal |       store[:boost_modal]       = object_account_user.setting_boost_modal | ||||||
|       store[:delete_modal]      = object_account_user.setting_delete_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[:auto_play_gif]     = object_account_user.setting_auto_play_gif | ||||||
|       store[:display_media]     = object_account_user.setting_display_media |       store[:display_media]     = object_account_user.setting_display_media | ||||||
|       store[:expand_spoilers]   = object_account_user.setting_expand_spoilers |       store[:expand_spoilers]   = object_account_user.setting_expand_spoilers | ||||||
|  |  | ||||||
|  | @ -71,6 +71,7 @@ | ||||||
|     .fields-group |     .fields-group | ||||||
|       = ff.input :'web.reblog_modal', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_boost_modal') |       = 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.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' |     %h4= t 'appearance.sensitive_content' | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -233,6 +233,7 @@ en: | ||||||
|         setting_display_media_show_all: Show all |         setting_display_media_show_all: Show all | ||||||
|         setting_expand_spoilers: Always expand posts marked with content warnings |         setting_expand_spoilers: Always expand posts marked with content warnings | ||||||
|         setting_hide_network: Hide your social graph |         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_reduce_motion: Reduce motion in animations | ||||||
|         setting_system_font_ui: Use system's default font |         setting_system_font_ui: Use system's default font | ||||||
|         setting_system_scrollbars_ui: Use system's default scrollbar |         setting_system_scrollbars_ui: Use system's default scrollbar | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue