nicolium: partial migration of filters
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -1,106 +1,7 @@
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import toast from '@/toast';
|
||||
import { isLoggedIn } from '@/utils/auth';
|
||||
|
||||
import { getClient } from '../api';
|
||||
|
||||
import type { AppDispatch, RootState } from '@/store';
|
||||
import type { Filter, FilterContext } from 'pl-api';
|
||||
import type { Filter } from 'pl-api';
|
||||
|
||||
const FILTERS_FETCH_SUCCESS = 'FILTERS_FETCH_SUCCESS' as const;
|
||||
|
||||
const messages = defineMessages({
|
||||
added: { id: 'filters.added', defaultMessage: 'Filter added.' },
|
||||
updated: { id: 'filters.updated', defaultMessage: 'Filter updated.' },
|
||||
removed: { id: 'filters.removed', defaultMessage: 'Filter deleted.' },
|
||||
});
|
||||
|
||||
type FilterKeywords = { keyword: string; whole_word: boolean }[];
|
||||
|
||||
const fetchFilters = () => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
return getClient(getState)
|
||||
.filtering.getFilters()
|
||||
.then((data) =>
|
||||
dispatch<FiltersAction>({
|
||||
type: FILTERS_FETCH_SUCCESS,
|
||||
filters: data,
|
||||
}),
|
||||
)
|
||||
.catch((error) => ({
|
||||
error,
|
||||
}));
|
||||
};
|
||||
|
||||
const fetchFilter = (filterId: string) => (dispatch: AppDispatch, getState: () => RootState) =>
|
||||
getClient(getState).filtering.getFilter(filterId);
|
||||
|
||||
const createFilter =
|
||||
(
|
||||
title: string,
|
||||
expires_in: number | undefined,
|
||||
context: Array<FilterContext>,
|
||||
filter_action: Filter['filter_action'],
|
||||
keywords_attributes: FilterKeywords,
|
||||
) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) =>
|
||||
getClient(getState)
|
||||
.filtering.createFilter({
|
||||
title,
|
||||
context,
|
||||
filter_action,
|
||||
expires_in,
|
||||
keywords_attributes,
|
||||
})
|
||||
.then((response) => {
|
||||
toast.success(messages.added);
|
||||
|
||||
return response;
|
||||
});
|
||||
|
||||
const updateFilter =
|
||||
(
|
||||
filterId: string,
|
||||
title: string,
|
||||
expires_in: number | undefined,
|
||||
context: Array<FilterContext>,
|
||||
filter_action: Filter['filter_action'],
|
||||
keywords_attributes: FilterKeywords,
|
||||
) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) =>
|
||||
getClient(getState)
|
||||
.filtering.updateFilter(filterId, {
|
||||
title,
|
||||
context,
|
||||
filter_action,
|
||||
expires_in,
|
||||
keywords_attributes,
|
||||
})
|
||||
.then((response) => {
|
||||
toast.success(messages.updated);
|
||||
|
||||
return response;
|
||||
});
|
||||
|
||||
const deleteFilter = (filterId: string) => (dispatch: AppDispatch, getState: () => RootState) =>
|
||||
getClient(getState)
|
||||
.filtering.deleteFilter(filterId)
|
||||
.then((response) => {
|
||||
toast.success(messages.removed);
|
||||
|
||||
return response;
|
||||
});
|
||||
|
||||
type FiltersAction = { type: typeof FILTERS_FETCH_SUCCESS; filters: Array<Filter> };
|
||||
|
||||
export {
|
||||
FILTERS_FETCH_SUCCESS,
|
||||
fetchFilters,
|
||||
fetchFilter,
|
||||
createFilter,
|
||||
updateFilter,
|
||||
deleteFilter,
|
||||
type FiltersAction,
|
||||
};
|
||||
export { FILTERS_FETCH_SUCCESS, type FiltersAction };
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { fetchFilters } from '@/actions/filters';
|
||||
import { MARKER_FETCH_SUCCESS } from '@/actions/markers';
|
||||
import { updateNotificationsQueue } from '@/actions/notifications';
|
||||
import { getLocale } from '@/actions/settings';
|
||||
@ -134,7 +133,7 @@ const useUserStream = () => {
|
||||
updateConversations(event.payload);
|
||||
break;
|
||||
case 'filters_changed':
|
||||
dispatch(fetchFilters());
|
||||
queryClient.invalidateQueries({ queryKey: ['filters'] });
|
||||
break;
|
||||
case 'chat_update':
|
||||
dispatch((_dispatch, getState) => {
|
||||
|
||||
@ -4,7 +4,6 @@ import React, { Suspense, useEffect, useRef } from 'react';
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
|
||||
import { fetchConfig } from '@/actions/admin';
|
||||
import { fetchFilters } from '@/actions/filters';
|
||||
import { fetchMarker } from '@/actions/markers';
|
||||
import { expandNotifications } from '@/actions/notifications';
|
||||
import { register as registerPushNotifications } from '@/actions/push-notifications/registerer';
|
||||
@ -23,6 +22,7 @@ import { useOwnAccount } from '@/hooks/use-own-account';
|
||||
import { prefetchFollowRequests } from '@/queries/accounts/use-follow-requests';
|
||||
import { queryClient } from '@/queries/client';
|
||||
import { prefetchCustomEmojis } from '@/queries/instance/use-custom-emojis';
|
||||
import { useFilters } from '@/queries/settings/use-filters';
|
||||
import { scheduledStatusesQueryOptions } from '@/queries/statuses/scheduled-statuses';
|
||||
import { useSettings } from '@/stores/settings';
|
||||
import { useShoutboxSubscription } from '@/stores/shoutbox';
|
||||
@ -38,11 +38,11 @@ import {
|
||||
DropdownNavigation,
|
||||
StatusHoverCard,
|
||||
} from './util/async-components';
|
||||
import GlobalHotkeys from './util/global-hotkeys';
|
||||
|
||||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
||||
// Without this it ends up in ~8 very commonly used bundles.
|
||||
import '@/components/status';
|
||||
import GlobalHotkeys from './util/global-hotkeys';
|
||||
|
||||
const UI: React.FC = React.memo(() => {
|
||||
const navigate = useNavigate();
|
||||
@ -60,6 +60,7 @@ const UI: React.FC = React.memo(() => {
|
||||
const standalone = useAppSelector(isStandalone);
|
||||
|
||||
useShoutboxSubscription();
|
||||
useFilters();
|
||||
|
||||
const { isDragging } = useDraggedFiles(node);
|
||||
|
||||
@ -100,10 +101,6 @@ const UI: React.FC = React.memo(() => {
|
||||
dispatch(fetchConfig());
|
||||
}
|
||||
|
||||
if (features.filters || features.filtersV2) {
|
||||
setTimeout(() => dispatch(fetchFilters()), 500);
|
||||
}
|
||||
|
||||
if (account.locked) {
|
||||
setTimeout(() => prefetchFollowRequests(client), 700);
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ import { Filter, type FilterContext } from 'pl-api';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { createFilter, fetchFilter, updateFilter } from '@/actions/filters';
|
||||
import List, { ListItem } from '@/components/list';
|
||||
import MissingIndicator from '@/components/missing-indicator';
|
||||
import Button from '@/components/ui/button';
|
||||
@ -20,8 +19,8 @@ import Text from '@/components/ui/text';
|
||||
import Toggle from '@/components/ui/toggle';
|
||||
import { SelectDropdown } from '@/features/forms';
|
||||
import { editFilterRoute } from '@/features/ui/router';
|
||||
import { useAppDispatch } from '@/hooks/use-app-dispatch';
|
||||
import { useFeatures } from '@/hooks/use-features';
|
||||
import { useCreateFilter, useFilter, useUpdateFilter } from '@/queries/settings/use-filters';
|
||||
import toast from '@/toast';
|
||||
|
||||
import type { StreamfieldComponent } from '@/components/ui/streamfield';
|
||||
@ -76,7 +75,7 @@ const messages = defineMessages({
|
||||
},
|
||||
add_new: { id: 'column.filters.add_new', defaultMessage: 'Add new filter' },
|
||||
edit: { id: 'column.filters.edit', defaultMessage: 'Edit filter' },
|
||||
create_error: { id: 'column.filters.create_error', defaultMessage: 'Error adding filter' },
|
||||
createError: { id: 'column.filters.create_error', defaultMessage: 'Error adding filter' },
|
||||
expiration_never: { id: 'column.filters.expiration.never', defaultMessage: 'Never' },
|
||||
expiration_1800: { id: 'column.filters.expiration.1800', defaultMessage: '30 minutes' },
|
||||
expiration_3600: { id: 'column.filters.expiration.3600', defaultMessage: '1 hour' },
|
||||
@ -123,11 +122,15 @@ const EditFilterPage: React.FC = () => {
|
||||
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [notFound, setNotFound] = useState(false);
|
||||
const {
|
||||
data: filter,
|
||||
isFetching: isFetchingFilter,
|
||||
isError: notFound,
|
||||
} = useFilter(filterId !== 'new' ? filterId : undefined);
|
||||
const { mutate: createFilter, isPending: isCreating } = useCreateFilter();
|
||||
const { mutate: updateFilter, isPending: isUpdating } = useUpdateFilter(filterId);
|
||||
|
||||
const [title, setTitle] = useState('');
|
||||
const [expiresIn, setExpiresIn] = useState<number | undefined>();
|
||||
@ -176,17 +179,23 @@ const EditFilterPage: React.FC = () => {
|
||||
context.push('account');
|
||||
}
|
||||
|
||||
dispatch(
|
||||
filterId !== 'new'
|
||||
? updateFilter(filterId, title, expiresIn, context, filterAction, keywords)
|
||||
: createFilter(title, expiresIn, context, filterAction, keywords),
|
||||
)
|
||||
.then(() => {
|
||||
navigate({ to: '/filters' });
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(intl.formatMessage(messages.create_error));
|
||||
});
|
||||
(filterId !== 'new' ? updateFilter : createFilter)(
|
||||
{
|
||||
title,
|
||||
expires_in: expiresIn,
|
||||
context,
|
||||
filter_action: filterAction,
|
||||
keywords_attributes: keywords,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
navigate({ to: '/filters' });
|
||||
},
|
||||
onError: () => {
|
||||
toast.error(intl.formatMessage(messages.createError));
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const handleChangeKeyword = (keywords: { keyword: string; whole_word: boolean }[]) => {
|
||||
@ -206,25 +215,17 @@ const EditFilterPage: React.FC = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (filterId !== 'new') {
|
||||
setLoading(true);
|
||||
dispatch(fetchFilter(filterId))?.then((filter) => {
|
||||
if (filter) {
|
||||
setTitle(filter.title);
|
||||
setHomeTimeline(filter.context.includes('home'));
|
||||
setPublicTimeline(filter.context.includes('public'));
|
||||
setNotifications(filter.context.includes('notifications'));
|
||||
setConversations(filter.context.includes('thread'));
|
||||
setAccounts(filter.context.includes('account'));
|
||||
setFilterAction(filter.filter_action);
|
||||
setKeywords(filter.keywords);
|
||||
} else {
|
||||
setNotFound(true);
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
if (filter) {
|
||||
setTitle(filter.title);
|
||||
setHomeTimeline(filter.context.includes('home'));
|
||||
setPublicTimeline(filter.context.includes('public'));
|
||||
setNotifications(filter.context.includes('notifications'));
|
||||
setConversations(filter.context.includes('thread'));
|
||||
setAccounts(filter.context.includes('account'));
|
||||
setFilterAction(filter.filter_action);
|
||||
setKeywords(filter.keywords);
|
||||
}
|
||||
}, [filterId]);
|
||||
}, [isFetchingFilter]);
|
||||
|
||||
if (notFound) return <MissingIndicator />;
|
||||
|
||||
@ -363,7 +364,11 @@ const EditFilterPage: React.FC = () => {
|
||||
{features.filtersV2 && keywordsField}
|
||||
|
||||
<FormActions>
|
||||
<Button type='submit' theme='primary' disabled={loading}>
|
||||
<Button
|
||||
type='submit'
|
||||
theme='primary'
|
||||
disabled={isFetchingFilter || isUpdating || isCreating}
|
||||
>
|
||||
{intl.formatMessage(filterId !== 'new' ? messages.edit : messages.add_new)}
|
||||
</Button>
|
||||
</FormActions>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { fetchFilters, deleteFilter } from '@/actions/filters';
|
||||
import RelativeTimestamp from '@/components/relative-timestamp';
|
||||
import ScrollableList from '@/components/scrollable-list';
|
||||
import Button from '@/components/ui/button';
|
||||
@ -9,26 +8,25 @@ import Column from '@/components/ui/column';
|
||||
import HStack from '@/components/ui/hstack';
|
||||
import Stack from '@/components/ui/stack';
|
||||
import Text from '@/components/ui/text';
|
||||
import { useAppDispatch } from '@/hooks/use-app-dispatch';
|
||||
import { useAppSelector } from '@/hooks/use-app-selector';
|
||||
import { useFeatures } from '@/hooks/use-features';
|
||||
import { useDeleteFilter, useFilters } from '@/queries/settings/use-filters';
|
||||
import toast from '@/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.filters', defaultMessage: 'Muted words' },
|
||||
home_timeline: { id: 'column.filters.home_timeline', defaultMessage: 'Home timeline' },
|
||||
public_timeline: { id: 'column.filters.public_timeline', defaultMessage: 'Public timeline' },
|
||||
homeTimeline: { id: 'column.filters.home_timeline', defaultMessage: 'Home timeline' },
|
||||
publicTimeline: { id: 'column.filters.public_timeline', defaultMessage: 'Public timeline' },
|
||||
notifications: { id: 'column.filters.notifications', defaultMessage: 'Notifications' },
|
||||
conversations: { id: 'column.filters.conversations', defaultMessage: 'Conversations' },
|
||||
accounts: { id: 'column.filters.accounts', defaultMessage: 'Accounts' },
|
||||
delete_error: { id: 'column.filters.delete_error', defaultMessage: 'Error deleting filter' },
|
||||
deleteError: { id: 'column.filters.delete_error', defaultMessage: 'Error deleting filter' },
|
||||
edit: { id: 'column.filters.edit', defaultMessage: 'Edit filter' },
|
||||
delete: { id: 'column.filters.delete', defaultMessage: 'Delete' },
|
||||
});
|
||||
|
||||
const contexts = {
|
||||
home: messages.home_timeline,
|
||||
public: messages.public_timeline,
|
||||
home: messages.homeTimeline,
|
||||
public: messages.publicTimeline,
|
||||
notifications: messages.notifications,
|
||||
thread: messages.conversations,
|
||||
account: messages.accounts,
|
||||
@ -36,23 +34,19 @@ const contexts = {
|
||||
|
||||
const FiltersPage = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const { filtersV2 } = useFeatures();
|
||||
|
||||
const filters = useAppSelector((state) => state.filters);
|
||||
const { data: filters = [] } = useFilters();
|
||||
const { mutate: deleteFilter } = useDeleteFilter();
|
||||
|
||||
const handleFilterDelete = (id: string) => () => {
|
||||
dispatch(deleteFilter(id))
|
||||
.then(() => dispatch(fetchFilters()))
|
||||
.catch(() => {
|
||||
toast.error(intl.formatMessage(messages.delete_error));
|
||||
});
|
||||
deleteFilter(id, {
|
||||
onError: () => {
|
||||
toast.error(intl.formatMessage(messages.deleteError));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchFilters());
|
||||
}, []);
|
||||
|
||||
const emptyMessage = (
|
||||
<FormattedMessage id='empty_column.filters' defaultMessage="You haven't muted any word yet." />
|
||||
);
|
||||
|
||||
@ -4,6 +4,7 @@ const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
staleTime: 60000, // 1 minute
|
||||
gcTime: Infinity,
|
||||
retry: false,
|
||||
|
||||
93
packages/pl-fe/src/queries/settings/use-filters.ts
Normal file
93
packages/pl-fe/src/queries/settings/use-filters.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { type FiltersAction, FILTERS_FETCH_SUCCESS } from '@/actions/filters';
|
||||
import { useAppDispatch } from '@/hooks/use-app-dispatch';
|
||||
import { useClient } from '@/hooks/use-client';
|
||||
import { useFeatures } from '@/hooks/use-features';
|
||||
|
||||
import type { CreateFilterParams, Filter, UpdateFilterParams } from 'pl-api';
|
||||
|
||||
const useFilters = () => {
|
||||
const client = useClient();
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['filters'],
|
||||
queryFn: async () => {
|
||||
const response = await client.filtering.getFilters();
|
||||
|
||||
dispatch<FiltersAction>({
|
||||
type: FILTERS_FETCH_SUCCESS,
|
||||
filters: response,
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
enabled: features.filters || features.filtersV2,
|
||||
staleTime: 30 * 60 * 1000,
|
||||
});
|
||||
};
|
||||
|
||||
const useFilter = (filterId?: string) => {
|
||||
const client = useClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['filters', filterId],
|
||||
queryFn: () => {
|
||||
if (!filterId) return undefined;
|
||||
return client.filtering.getFilter(filterId);
|
||||
},
|
||||
enabled: !!filterId,
|
||||
placeholderData: () => {
|
||||
queryClient
|
||||
.getQueryData<Array<Filter>>(['filters'])
|
||||
?.find((filter) => filter.id === filterId);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const useCreateFilter = () => {
|
||||
const client = useClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ['filters', 'create'],
|
||||
mutationFn: (data: CreateFilterParams) => client.filtering.createFilter(data),
|
||||
onSettled: (data) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['filters'] });
|
||||
if (data) queryClient.setQueryData<Filter>(['filters', data.id], data);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const useUpdateFilter = (filterId: string) => {
|
||||
const client = useClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ['filters', filterId, 'update'],
|
||||
mutationFn: (data: UpdateFilterParams) => client.filtering.updateFilter(filterId, data),
|
||||
onSettled: (data) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['filters'] });
|
||||
if (data) queryClient.setQueryData<Filter>(['filters', filterId], data);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const useDeleteFilter = () => {
|
||||
const client = useClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ['filters', 'delete'],
|
||||
mutationFn: (filterId: string) => client.filtering.deleteFilter(filterId),
|
||||
onSettled: (_, __, filterId) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['filters'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['filters', filterId] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export { useFilters, useFilter, useCreateFilter, useUpdateFilter, useDeleteFilter };
|
||||
@ -15,7 +15,7 @@ const useTrends = () => {
|
||||
queryKey: ['trends', 'tags'],
|
||||
queryFn: () => client.trends.getTrendingTags(),
|
||||
placeholderData: [],
|
||||
staleTime: 600000, // 10 minutes
|
||||
staleTime: 10 * 60 * 1000, // 10 minutes
|
||||
enabled: isLoggedIn && features.trends,
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user