@ -1,109 +0,0 @@
|
||||
import { Entities } from 'pl-fe/entity-store/entities';
|
||||
import { queryClient } from 'pl-fe/queries/client';
|
||||
import { isLoggedIn } from 'pl-fe/utils/auth';
|
||||
|
||||
import { getClient } from '../api';
|
||||
|
||||
import type { PaginatedResponse } from 'pl-api';
|
||||
import type { EntityStore } from 'pl-fe/entity-store/types';
|
||||
import type { Account } from 'pl-fe/normalizers/account';
|
||||
import type { MinifiedSuggestion } from 'pl-fe/queries/trends/use-suggested-accounts';
|
||||
import type { AppDispatch, RootState } from 'pl-fe/store';
|
||||
|
||||
const DOMAIN_UNBLOCK_SUCCESS = 'DOMAIN_UNBLOCK_SUCCESS' as const;
|
||||
|
||||
const DOMAIN_BLOCKS_FETCH_SUCCESS = 'DOMAIN_BLOCKS_FETCH_SUCCESS' as const;
|
||||
|
||||
const DOMAIN_BLOCKS_EXPAND_SUCCESS = 'DOMAIN_BLOCKS_EXPAND_SUCCESS' as const;
|
||||
|
||||
const blockDomain = (domain: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
return getClient(getState).filtering.blockDomain(domain).then(() => {
|
||||
// TODO: Update relationships on block
|
||||
const accounts = selectAccountsByDomain(getState(), domain);
|
||||
if (!accounts) return;
|
||||
|
||||
queryClient.setQueryData<Array<MinifiedSuggestion>>(['suggestions'], suggestions => suggestions
|
||||
? suggestions.filter((suggestion) => !accounts.includes(suggestion.account_id))
|
||||
: undefined);
|
||||
});
|
||||
};
|
||||
|
||||
const unblockDomain = (domain: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
return getClient(getState).filtering.unblockDomain(domain).then(() => {
|
||||
// TODO: Update relationships on unblock
|
||||
const accounts = selectAccountsByDomain(getState(), domain);
|
||||
if (!accounts) return;
|
||||
dispatch(unblockDomainSuccess(domain, accounts));
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
const unblockDomainSuccess = (domain: string, accounts: string[]) => ({
|
||||
type: DOMAIN_UNBLOCK_SUCCESS,
|
||||
domain,
|
||||
accounts,
|
||||
});
|
||||
|
||||
const fetchDomainBlocks = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
return getClient(getState).filtering.getDomainBlocks().then(response => {
|
||||
dispatch(fetchDomainBlocksSuccess(response.items, response.next));
|
||||
});
|
||||
};
|
||||
|
||||
const fetchDomainBlocksSuccess = (domains: string[], next: (() => Promise<PaginatedResponse<string>>) | null) => ({
|
||||
type: DOMAIN_BLOCKS_FETCH_SUCCESS,
|
||||
domains,
|
||||
next,
|
||||
});
|
||||
|
||||
const expandDomainBlocks = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const next = getState().domain_lists.blocks.next;
|
||||
|
||||
if (!next) return;
|
||||
|
||||
next().then(response => {
|
||||
dispatch(expandDomainBlocksSuccess(response.items, response.next));
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
const selectAccountsByDomain = (state: RootState, domain: string): string[] => {
|
||||
const store = state.entities[Entities.ACCOUNTS]?.store as EntityStore<Account> | undefined;
|
||||
const entries = store ? Object.entries(store) : undefined;
|
||||
const accounts = entries
|
||||
?.filter(([_, item]) => item && item.acct.endsWith(`@${domain}`))
|
||||
.map(([_, item]) => item!.id);
|
||||
return accounts || [];
|
||||
};
|
||||
|
||||
const expandDomainBlocksSuccess = (domains: string[], next: (() => Promise<PaginatedResponse<string>>) | null) => ({
|
||||
type: DOMAIN_BLOCKS_EXPAND_SUCCESS,
|
||||
domains,
|
||||
next,
|
||||
});
|
||||
|
||||
type DomainBlocksAction =
|
||||
| ReturnType<typeof unblockDomainSuccess>
|
||||
| ReturnType<typeof fetchDomainBlocksSuccess>
|
||||
| ReturnType<typeof expandDomainBlocksSuccess>;
|
||||
|
||||
export {
|
||||
DOMAIN_UNBLOCK_SUCCESS,
|
||||
DOMAIN_BLOCKS_FETCH_SUCCESS,
|
||||
DOMAIN_BLOCKS_EXPAND_SUCCESS,
|
||||
blockDomain,
|
||||
unblockDomain,
|
||||
fetchDomainBlocks,
|
||||
expandDomainBlocks,
|
||||
type DomainBlocksAction,
|
||||
};
|
||||
@ -1,11 +1,11 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { unblockDomain } from 'pl-fe/actions/domain-blocks';
|
||||
import HStack from 'pl-fe/components/ui/hstack';
|
||||
import IconButton from 'pl-fe/components/ui/icon-button';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { unblockDomainMutationOptions } from 'pl-fe/queries/settings/domain-blocks';
|
||||
|
||||
const messages = defineMessages({
|
||||
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
|
||||
@ -17,20 +17,21 @@ interface IDomain {
|
||||
}
|
||||
|
||||
const Domain: React.FC<IDomain> = ({ domain }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const { mutate: unblockDomain } = useMutation(unblockDomainMutationOptions);
|
||||
|
||||
// const onBlockDomain = () => {
|
||||
// openModal('CONFIRM', {
|
||||
// heading: <FormattedMessage id='confirmations.domain_block.heading' defaultMessage='Block {domain}' values={{ domain }} />,
|
||||
// message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.' values={{ domain: <strong>{domain}</strong> }} />,
|
||||
// confirm: intl.formatMessage(messages.blockDomainConfirm),
|
||||
// onConfirm: () => dispatch(blockDomain(domain)),
|
||||
// onConfirm: () => blockDomain(domain),
|
||||
// });
|
||||
// }
|
||||
|
||||
const handleDomainUnblock = () => {
|
||||
dispatch(unblockDomain(domain));
|
||||
unblockDomain(domain);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -7,7 +7,6 @@ import * as v from 'valibot';
|
||||
|
||||
import { biteAccount, blockAccount, pinAccount, removeFromFollowers, unblockAccount, unmuteAccount, unpinAccount } from 'pl-fe/actions/accounts';
|
||||
import { mentionCompose, directCompose } from 'pl-fe/actions/compose';
|
||||
import { blockDomain, unblockDomain } from 'pl-fe/actions/domain-blocks';
|
||||
import { initReport, ReportableEntities } from 'pl-fe/actions/reports';
|
||||
import { useFollow } from 'pl-fe/api/hooks/accounts/use-follow';
|
||||
import Badge from 'pl-fe/components/badge';
|
||||
@ -26,6 +25,7 @@ import { useFeatures } from 'pl-fe/hooks/use-features';
|
||||
import { useOwnAccount } from 'pl-fe/hooks/use-own-account';
|
||||
import { useChats } from 'pl-fe/queries/chats';
|
||||
import { queryClient } from 'pl-fe/queries/client';
|
||||
import { blockDomainMutationOptions, unblockDomainMutationOptions } from 'pl-fe/queries/settings/domain-blocks';
|
||||
import { useModalsStore } from 'pl-fe/stores/modals';
|
||||
import { useSettingsStore } from 'pl-fe/stores/settings';
|
||||
import toast from 'pl-fe/toast';
|
||||
@ -100,6 +100,9 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
||||
|
||||
const { getOrCreateChatByAccountId } = useChats();
|
||||
|
||||
const { mutate: blockDomain } = useMutation(blockDomainMutationOptions);
|
||||
const { mutate: unblockDomain } = useMutation(unblockDomainMutationOptions);
|
||||
|
||||
const createAndNavigateToChat = useMutation({
|
||||
mutationFn: (accountId: string) => getOrCreateChatByAccountId(accountId),
|
||||
onError: (error: { response: PlfeResponse }) => {
|
||||
@ -203,12 +206,12 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
||||
heading: <FormattedMessage id='confirmations.domain_block.heading' defaultMessage='Block {domain}' values={{ domain }} />,
|
||||
message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications.' values={{ domain: <strong>{domain}</strong> }} />,
|
||||
confirm: intl.formatMessage(messages.blockDomainConfirm),
|
||||
onConfirm: () => dispatch(blockDomain(domain)),
|
||||
onConfirm: () => blockDomain(domain),
|
||||
});
|
||||
};
|
||||
|
||||
const onUnblockDomain = (domain: string) => {
|
||||
dispatch(unblockDomain(domain));
|
||||
unblockDomain(domain);
|
||||
};
|
||||
|
||||
const onProfileExternal = (url: string) => {
|
||||
|
||||
@ -1,34 +1,28 @@
|
||||
import debounce from 'lodash/debounce';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { fetchDomainBlocks, expandDomainBlocks } from 'pl-fe/actions/domain-blocks';
|
||||
import Domain from 'pl-fe/components/domain';
|
||||
import ScrollableList from 'pl-fe/components/scrollable-list';
|
||||
import Column from 'pl-fe/components/ui/column';
|
||||
import Spinner from 'pl-fe/components/ui/spinner';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||
import { domainBlocksQueryOptions } from 'pl-fe/queries/settings/domain-blocks';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' },
|
||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
||||
});
|
||||
|
||||
const handleLoadMore = debounce((dispatch) => {
|
||||
dispatch(expandDomainBlocks());
|
||||
}, 300, { leading: true });
|
||||
|
||||
const DomainBlocks: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const domains = useAppSelector((state) => state.domain_lists.blocks.items);
|
||||
const hasMore = useAppSelector((state) => !!state.domain_lists.blocks.next);
|
||||
const { data: domains, hasNextPage, fetchNextPage } = useInfiniteQuery(domainBlocksQueryOptions);
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch(fetchDomainBlocks());
|
||||
}, []);
|
||||
const handleLoadMore = () => {
|
||||
if (hasNextPage) {
|
||||
fetchNextPage({ cancelRefetch: false });
|
||||
}
|
||||
};
|
||||
|
||||
if (!domains) {
|
||||
return (
|
||||
@ -44,8 +38,8 @@ const DomainBlocks: React.FC = () => {
|
||||
<Column label={intl.formatMessage(messages.heading)}>
|
||||
<ScrollableList
|
||||
scrollKey='domainBlocks'
|
||||
onLoadMore={() => handleLoadMore(dispatch)}
|
||||
hasMore={hasMore}
|
||||
onLoadMore={handleLoadMore}
|
||||
hasMore={hasNextPage}
|
||||
emptyMessage={emptyMessage}
|
||||
listClassName='divide-y divide-gray-200 dark:divide-gray-800'
|
||||
>
|
||||
|
||||
57
packages/pl-fe/src/queries/settings/domain-blocks.ts
Normal file
57
packages/pl-fe/src/queries/settings/domain-blocks.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { getClient } from 'pl-fe/api';
|
||||
import { Entities } from 'pl-fe/entity-store/entities';
|
||||
|
||||
import { queryClient } from '../client';
|
||||
import { makePaginatedResponseQueryOptions } from '../utils/make-paginated-response-query-options';
|
||||
import { mutationOptions } from '../utils/mutation-options';
|
||||
|
||||
import type { MinifiedSuggestion } from '../trends/use-suggested-accounts';
|
||||
import type { EntityStore } from 'pl-fe/entity-store/types';
|
||||
import type { Account } from 'pl-fe/normalizers/account';
|
||||
import type { RootState, Store } from 'pl-fe/store';
|
||||
|
||||
let store: Store;
|
||||
import('pl-fe/store').then((value) => store = value.store).catch(() => {});
|
||||
|
||||
const domainBlocksQueryOptions = makePaginatedResponseQueryOptions(
|
||||
['settings', 'domainBlocks'],
|
||||
(client) => client.filtering.getDomainBlocks(),
|
||||
)();
|
||||
|
||||
const blockDomainMutationOptions = mutationOptions({
|
||||
mutationKey: ['settings', 'domainBlocks'],
|
||||
mutationFn: (domain: string) => getClient().filtering.blockDomain(domain),
|
||||
onSettled: (_, __, domain) => {
|
||||
queryClient.invalidateQueries(domainBlocksQueryOptions);
|
||||
|
||||
const accounts = selectAccountsByDomain(store.getState(), domain);
|
||||
if (!accounts) return;
|
||||
|
||||
queryClient.setQueryData<Array<MinifiedSuggestion>>(['suggestions'], suggestions => suggestions
|
||||
? suggestions.filter((suggestion) => !accounts.includes(suggestion.account_id))
|
||||
: undefined);
|
||||
},
|
||||
});
|
||||
|
||||
const unblockDomainMutationOptions = mutationOptions({
|
||||
mutationKey: ['settings', 'domainBlocks'],
|
||||
mutationFn: (domain: string) => getClient().filtering.unblockDomain(domain),
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries(domainBlocksQueryOptions);
|
||||
},
|
||||
});
|
||||
|
||||
const selectAccountsByDomain = (state: RootState, domain: string): string[] => {
|
||||
const store = state.entities[Entities.ACCOUNTS]?.store as EntityStore<Account> | undefined;
|
||||
const entries = store ? Object.entries(store) : undefined;
|
||||
const accounts = entries
|
||||
?.filter(([_, item]) => item && item.acct.endsWith(`@${domain}`))
|
||||
.map(([_, item]) => item!.id);
|
||||
return accounts || [];
|
||||
};
|
||||
|
||||
export {
|
||||
domainBlocksQueryOptions,
|
||||
blockDomainMutationOptions,
|
||||
unblockDomainMutationOptions,
|
||||
};
|
||||
@ -1,12 +0,0 @@
|
||||
import reducer from './domain-lists';
|
||||
|
||||
describe('domain_lists reducer', () => {
|
||||
it('should return the initial state', () => {
|
||||
expect(reducer(undefined, {} as any).toJS()).toEqual({
|
||||
blocks: {
|
||||
items: [],
|
||||
next: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,47 +0,0 @@
|
||||
import { create } from 'mutative';
|
||||
|
||||
import {
|
||||
DOMAIN_BLOCKS_FETCH_SUCCESS,
|
||||
DOMAIN_BLOCKS_EXPAND_SUCCESS,
|
||||
DOMAIN_UNBLOCK_SUCCESS,
|
||||
type DomainBlocksAction,
|
||||
} from '../actions/domain-blocks';
|
||||
|
||||
import type { PaginatedResponse } from 'pl-api';
|
||||
|
||||
interface State {
|
||||
blocks: {
|
||||
items: Array<string>;
|
||||
next: (() => Promise<PaginatedResponse<string>>) | null;
|
||||
};
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
blocks: {
|
||||
items: [],
|
||||
next: null,
|
||||
},
|
||||
};
|
||||
|
||||
const domainLists = (state: State = initialState, action: DomainBlocksAction): State => {
|
||||
switch (action.type) {
|
||||
case DOMAIN_BLOCKS_FETCH_SUCCESS:
|
||||
return create(state, (draft) => {
|
||||
draft.blocks.items = action.domains;
|
||||
draft.blocks.next = action.next;
|
||||
});
|
||||
case DOMAIN_BLOCKS_EXPAND_SUCCESS:
|
||||
return create(state, (draft) => {
|
||||
draft.blocks.items = [...new Set([...draft.blocks.items, ...action.domains])];
|
||||
draft.blocks.next = action.next;
|
||||
});
|
||||
case DOMAIN_UNBLOCK_SUCCESS:
|
||||
return create(state, (draft) => {
|
||||
draft.blocks.items = draft.blocks.items.filter(item => item !== action.domain);
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export { domainLists as default };
|
||||
@ -12,7 +12,6 @@ import auth from './auth';
|
||||
import compose from './compose';
|
||||
import contexts from './contexts';
|
||||
import conversations from './conversations';
|
||||
import domain_lists from './domain-lists';
|
||||
import draft_statuses from './draft-statuses';
|
||||
import filters from './filters';
|
||||
import instance from './instance';
|
||||
@ -41,7 +40,6 @@ const reducers = {
|
||||
compose,
|
||||
contexts,
|
||||
conversations,
|
||||
domain_lists,
|
||||
draft_statuses,
|
||||
entities,
|
||||
filters,
|
||||
|
||||
Reference in New Issue
Block a user