From ed452805db19e389f1ba59b7e0762fac66b1351b Mon Sep 17 00:00:00 2001 From: Echo Date: Thu, 13 Aug 2026 08:57:56 +0000 Subject: [PATCH] Redesigned composer feedback (#40125) --- .../mastodon/actions/compose_typed.ts | 92 ++------ .../components/autosuggest_textarea.jsx | 10 +- .../components/button/redesign.module.scss | 24 +- .../components/dropdown/redesign.module.scss | 23 +- .../mastodon/components/dropdown/redesign.tsx | 3 + .../form_fields/redesign.module.scss | 6 +- .../form_fields/text_input_field.tsx | 11 +- .../compose/redesign/cancel_modal.tsx | 65 ++++++ .../features/compose/redesign/emoji.tsx | 2 +- .../features/compose/redesign/footer.tsx | 16 +- .../features/compose/redesign/index.tsx | 213 ++++-------------- .../compose/redesign/modals.module.scss | 25 ++ .../features/compose/redesign/poll.tsx | 139 ++++++++++-- .../features/compose/redesign/selectors.ts | 45 ++-- .../compose/redesign/styles.module.scss | 54 +++-- .../features/compose/redesign/textarea.tsx | 199 ++++++++++++++++ .../compose/redesign/trigger.module.scss | 1 + .../features/compose/redesign/upload.tsx | 27 ++- .../features/compose/redesign/visibility.tsx | 48 ++-- .../features/ui/components/modal_root.jsx | 1 + app/javascript/mastodon/locales/en.json | 8 +- app/javascript/mastodon/reducers/compose.js | 39 +++- .../mastodon/reducers/slices/composer.ts | 109 ++++++++- 23 files changed, 801 insertions(+), 359 deletions(-) create mode 100644 app/javascript/mastodon/features/compose/redesign/cancel_modal.tsx create mode 100644 app/javascript/mastodon/features/compose/redesign/modals.module.scss create mode 100644 app/javascript/mastodon/features/compose/redesign/textarea.tsx diff --git a/app/javascript/mastodon/actions/compose_typed.ts b/app/javascript/mastodon/actions/compose_typed.ts index 2cc4526b828..9ff15f4b6c2 100644 --- a/app/javascript/mastodon/actions/compose_typed.ts +++ b/app/javascript/mastodon/actions/compose_typed.ts @@ -15,15 +15,8 @@ import { createAppThunk, } from '@/mastodon/store/typed_functions'; -import type { ApiStatusJSON } from '../api_types/statuses'; - import { showAlert } from './alerts'; -import { - changeCompose, - focusCompose, - submitCompose as submitComposeApi, - uploadCompose, -} from './compose'; +import { changeCompose, focusCompose, uploadCompose } from './compose'; import { importFetchedStatuses } from './importer'; import { openModal } from './modal'; @@ -285,70 +278,35 @@ export const setDragUploadEnabled = createAction( 'compose/setDragUploadEnabled', ); -export const submitCompose = createAppThunk( +export const addPollOption = createAppThunk( + 'compose/addPollOption', + (_arg, { getState }) => ({ + maxOptions: + getState().server.server.item?.configuration.polls.max_options ?? 4, + }), +); + +export const updatePollOption = createAppThunk( + 'compose/updatePollOption', ( - { - textareaValue = '', - redirectOnSuccess, - }: { textareaValue?: string; redirectOnSuccess?: boolean }, - { getState, dispatch }, + arg: { + index: number; + text: string; + }, + { getState }, ) => { - if ( - textareaValue && - (getState().compose.get('text') as string) !== textareaValue - ) { - dispatch(changeCompose(textareaValue)); - } - - const { compose, meta, statuses, settings } = getState(); - const privacy = compose.get('privacy') as StatusVisibility; - const missingAltText = ( - compose.get('media_attachments') as unknown as Immutable.List< - Immutable.Map - > - ).some( - (media) => - ['image', 'gifv'].includes(media.get('type') ?? '') && - (media.get('description') ?? '').length === 0, - ); - const me = meta.get('me') as string | null; - const quotedStatusId = compose.get('quoted_status_id') as string | null; - const quoteToPrivate = - !!quotedStatusId && - privacy === 'private' && - statuses.getIn([quotedStatusId, 'account']) !== me && - !settings.getIn(['dismissed_banners', PRIVATE_QUOTE_MODAL_ID]); - - if ( - !!meta.get('missing_alt_text_modal') && - missingAltText && - privacy !== 'direct' - ) { - dispatch( - openModal({ - modalType: 'CONFIRM_MISSING_ALT_TEXT', - modalProps: {}, - }), - ); - } else if (quoteToPrivate) { - dispatch( - openModal({ - modalType: 'CONFIRM_PRIVATE_QUOTE_NOTIFY', - modalProps: {}, - }), - ); - } else { - dispatch( - submitComposeApi((status: ApiStatusJSON) => { - if (redirectOnSuccess) { - window.location.assign(status.url); - } - }), - ); - } + return { + ...arg, + maxOptions: + getState().server.server.item?.configuration.polls.max_options ?? 4, + }; }, ); +export const deletePollOption = createAction<{ index: number }>( + 'compose/deletePollOption', +); + const urlLikeRegex = /^https?:\/\/[^\s]+\/[^\s]+$/i; export const processPasteOrDrop = createAppThunk( diff --git a/app/javascript/mastodon/components/autosuggest_textarea.jsx b/app/javascript/mastodon/components/autosuggest_textarea.jsx index 3e48d38b6c2..6eabdbd9f56 100644 --- a/app/javascript/mastodon/components/autosuggest_textarea.jsx +++ b/app/javascript/mastodon/components/autosuggest_textarea.jsx @@ -29,9 +29,11 @@ const AutosuggestTextarea = forwardRef(({ onPaste, onDrop, onFocus, + onBlur, autoFocus = true, lang, className, + ...props }, textareaRef) => { const [suggestionsHidden, setSuggestionsHidden] = useState(true); @@ -111,14 +113,13 @@ const AutosuggestTextarea = forwardRef(({ onKeyDown(e); }, [disabled, suggestions, suggestionsHidden, selectedSuggestion, setSelectedSuggestion, setSuggestionsHidden, onSuggestionSelected, onKeyDown]); - const closeMenu = useCallback(() => { + const closeMenu = useCallback((e) => { setSuggestionsHidden(true); + onBlur?.(e); }, [setSuggestionsHidden]); const handleFocus = useCallback((e) => { - if (onFocus) { - onFocus(e); - } + onFocus?.(e); }, [onFocus]); const handleSuggestionClick = useCallback((e) => { @@ -182,6 +183,7 @@ const AutosuggestTextarea = forwardRef(({ return (