From 7135f513a45460742b87c7db88e3e280e3a7a060 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 3 Dec 2024 10:42:52 +0100 Subject: [PATCH] Add ability to search for all accounts when creating a list in web UI (#33036) --- app/javascript/mastodon/actions/alerts.js | 66 ----- app/javascript/mastodon/actions/alerts.ts | 90 +++++++ app/javascript/mastodon/api/accounts.ts | 13 + .../mastodon/components/account.jsx | 175 ------------- .../mastodon/components/account.tsx | 235 ++++++++++++++++++ .../mastodon/components/empty_account.tsx | 33 --- .../mastodon/components/server_banner.jsx | 2 +- .../mastodon/containers/account_container.jsx | 60 ----- .../mastodon/features/about/index.jsx | 2 +- .../mastodon/features/blocks/index.jsx | 4 +- .../compose/components/navigation_bar.jsx | 5 +- .../compose/components/search_results.jsx | 4 +- .../mastodon/features/explore/results.jsx | 2 +- .../mastodon/features/favourites/index.jsx | 4 +- .../mastodon/features/followers/index.jsx | 4 +- .../mastodon/features/following/index.jsx | 4 +- .../mastodon/features/lists/members.tsx | 54 +++- .../mastodon/features/mutes/index.jsx | 4 +- .../notifications/components/notification.jsx | 8 +- .../mastodon/features/onboarding/follows.tsx | 9 +- .../mastodon/features/reblogs/index.jsx | 4 +- .../confirmation_modals/follow_to_list.tsx | 43 ++++ .../components/confirmation_modals/index.ts | 1 + .../features/ui/components/modal_root.jsx | 2 + app/javascript/mastodon/locales/en.json | 5 +- 25 files changed, 459 insertions(+), 374 deletions(-) delete mode 100644 app/javascript/mastodon/actions/alerts.js create mode 100644 app/javascript/mastodon/actions/alerts.ts delete mode 100644 app/javascript/mastodon/components/account.jsx create mode 100644 app/javascript/mastodon/components/account.tsx delete mode 100644 app/javascript/mastodon/components/empty_account.tsx delete mode 100644 app/javascript/mastodon/containers/account_container.jsx create mode 100644 app/javascript/mastodon/features/ui/components/confirmation_modals/follow_to_list.tsx diff --git a/app/javascript/mastodon/actions/alerts.js b/app/javascript/mastodon/actions/alerts.js deleted file mode 100644 index 48dee2587f..0000000000 --- a/app/javascript/mastodon/actions/alerts.js +++ /dev/null @@ -1,66 +0,0 @@ -import { defineMessages } from 'react-intl'; - -import { AxiosError } from 'axios'; - -const messages = defineMessages({ - unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' }, - unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' }, - rateLimitedTitle: { id: 'alert.rate_limited.title', defaultMessage: 'Rate limited' }, - rateLimitedMessage: { id: 'alert.rate_limited.message', defaultMessage: 'Please retry after {retry_time, time, medium}.' }, -}); - -export const ALERT_SHOW = 'ALERT_SHOW'; -export const ALERT_DISMISS = 'ALERT_DISMISS'; -export const ALERT_CLEAR = 'ALERT_CLEAR'; -export const ALERT_NOOP = 'ALERT_NOOP'; - -export const dismissAlert = alert => ({ - type: ALERT_DISMISS, - alert, -}); - -export const clearAlert = () => ({ - type: ALERT_CLEAR, -}); - -export const showAlert = alert => ({ - type: ALERT_SHOW, - alert, -}); - -export const showAlertForError = (error, skipNotFound = false) => { - if (error.response) { - const { data, status, statusText, headers } = error.response; - - // Skip these errors as they are reflected in the UI - if (skipNotFound && (status === 404 || status === 410)) { - return { type: ALERT_NOOP }; - } - - // Rate limit errors - if (status === 429 && headers['x-ratelimit-reset']) { - return showAlert({ - title: messages.rateLimitedTitle, - message: messages.rateLimitedMessage, - values: { 'retry_time': new Date(headers['x-ratelimit-reset']) }, - }); - } - - return showAlert({ - title: `${status}`, - message: data.error || statusText, - }); - } - - // An aborted request, e.g. due to reloading the browser window, it not really error - if (error.code === AxiosError.ECONNABORTED) { - return { type: ALERT_NOOP }; - } - - console.error(error); - - return showAlert({ - title: messages.unexpectedTitle, - message: messages.unexpectedMessage, - }); -}; diff --git a/app/javascript/mastodon/actions/alerts.ts b/app/javascript/mastodon/actions/alerts.ts new file mode 100644 index 0000000000..a521f3ef35 --- /dev/null +++ b/app/javascript/mastodon/actions/alerts.ts @@ -0,0 +1,90 @@ +import { defineMessages } from 'react-intl'; +import type { MessageDescriptor } from 'react-intl'; + +import { AxiosError } from 'axios'; +import type { AxiosResponse } from 'axios'; + +interface Alert { + title: string | MessageDescriptor; + message: string | MessageDescriptor; + values?: Record; +} + +interface ApiErrorResponse { + error?: string; +} + +const messages = defineMessages({ + unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' }, + unexpectedMessage: { + id: 'alert.unexpected.message', + defaultMessage: 'An unexpected error occurred.', + }, + rateLimitedTitle: { + id: 'alert.rate_limited.title', + defaultMessage: 'Rate limited', + }, + rateLimitedMessage: { + id: 'alert.rate_limited.message', + defaultMessage: 'Please retry after {retry_time, time, medium}.', + }, +}); + +export const ALERT_SHOW = 'ALERT_SHOW'; +export const ALERT_DISMISS = 'ALERT_DISMISS'; +export const ALERT_CLEAR = 'ALERT_CLEAR'; +export const ALERT_NOOP = 'ALERT_NOOP'; + +export const dismissAlert = (alert: Alert) => ({ + type: ALERT_DISMISS, + alert, +}); + +export const clearAlert = () => ({ + type: ALERT_CLEAR, +}); + +export const showAlert = (alert: Alert) => ({ + type: ALERT_SHOW, + alert, +}); + +export const showAlertForError = (error: unknown, skipNotFound = false) => { + if (error instanceof AxiosError && error.response) { + const { status, statusText, headers } = error.response; + const { data } = error.response as AxiosResponse; + + // Skip these errors as they are reflected in the UI + if (skipNotFound && (status === 404 || status === 410)) { + return { type: ALERT_NOOP }; + } + + // Rate limit errors + if (status === 429 && headers['x-ratelimit-reset']) { + return showAlert({ + title: messages.rateLimitedTitle, + message: messages.rateLimitedMessage, + values: { + retry_time: new Date(headers['x-ratelimit-reset'] as string), + }, + }); + } + + return showAlert({ + title: `${status}`, + message: data.error ?? statusText, + }); + } + + // An aborted request, e.g. due to reloading the browser window, it not really error + if (error instanceof AxiosError && error.code === AxiosError.ECONNABORTED) { + return { type: ALERT_NOOP }; + } + + console.error(error); + + return showAlert({ + title: messages.unexpectedTitle, + message: messages.unexpectedMessage, + }); +}; diff --git a/app/javascript/mastodon/api/accounts.ts b/app/javascript/mastodon/api/accounts.ts index bd1757e827..717010ba74 100644 --- a/app/javascript/mastodon/api/accounts.ts +++ b/app/javascript/mastodon/api/accounts.ts @@ -5,3 +5,16 @@ export const apiSubmitAccountNote = (id: string, value: string) => apiRequestPost(`v1/accounts/${id}/note`, { comment: value, }); + +export const apiFollowAccount = ( + id: string, + params?: { + reblogs: boolean; + }, +) => + apiRequestPost(`v1/accounts/${id}/follow`, { + ...params, + }); + +export const apiUnfollowAccount = (id: string) => + apiRequestPost(`v1/accounts/${id}/unfollow`); diff --git a/app/javascript/mastodon/components/account.jsx b/app/javascript/mastodon/components/account.jsx deleted file mode 100644 index fa66fd56bb..0000000000 --- a/app/javascript/mastodon/components/account.jsx +++ /dev/null @@ -1,175 +0,0 @@ -import PropTypes from 'prop-types'; -import { useCallback } from 'react'; - -import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; - -import classNames from 'classnames'; -import { Link } from 'react-router-dom'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; - -import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; -import { EmptyAccount } from 'mastodon/components/empty_account'; -import { FollowButton } from 'mastodon/components/follow_button'; -import { ShortNumber } from 'mastodon/components/short_number'; -import { VerifiedBadge } from 'mastodon/components/verified_badge'; - -import DropdownMenuContainer from '../containers/dropdown_menu_container'; -import { me } from '../initial_state'; - -import { Avatar } from './avatar'; -import { Button } from './button'; -import { FollowersCounter } from './counters'; -import { DisplayName } from './display_name'; -import { RelativeTimestamp } from './relative_timestamp'; - -const messages = defineMessages({ - unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' }, - unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' }, - mute_notifications: { id: 'account.mute_notifications_short', defaultMessage: 'Mute notifications' }, - unmute_notifications: { id: 'account.unmute_notifications_short', defaultMessage: 'Unmute notifications' }, - mute: { id: 'account.mute_short', defaultMessage: 'Mute' }, - block: { id: 'account.block_short', defaultMessage: 'Block' }, - more: { id: 'status.more', defaultMessage: 'More' }, -}); - -const Account = ({ size = 46, account, onBlock, onMute, onMuteNotifications, hidden, minimal, defaultAction, withBio }) => { - const intl = useIntl(); - - const handleBlock = useCallback(() => { - onBlock(account); - }, [onBlock, account]); - - const handleMute = useCallback(() => { - onMute(account); - }, [onMute, account]); - - const handleMuteNotifications = useCallback(() => { - onMuteNotifications(account, true); - }, [onMuteNotifications, account]); - - const handleUnmuteNotifications = useCallback(() => { - onMuteNotifications(account, false); - }, [onMuteNotifications, account]); - - if (!account) { - return ; - } - - if (hidden) { - return ( - <> - {account.get('display_name')} - {account.get('username')} - - ); - } - - let buttons; - - if (account.get('id') !== me && account.get('relationship', null) !== null) { - const requested = account.getIn(['relationship', 'requested']); - const blocking = account.getIn(['relationship', 'blocking']); - const muting = account.getIn(['relationship', 'muting']); - - if (requested) { - buttons = ; - } else if (blocking) { - buttons =