Implement new Collection inclusion rules in Collection accounts editor (#38719)
This commit is contained in:
@@ -4,11 +4,8 @@ import { FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { showAlertForError } from 'mastodon/actions/alerts';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { apiFollowAccount } from 'mastodon/api/accounts';
|
||||
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { AccountListItem } from 'mastodon/components/account_list_item';
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import { DisplayName } from 'mastodon/components/display_name';
|
||||
@@ -21,19 +18,18 @@ import {
|
||||
} from 'mastodon/components/scrollable_list/components';
|
||||
import { useAccount } from 'mastodon/hooks/useAccount';
|
||||
import { useSearchAccounts } from 'mastodon/hooks/useSearchAccounts';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import {
|
||||
addCollectionItem,
|
||||
getCollectionItemIds,
|
||||
removeCollectionItem,
|
||||
updateCollectionEditorField,
|
||||
} from 'mastodon/reducers/slices/collections';
|
||||
import { store, useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import classes from './styles.module.scss';
|
||||
import { WizardStepTitle } from './wizard_step_title';
|
||||
|
||||
const MAX_ACCOUNT_COUNT = 3;
|
||||
const MAX_ACCOUNT_COUNT = 25;
|
||||
|
||||
const AddedAccountItem: React.FC<{
|
||||
accountId: string;
|
||||
@@ -43,20 +39,24 @@ const AddedAccountItem: React.FC<{
|
||||
onRemove(accountId);
|
||||
}, [accountId, onRemove]);
|
||||
|
||||
return (
|
||||
<Account minimal key={accountId} id={accountId}>
|
||||
const renderButton = useCallback(
|
||||
() => (
|
||||
<Button compact secondary onClick={handleRemoveAccount}>
|
||||
<FormattedMessage
|
||||
id='collections.remove_account'
|
||||
defaultMessage='Remove'
|
||||
/>
|
||||
</Button>
|
||||
</Account>
|
||||
),
|
||||
[handleRemoveAccount],
|
||||
);
|
||||
|
||||
return <AccountListItem accountId={accountId} renderButton={renderButton} />;
|
||||
};
|
||||
|
||||
interface SuggestionItem {
|
||||
id: string;
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
const SuggestedAccountItem: React.FC<SuggestionItem> = ({ id }) => {
|
||||
@@ -77,6 +77,7 @@ const renderAccountItem = (item: SuggestionItem) => (
|
||||
);
|
||||
|
||||
const getItemId = (item: SuggestionItem) => item.id;
|
||||
const getIsItemDisabled = (item: SuggestionItem) => item.isDisabled ?? false;
|
||||
|
||||
export const CollectionAccounts: React.FC<{
|
||||
collection?: ApiCollectionJSON | null;
|
||||
@@ -106,21 +107,22 @@ export const CollectionAccounts: React.FC<{
|
||||
const hasMaxAccounts = accountIds.length === MAX_ACCOUNT_COUNT;
|
||||
|
||||
const {
|
||||
accountIds: suggestedAccountIds,
|
||||
accounts: suggestedAccounts,
|
||||
isLoading: isLoadingSuggestions,
|
||||
searchAccounts,
|
||||
resetAccounts,
|
||||
} = useSearchAccounts({
|
||||
withRelationships: true,
|
||||
filterResults: (account) =>
|
||||
!accountIds.includes(account.id) &&
|
||||
// Only suggest accounts who allow being featured/recommended
|
||||
account.feature_approval.current_user === 'automatic',
|
||||
// Don't suggest accounts that were already added
|
||||
filterResults: (account) => !accountIds.includes(account.id),
|
||||
});
|
||||
|
||||
const suggestedItems = suggestedAccountIds.map((id) => ({
|
||||
const suggestedItems = suggestedAccounts.map(({ id, feature_approval }) => ({
|
||||
id,
|
||||
isDisabled: accountIds.includes(id),
|
||||
// Disable accounts who can't be added to a collection
|
||||
isDisabled: !['automatic', 'manual'].includes(
|
||||
feature_approval.current_user,
|
||||
),
|
||||
}));
|
||||
|
||||
const handleSearchValueChange = useCallback(
|
||||
@@ -140,43 +142,6 @@ export const CollectionAccounts: React.FC<{
|
||||
[],
|
||||
);
|
||||
|
||||
const relationships = useAppSelector((state) => state.relationships);
|
||||
|
||||
const confirmFollowStatus = useCallback(
|
||||
(accountId: string, onFollowing: () => void) => {
|
||||
const relationship = relationships.get(accountId);
|
||||
|
||||
if (!relationship) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
accountId === me ||
|
||||
relationship.following ||
|
||||
relationship.requested
|
||||
) {
|
||||
onFollowing();
|
||||
} else {
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'CONFIRM_FOLLOW_TO_COLLECTION',
|
||||
modalProps: {
|
||||
accountId,
|
||||
onConfirm: () => {
|
||||
apiFollowAccount(accountId)
|
||||
.then(onFollowing)
|
||||
.catch((err: unknown) => {
|
||||
store.dispatch(showAlertForError(err));
|
||||
});
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
[dispatch, relationships],
|
||||
);
|
||||
|
||||
const removeAccountItem = useCallback(
|
||||
(accountId: string) => {
|
||||
dispatch(
|
||||
@@ -191,16 +156,14 @@ export const CollectionAccounts: React.FC<{
|
||||
|
||||
const addAccountItem = useCallback(
|
||||
(item: SuggestionItem) => {
|
||||
confirmFollowStatus(item.id, () => {
|
||||
dispatch(
|
||||
updateCollectionEditorField({
|
||||
field: 'accountIds',
|
||||
value: [...accountIds, item.id],
|
||||
}),
|
||||
);
|
||||
});
|
||||
dispatch(
|
||||
updateCollectionEditorField({
|
||||
field: 'accountIds',
|
||||
value: [...accountIds, item.id],
|
||||
}),
|
||||
);
|
||||
},
|
||||
[accountIds, confirmFollowStatus, dispatch],
|
||||
[accountIds, dispatch],
|
||||
);
|
||||
|
||||
const instantRemoveAccountItem = useCallback(
|
||||
@@ -227,15 +190,13 @@ export const CollectionAccounts: React.FC<{
|
||||
|
||||
const instantAddAccountItem = useCallback(
|
||||
(item: SuggestionItem) => {
|
||||
confirmFollowStatus(item.id, () => {
|
||||
if (id) {
|
||||
void dispatch(
|
||||
addCollectionItem({ collectionId: id, accountId: item.id }),
|
||||
);
|
||||
}
|
||||
});
|
||||
if (id) {
|
||||
void dispatch(
|
||||
addCollectionItem({ collectionId: id, accountId: item.id }),
|
||||
);
|
||||
}
|
||||
},
|
||||
[confirmFollowStatus, dispatch, id],
|
||||
[dispatch, id],
|
||||
);
|
||||
|
||||
const handleRemoveAccountItem = useCallback(
|
||||
@@ -307,6 +268,7 @@ export const CollectionAccounts: React.FC<{
|
||||
isLoading={isLoadingSuggestions}
|
||||
items={suggestedItems}
|
||||
getItemId={getItemId}
|
||||
getIsItemDisabled={getIsItemDisabled}
|
||||
renderItem={renderAccountItem}
|
||||
onSelectItem={handleSelectItem}
|
||||
status={
|
||||
|
||||
@@ -164,7 +164,7 @@ const ListMembers: React.FC<{
|
||||
const [mode, setMode] = useState<Mode>('remove');
|
||||
|
||||
const {
|
||||
accountIds: searchAccountIds,
|
||||
accounts: accountsFromSearch,
|
||||
isLoading: loadingSearchResults,
|
||||
searchAccounts: handleSearch,
|
||||
} = useSearchAccounts({
|
||||
@@ -177,6 +177,7 @@ const ListMembers: React.FC<{
|
||||
}
|
||||
},
|
||||
});
|
||||
const accountIdsFromSearch = accountsFromSearch.map((item) => item.id);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
@@ -220,7 +221,7 @@ const ListMembers: React.FC<{
|
||||
let displayedAccountIds: string[];
|
||||
|
||||
if (mode === 'add' && searching) {
|
||||
displayedAccountIds = searchAccountIds;
|
||||
displayedAccountIds = accountIdsFromSearch;
|
||||
} else {
|
||||
displayedAccountIds = accountIds;
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { useAccount } from 'mastodon/hooks/useAccount';
|
||||
|
||||
import type { BaseConfirmationModalProps } from './confirmation_modal';
|
||||
import { ConfirmationModal } from './confirmation_modal';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'confirmations.follow_to_collection.title',
|
||||
defaultMessage: 'Follow account?',
|
||||
},
|
||||
confirm: {
|
||||
id: 'confirmations.follow_to_collection.confirm',
|
||||
defaultMessage: 'Follow and add to collection',
|
||||
},
|
||||
});
|
||||
|
||||
export const ConfirmFollowToCollectionModal: React.FC<
|
||||
{
|
||||
accountId: string;
|
||||
onConfirm: () => void;
|
||||
} & BaseConfirmationModalProps
|
||||
> = ({ accountId, onConfirm, onClose }) => {
|
||||
const intl = useIntl();
|
||||
const account = useAccount(accountId);
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
title={intl.formatMessage(messages.title)}
|
||||
message={
|
||||
<FormattedMessage
|
||||
id='confirmations.follow_to_collection.message'
|
||||
defaultMessage='You need to be following {name} to add them to a collection.'
|
||||
values={{ name: <strong>@{account?.acct}</strong> }}
|
||||
/>
|
||||
}
|
||||
confirm={intl.formatMessage(messages.confirm)}
|
||||
onConfirm={onConfirm}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -13,7 +13,6 @@ export { ConfirmUnblockModal } from './unblock';
|
||||
export { ConfirmClearNotificationsModal } from './clear_notifications';
|
||||
export { ConfirmLogOutModal } from './log_out';
|
||||
export { ConfirmFollowToListModal } from './follow_to_list';
|
||||
export { ConfirmFollowToCollectionModal } from './follow_to_collection';
|
||||
export { ConfirmMissingAltTextModal } from './missing_alt_text';
|
||||
export { ConfirmRevokeQuoteModal } from './revoke_quote';
|
||||
export { QuietPostQuoteInfoModal } from './quiet_post_quote_info';
|
||||
|
||||
@@ -39,7 +39,6 @@ import {
|
||||
ConfirmClearNotificationsModal,
|
||||
ConfirmLogOutModal,
|
||||
ConfirmFollowToListModal,
|
||||
ConfirmFollowToCollectionModal,
|
||||
ConfirmMissingAltTextModal,
|
||||
ConfirmRevokeQuoteModal,
|
||||
QuietPostQuoteInfoModal,
|
||||
@@ -69,7 +68,6 @@ 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_FOLLOW_TO_COLLECTION': () => Promise.resolve({ default: ConfirmFollowToCollectionModal }),
|
||||
'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }),
|
||||
'CONFIRM_PRIVATE_QUOTE_NOTIFY': () => Promise.resolve({ default: PrivateQuoteNotify }),
|
||||
'CONFIRM_REVOKE_QUOTE': () => Promise.resolve({ default: ConfirmRevokeQuoteModal }),
|
||||
|
||||
@@ -21,7 +21,7 @@ export function useSearchAccounts({
|
||||
} = {}) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [accountIds, setAccountIds] = useState<string[]>([]);
|
||||
const [accounts, setAccounts] = useState<ApiAccountJSON[]>([]);
|
||||
const [loadingState, setLoadingState] = useState<
|
||||
'idle' | 'loading' | 'error'
|
||||
>('idle');
|
||||
@@ -37,7 +37,7 @@ export function useSearchAccounts({
|
||||
if (value.trim().length === 0) {
|
||||
onSettled?.('');
|
||||
if (resetOnInputClear) {
|
||||
setAccountIds([]);
|
||||
setAccounts([]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export function useSearchAccounts({
|
||||
if (withRelationships) {
|
||||
dispatch(fetchRelationships(accountIds));
|
||||
}
|
||||
setAccountIds(accountIds);
|
||||
setAccounts(accounts);
|
||||
setLoadingState('idle');
|
||||
onSettled?.(value);
|
||||
})
|
||||
@@ -74,13 +74,13 @@ export function useSearchAccounts({
|
||||
);
|
||||
|
||||
const resetAccounts = useCallback(() => {
|
||||
setAccountIds([]);
|
||||
setAccounts([]);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
searchAccounts,
|
||||
resetAccounts,
|
||||
accountIds,
|
||||
accounts,
|
||||
isLoading: loadingState === 'loading',
|
||||
isError: loadingState === 'error',
|
||||
};
|
||||
|
||||
@@ -505,9 +505,6 @@
|
||||
"confirmations.discard_draft.post.title": "Discard your draft post?",
|
||||
"confirmations.discard_edit_media.confirm": "Discard",
|
||||
"confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?",
|
||||
"confirmations.follow_to_collection.confirm": "Follow and add to collection",
|
||||
"confirmations.follow_to_collection.message": "You need to be following {name} to add them to a collection.",
|
||||
"confirmations.follow_to_collection.title": "Follow account?",
|
||||
"confirmations.follow_to_list.confirm": "Follow and add to list",
|
||||
"confirmations.follow_to_list.message": "You need to be following {name} to add them to a list.",
|
||||
"confirmations.follow_to_list.title": "Follow user?",
|
||||
|
||||
Reference in New Issue
Block a user