import { useCallback, useState, useEffect, useRef } from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; import { useParams, Link } from 'react-router-dom'; import { useDebouncedCallback } from 'use-debounce'; import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react'; import { importFetchedAccounts } from 'mastodon/actions/importer'; import { fetchList } from 'mastodon/actions/lists'; import { apiRequest } from 'mastodon/api'; import { apiGetAccounts, apiAddAccountToList, apiRemoveAccountFromList, } from 'mastodon/api/lists'; import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; import { Avatar } from 'mastodon/components/avatar'; import { Button } from 'mastodon/components/button'; import Column from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; import { ColumnSearchHeader } from 'mastodon/components/column_search_header'; import { FollowersCounter } from 'mastodon/components/counters'; import { DisplayName } from 'mastodon/components/display_name'; import ScrollableList from 'mastodon/components/scrollable_list'; import { ShortNumber } from 'mastodon/components/short_number'; import { VerifiedBadge } from 'mastodon/components/verified_badge'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; const messages = defineMessages({ heading: { id: 'column.list_members', defaultMessage: 'Manage list members' }, placeholder: { id: 'lists.search_placeholder', defaultMessage: 'Search people you follow', }, enterSearch: { id: 'lists.add_to_list', defaultMessage: 'Add to list' }, add: { id: 'lists.add_member', defaultMessage: 'Add' }, remove: { id: 'lists.remove_member', defaultMessage: 'Remove' }, back: { id: 'column_back_button.label', defaultMessage: 'Back' }, }); type Mode = 'remove' | 'add'; const AccountItem: React.FC<{ accountId: string; listId: string; partOfList: boolean; onToggle: (accountId: string) => void; }> = ({ accountId, listId, partOfList, onToggle }) => { const intl = useIntl(); const account = useAppSelector((state) => state.accounts.get(accountId)); const handleClick = useCallback(() => { if (partOfList) { void apiRemoveAccountFromList(listId, accountId); } else { void apiAddAccountToList(listId, accountId); } onToggle(accountId); }, [accountId, listId, partOfList, onToggle]); if (!account) { return null; } const firstVerifiedField = account.fields.find((item) => !!item.verified_at); return (
{' '} {firstVerifiedField && ( )}
); }; const ListMembers: React.FC<{ multiColumn?: boolean; }> = ({ multiColumn }) => { const dispatch = useAppDispatch(); const { id } = useParams<{ id: string }>(); const intl = useIntl(); const [searching, setSearching] = useState(false); const [accountIds, setAccountIds] = useState([]); const [searchAccountIds, setSearchAccountIds] = useState([]); const [loading, setLoading] = useState(true); const [mode, setMode] = useState('remove'); useEffect(() => { if (id) { setLoading(true); dispatch(fetchList(id)); void apiGetAccounts(id) .then((data) => { dispatch(importFetchedAccounts(data)); setAccountIds(data.map((a) => a.id)); setLoading(false); return ''; }) .catch(() => { setLoading(false); }); } }, [dispatch, id]); const handleSearchClick = useCallback(() => { setMode('add'); }, [setMode]); const handleDismissSearchClick = useCallback(() => { setMode('remove'); setSearching(false); }, [setMode]); const handleAccountToggle = useCallback( (accountId: string) => { const partOfList = accountIds.includes(accountId); if (partOfList) { setAccountIds(accountIds.filter((id) => id !== accountId)); } else { setAccountIds([accountId, ...accountIds]); } }, [accountIds, setAccountIds], ); const searchRequestRef = useRef(null); const handleSearch = useDebouncedCallback( (value: string) => { if (searchRequestRef.current) { searchRequestRef.current.abort(); } if (value.trim().length === 0) { setSearching(false); return; } setLoading(true); searchRequestRef.current = new AbortController(); void apiRequest('GET', 'v1/accounts/search', { signal: searchRequestRef.current.signal, params: { q: value, resolve: false, following: true, }, }) .then((data) => { dispatch(importFetchedAccounts(data)); setSearchAccountIds(data.map((a) => a.id)); setLoading(false); setSearching(true); return ''; }) .catch(() => { setSearching(true); setLoading(false); }); }, 500, { leading: true, trailing: true }, ); let displayedAccountIds: string[]; if (mode === 'add' && searching) { displayedAccountIds = searchAccountIds; } else { displayedAccountIds = accountIds; } return ( {displayedAccountIds.length > 0 &&
}
} emptyMessage={ mode === 'remove' ? ( <>
) : ( ) } > {displayedAccountIds.map((accountId) => ( ))} {intl.formatMessage(messages.heading)} ); }; // eslint-disable-next-line import/no-default-export export default ListMembers;