Complete search_filter reducer implementation and refactor code

merge-requests/3337/head
danidfra 2025-02-25 18:34:06 -03:00
rodzic 781aaefbe1
commit bba853893d
5 zmienionych plików z 236 dodań i 200 usunięć

Wyświetl plik

@ -11,57 +11,43 @@ import Text from 'soapbox/components/ui/text.tsx';
import { import {
CreateFilter, CreateFilter,
LanguageFilter, LanguageFilter,
MediaFilter,
PlatformFilters, PlatformFilters,
RepliesFilter, ToggleFilter,
generateFilter, generateFilter,
} from 'soapbox/features/explorer/components/filters.tsx'; } from 'soapbox/features/explorer/components/filters.tsx';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts'; import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
const messages = defineMessages({ const messages = defineMessages({
filters: { id: 'column.explorer.filters', defaultMessage: 'Filters:' }, filters: { id: 'column.explorer.filters', defaultMessage: 'Filters:' },
showReplies: { id: 'column.explorer.filters.show_replies', defaultMessage: 'Show replies:' },
language: { id: 'column.explorer.filters.language', defaultMessage: 'Language:' },
platforms: { id: 'column.explorer.filters.platforms', defaultMessage: 'Platforms:' },
createYourFilter: { id: 'column.explorer.filters.create_your_filter', defaultMessage: 'Create your filter' },
filterByWords: { id: 'column.explorer.filters.filter_by_words', defaultMessage: 'Filter by this/these words' },
include: { id: 'column.explorer.filters.include', defaultMessage: 'Include' },
exclude: { id: 'column.explorer.filters.exclude', defaultMessage: 'Exclude' },
nostr: { id: 'column.explorer.filters.nostr', defaultMessage: 'Nostr' },
bluesky: { id: 'column.explorer.filters.bluesky', defaultMessage: 'Bluesky' },
fediverse: { id: 'column.explorer.filters.fediverse', defaultMessage: 'Fediverse' },
cancel: { id: 'column.explorer.filters.cancel', defaultMessage: 'Cancel' },
addFilter: { id: 'column.explorer.filters.add_filter', defaultMessage: 'Add Filter' },
}); });
interface IGenerateFilter { interface IGenerateFilter {
name: string; name: string;
state: boolean | null; status: boolean | null;
value: string; value: string;
} }
const ExplorerFilter = () => { const ExplorerFilter = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const filters = useAppSelector((state) => state.search_filter);
const intl = useIntl(); const intl = useIntl();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(true);
const [tagFilters, setTagFilters] = useState<IGenerateFilter[]>([
{ 'name': 'Nostr', state: null, 'value': 'protocol:nostr' },
{ 'name': 'Bluesky', state: null, 'value': 'protocol:atproto' },
{ 'name': 'Fediverse', state: null, 'value': 'protocol:activitypub' },
]);
useEffect( useEffect(
() => { () => {
const language = filters[0].name.toLowerCase() !== 'default' ? filters[0].value : '';
const value = tagFilters const protocols = filters.slice(1, 4).filter((protocol) => !protocol.status).map((filter) => filter.value).join(' ');
.filter((searchFilter) => !searchFilter.value.startsWith('protocol:')) const defaultFilters = filters.slice(4, 7).filter((x) => x.status).map((filter) => filter.value).join(' ');
const newFilters = filters.slice(7)
.map((searchFilter) => searchFilter.value) .map((searchFilter) => searchFilter.value)
.join(' '); .join(' ');
const value = [ language, protocols, defaultFilters, newFilters ].join(' ');
dispatch(changeSearch(value)); dispatch(changeSearch(value));
dispatch(submitSearch(undefined, value)); dispatch(submitSearch(undefined, value));
}, [tagFilters, dispatch], }, [filters, dispatch],
); );
return ( return (
@ -74,7 +60,7 @@ const ExplorerFilter = () => {
{intl.formatMessage(messages.filters)} {intl.formatMessage(messages.filters)}
</Text> </Text>
{tagFilters.length > 0 && [...tagFilters.slice(0, 3).filter((x)=> x.value[0] !== '-' && x.state === null).map((value) => generateFilter(value, setTagFilters)), ...tagFilters.slice(3).map((value) => generateFilter(value, setTagFilters))]} {filters.length > 0 && [...filters.slice(0, 7).filter((value) => value.status).map((value) => generateFilter(dispatch, value)), ...filters.slice(7).map((value) => generateFilter(dispatch, value))]}
</HStack> </HStack>
<IconButton <IconButton
@ -88,21 +74,24 @@ const ExplorerFilter = () => {
<Stack className={`overflow-hidden transition-all duration-500 ease-in-out ${isOpen ? 'max-h-[1000px] opacity-100' : 'max-h-0 opacity-0'}`} space={3}> <Stack className={`overflow-hidden transition-all duration-500 ease-in-out ${isOpen ? 'max-h-[1000px] opacity-100' : 'max-h-0 opacity-0'}`} space={3}>
{/* Show Reply toggle */} {/* Show Reply toggle */}
<RepliesFilter onChangeFilters={setTagFilters} /> <ToggleFilter type='reply' />
{/* Media toggle */} {/* Media toggle */}
<MediaFilter onChangeFilters={setTagFilters} /> <ToggleFilter type='media' />
{/* Video toggle */}
<ToggleFilter type='video' />
{/* Language */} {/* Language */}
<LanguageFilter onChangeFilters={setTagFilters} /> <LanguageFilter />
{/* Platforms */} {/* Platforms */}
<PlatformFilters onChangeFilters={setTagFilters} filters={tagFilters} /> <PlatformFilters />
<Divider /> <Divider />
{/* Create your filter */} {/* Create your filter */}
<CreateFilter onChangeFilters={setTagFilters} /> <CreateFilter />
</Stack> </Stack>

Wyświetl plik

@ -15,11 +15,16 @@ import Text from 'soapbox/components/ui/text.tsx';
import Toggle from 'soapbox/components/ui/toggle.tsx'; import Toggle from 'soapbox/components/ui/toggle.tsx';
import { IGenerateFilter } from 'soapbox/features/explorer/components/explorerFilter.tsx'; import { IGenerateFilter } from 'soapbox/features/explorer/components/explorerFilter.tsx';
import { SelectDropdown } from 'soapbox/features/forms/index.tsx'; import { SelectDropdown } from 'soapbox/features/forms/index.tsx';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { changeLanguage, createFilter, handleToggle, removeFilter, selectProtocol } from 'soapbox/reducers/search-filter.ts';
import { AppDispatch, RootState } from 'soapbox/store.ts';
import toast from 'soapbox/toast.tsx';
const messages = defineMessages({ const messages = defineMessages({
filters: { id: 'column.explorer.filters', defaultMessage: 'Filters:' },
showReplies: { id: 'column.explorer.filters.show_replies', defaultMessage: 'Show replies:' }, showReplies: { id: 'column.explorer.filters.show_replies', defaultMessage: 'Show replies:' },
showMedia: { id: 'column.explorer.filters.show_text_posts', defaultMessage: 'Just text posts:' }, showMedia: { id: 'column.explorer.filters.show_text_posts', defaultMessage: 'Just text posts:' },
showVideo: { id: 'column.explorer.filters.show_video_posts', defaultMessage: 'Just posts with video:' },
language: { id: 'column.explorer.filters.language', defaultMessage: 'Language:' }, language: { id: 'column.explorer.filters.language', defaultMessage: 'Language:' },
platforms: { id: 'column.explorer.filters.platforms', defaultMessage: 'Platforms:' }, platforms: { id: 'column.explorer.filters.platforms', defaultMessage: 'Platforms:' },
createYourFilter: { id: 'column.explorer.filters.create_your_filter', defaultMessage: 'Create your filter' }, createYourFilter: { id: 'column.explorer.filters.create_your_filter', defaultMessage: 'Create your filter' },
@ -93,36 +98,46 @@ const languages = {
zh: '中文', zh: '中文',
}; };
interface IFilter { const PlatformFilters = () => {
onChangeFilters: React.Dispatch<React.SetStateAction<IGenerateFilter[]>>;
}
interface IPlatformFilters {
filters: IGenerateFilter[];
onChangeFilters: React.Dispatch<React.SetStateAction<IGenerateFilter[]>>;
}
const PlatformFilters = ({ onChangeFilters, filters }: IPlatformFilters) => {
const intl = useIntl(); const intl = useIntl();
const dispatch = useAppDispatch();
const filterList = useAppSelector((state: RootState) => state.search_filter);
const toggleProtocolFilter = (protocolName: string, protocolValue: string) => { const handleProtocolFilter = (e: React.ChangeEvent<HTMLInputElement>) => {
onChangeFilters(prevFilters => { const protocol = e.target.name;
const exists = prevFilters.some(tag => tag.name.toLowerCase() === protocolName.toLowerCase() && tag.value[0] !== '-'); dispatch(selectProtocol(protocol));
const newFilterList = prevFilters.filter(tag => tag.name.toLowerCase() !== protocolName.toLowerCase()); };
const newFilter = { const CheckBox = ({ protocolN } : { protocolN: string }) => {
name: protocolName, const filter = filterList.find((filter) => filter.name.toLowerCase() === protocolN);
state: null, const checked = filter?.status;
value: exists ? `-protocol:${protocolValue}` : `protocol:${protocolValue}`,
};
if (newFilterList.length === 0) { let message;
return [newFilter]; switch (protocolN) {
} case 'nostr':
message = messages.nostr;
break;
case 'bluesky':
message = messages.bluesky;
break;
default:
message = messages.fediverse;
}
return [newFilterList[0], newFilter, ...newFilterList.slice(1)];
}); return (
<HStack alignItems='center' space={2}>
<Checkbox
name={protocolN}
checked={checked}
onChange={handleProtocolFilter}
/>
<Text size='lg'>
{intl.formatMessage(message)}
</Text>
</HStack>
);
}; };
return ( return (
@ -132,56 +147,33 @@ const PlatformFilters = ({ onChangeFilters, filters }: IPlatformFilters) => {
</Text> </Text>
{/* Nostr */} {/* Nostr */}
<HStack alignItems='center' space={2}> <CheckBox protocolN={'nostr'} />
<Checkbox
name='nostr'
checked={filters.some(tag => tag.name.toLowerCase() === 'nostr' && tag.value[0] !== '-')}
onChange={() => toggleProtocolFilter('Nostr', 'nostr')}
/>
<Text size='lg'>
{intl.formatMessage(messages.nostr)}
</Text>
</HStack>
{/* Bluesky */} {/* Bluesky */}
<HStack alignItems='center' space={2}> <CheckBox protocolN={'bluesky'} />
<Checkbox
name='bluesky'
checked={filters.some(tag => tag.name.toLowerCase() === 'bluesky' && tag.value[0] !== '-')}
onChange={() => toggleProtocolFilter('Bluesky', 'atproto')}
/>
<Text size='lg'>
{intl.formatMessage(messages.bluesky)}
</Text>
</HStack>
{/* Fediverse */} {/* Fediverse */}
<HStack alignItems='center' space={2}> <CheckBox protocolN={'fediverse'} />
<Checkbox
name='fediverse'
checked={filters.some(tag => tag.name.toLowerCase() === 'fediverse' && tag.value[0] !== '-')}
onChange={() => toggleProtocolFilter('Fediverse', 'activitypub')}
/>
<Text size='lg'>
{intl.formatMessage(messages.fediverse)}
</Text>
</HStack>
</HStack> </HStack>
); );
}; };
const CreateFilter = ({ onChangeFilters }: IFilter) => { const CreateFilter = () => {
const intl = useIntl(); const intl = useIntl();
const dispatch = useAppDispatch();
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const [include, setInclude] = useState(''); const [include, setInclude] = useState(true);
const hasValue = inputValue.length > 0; const hasValue = inputValue.length > 0;
const handleAddFilter = () => { const handleAddFilter = () => {
onChangeFilters((prev) => { if (inputValue.length > 0) {
return [...prev, { name: inputValue, state: include === '', value: `${include}${inputValue.split(' ').join(` ${include}`)}` }]; dispatch(createFilter({ name: inputValue, status: include }));
}); } else {
toast.error('Hey there... you forget to write the filter!');
}
}; };
return ( return (
@ -223,9 +215,9 @@ const CreateFilter = ({ onChangeFilters }: IFilter) => {
<HStack alignItems='center' space={2}> <HStack alignItems='center' space={2}>
<Checkbox <Checkbox
name='include' name='include'
checked={!(include.length > 0)} checked={include}
onChange={() => { onChange={() => {
setInclude(''); setInclude(true);
}} }}
/> />
<Text size='lg'> <Text size='lg'>
@ -237,9 +229,9 @@ const CreateFilter = ({ onChangeFilters }: IFilter) => {
<HStack alignItems='center' space={2}> <HStack alignItems='center' space={2}>
<Checkbox <Checkbox
name='exclude' name='exclude'
checked={(include.length > 0)} checked={!include}
onChange={() => { onChange={() => {
setInclude('-'); setInclude(false);
}} }}
/> />
<Text size='lg'> <Text size='lg'>
@ -252,7 +244,7 @@ const CreateFilter = ({ onChangeFilters }: IFilter) => {
<HStack className='w-full p-0.5' space={2}> <HStack className='w-full p-0.5' space={2}>
<Button <Button
className='w-1/2' theme='secondary' onClick={() => { className='w-1/2' theme='secondary' onClick={() => {
setInclude(''); setInclude(false);
setInputValue(''); setInputValue('');
} }
} }
@ -270,19 +262,13 @@ const CreateFilter = ({ onChangeFilters }: IFilter) => {
}; };
const LanguageFilter = ({ onChangeFilters }: IFilter) => { const LanguageFilter = () => {
const intl = useIntl(); const intl = useIntl();
const dispatch = useAppDispatch();
const handleSelectChange: React.ChangeEventHandler<HTMLSelectElement> = e => { const handleSelectChange: React.ChangeEventHandler<HTMLSelectElement> = e => {
const value = e.target.value; const language = e.target.value;
dispatch(changeLanguage(language));
if (value.toLowerCase() === 'default') {
onChangeFilters((prevValue) => prevValue.filter((value) => !value.value.includes('language:')));
} else {
onChangeFilters((prevValue) => {
return [{ name: value.toUpperCase(), state: null, value: `language:${value}` }, ...prevValue.filter((value) => !value.value.includes('language:'))];
});
}
}; };
return ( return (
@ -302,105 +288,97 @@ const LanguageFilter = ({ onChangeFilters }: IFilter) => {
}; };
const RepliesFilter = ({ onChangeFilters }: IFilter) => { const ToggleFilter = ({ type }: {type: 'reply' | 'media' | 'video'}) => {
const intl = useIntl(); const intl = useIntl();
const [showReplies, setShowReplies] = useState(false); const dispatch = useAppDispatch();
const filterType = type.toLowerCase();
let label;
const handleToggleReplies: React.ChangeEventHandler<HTMLInputElement> = (e) => { switch (type) {
setShowReplies(!showReplies); case 'reply':
const isOn = e.target.checked; label = intl.formatMessage(messages.showReplies);
break;
case 'media':
label = intl.formatMessage(messages.showMedia);
break;
default:
label = intl.formatMessage(messages.showVideo);
}
if (isOn) { const filters = useAppSelector((state) => state.search_filter);
onChangeFilters((prevValue) => [...prevValue.filter((prev) => prev.name.toLowerCase() !== 'reply'), { name: 'Reply', state: null, value: 'reply:true' }]); const repliesFilter = filters.find((filter) => filter.name.toLowerCase() === filterType);
} else { const checked = repliesFilter?.status;
onChangeFilters((prevValue) => [...prevValue.filter((prev) => prev.name.toLowerCase() !== 'reply')]);
} const handleToggleComponent = () => {
dispatch(handleToggle({ type: filterType, checked: !checked }));
}; };
return ( return (
<HStack className='flex-wrap whitespace-normal' alignItems='center' space={2}> <HStack className='flex-wrap whitespace-normal' alignItems='center' space={2}>
<Text size='lg' weight='bold'> <Text size='lg' weight='bold'>
{intl.formatMessage(messages.showReplies)} {label}
</Text> </Text>
<Toggle <Toggle
checked={showReplies} checked={checked}
onChange={handleToggleReplies} onChange={handleToggleComponent}
/> />
</HStack> </HStack>
); );
}; };
const MediaFilter = ({ onChangeFilters }: IFilter) => { const generateFilter = (dispatch: AppDispatch, { name, status }: IGenerateFilter) => {
const intl = useIntl();
const [showMedia, setShowMedia] = useState(false);
const handleToggleReplies: React.ChangeEventHandler<HTMLInputElement> = (e) => {
setShowMedia(!showMedia);
const isOn = e.target.checked;
if (isOn) {
onChangeFilters((prevValue) => [...prevValue.filter((prev) => prev.name.toLowerCase() !== 'text'), { name: 'Text', state: null, value: 'media:false' }]);
} else {
onChangeFilters((prevValue) => [...prevValue.filter((prev) => prev.name.toLowerCase() !== 'text')]);
}
};
return (
<HStack className='flex-wrap whitespace-normal' alignItems='center' space={2}>
<Text size='lg' weight='bold'>
{intl.formatMessage(messages.showMedia)}
</Text>
<Toggle
checked={showMedia}
onChange={handleToggleReplies}
/>
</HStack>
);
};
const generateFilter = ({ name, state }: IGenerateFilter, onChangeFilters: React.Dispatch<React.SetStateAction<IGenerateFilter[]>>) => {
let borderColor = ''; let borderColor = '';
let textColor = ''; let textColor = '';
let hasButton = false; let hasButton = false;
switch (name.toLowerCase()) { const nameLowCase = name.toLowerCase();
case 'nostr':
borderColor = 'border-purple-500'; const handleChangeFilters = () => {
textColor = 'text-purple-500'; dispatch(removeFilter(name));
break; };
case 'bluesky':
borderColor = 'border-blue-500'; if (Object.keys(languages).some((lang) => lang.toLowerCase() === nameLowCase)) {
textColor = 'text-blue-500'; borderColor = 'border-gray-500';
break; textColor = 'text-gray-500';
case 'fediverse': } else {
borderColor = 'border-indigo-500'; switch (nameLowCase) {
textColor = 'text-indigo-500'; case 'reply':
break; case 'media':
default: case 'video':
if (name.toLowerCase() === 'reply' || name.toLowerCase() === 'text' || Object.keys(languages).some((lang) => lang === name.toLowerCase())) { borderColor = 'border-gray-500';
borderColor = 'border-grey-500'; textColor = 'text-gray-500';
textColor = 'text-grey-500';
break; break;
} case 'nostr':
borderColor = state ? 'border-green-500' : 'border-red-500'; borderColor = 'border-purple-500';
textColor = state ? 'text-green-500' : 'text-red-500'; textColor = 'text-purple-500';
hasButton = true; break;
case 'bluesky':
borderColor = 'border-blue-500';
textColor = 'text-blue-500';
break;
case 'fediverse':
borderColor = 'border-indigo-500';
textColor = 'text-indigo-500';
break;
default:
borderColor = status ? 'border-green-500' : 'border-red-500';
textColor = status ? 'text-green-500' : 'text-red-500';
hasButton = true;
}
} }
return ( return (
<div <div
key={name} key={name}
className={`group m-1 flex items-center gap-0.5 whitespace-normal break-words rounded-full border-2 bg-transparent px-3 text-base font-medium shadow-sm hover:cursor-pointer ${hasButton ? 'hover:pr-1' : '' } ${borderColor} `} className={`group m-1 flex items-center gap-0.5 whitespace-normal break-words rounded-full border-2 bg-transparent px-3 text-base font-medium shadow-sm hover:cursor-pointer ${hasButton ? 'hover:pr-1' : '' } ${borderColor} ${textColor} `}
> >
{name} {name}
{hasButton && <IconButton {hasButton && <IconButton
iconClassName='!w-4' className={`hidden !p-0 px-1 group-hover:block ${textColor}`} src={xIcon} onClick={() => onChangeFilters((prevValue) => { iconClassName='!w-4' className={`hidden !p-0 px-1 group-hover:block ${textColor}`} src={xIcon}
return prevValue.filter((x) => x.name !== name); onClick={handleChangeFilters}
})}
/>} />}
</div> </div>
); );
}; };
export { CreateFilter, PlatformFilters, LanguageFilter, RepliesFilter, MediaFilter, generateFilter }; export { CreateFilter, PlatformFilters, LanguageFilter, ToggleFilter, generateFilter };

Wyświetl plik

@ -385,6 +385,7 @@
"column.explorer.filters.platforms": "Platforms:", "column.explorer.filters.platforms": "Platforms:",
"column.explorer.filters.show_replies": "Show replies:", "column.explorer.filters.show_replies": "Show replies:",
"column.explorer.filters.show_text_posts": "Just text posts:", "column.explorer.filters.show_text_posts": "Just text posts:",
"column.explorer.filters.show_video_posts": "Just posts with video:",
"column.explorer.nostr_card.text": "Wondering about Nostr? <a>Click here</a>", "column.explorer.nostr_card.text": "Wondering about Nostr? <a>Click here</a>",
"column.explorer.nostr_card.title": "Nostr", "column.explorer.nostr_card.title": "Nostr",
"column.explorer.popular_accounts": "Popular Accounts", "column.explorer.popular_accounts": "Popular Accounts",

Wyświetl plik

@ -41,6 +41,7 @@ import profile_hover_card from './profile-hover-card.ts';
import relationships from './relationships.ts'; import relationships from './relationships.ts';
import reports from './reports.ts'; import reports from './reports.ts';
import scheduled_statuses from './scheduled-statuses.ts'; import scheduled_statuses from './scheduled-statuses.ts';
import search_filter from './search-filter.ts';
import search from './search.ts'; import search from './search.ts';
import security from './security.ts'; import security from './security.ts';
import settings from './settings.ts'; import settings from './settings.ts';
@ -98,6 +99,7 @@ export default combineReducers({
reports, reports,
scheduled_statuses, scheduled_statuses,
search, search,
search_filter,
security, security,
settings, settings,
sidebar, sidebar,

Wyświetl plik

@ -1,43 +1,109 @@
import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import { PayloadAction, createSlice } from '@reduxjs/toolkit';
interface IFilters { interface IFilters {
name: string; name: string; // The name of the filter.
state: boolean; status: boolean; // Whether the filter is active or not.
value: string; value: string; // The filter value used for searching.
}
interface IToggle {
type: string; // The filter type to toggle.
checked: boolean; // The new status of the filter.
}
interface INewFilter {
name: string; // The name of the new filter.
status: boolean; // Whether the filter should be active by default.
} }
const initialState: IFilters[] = [ const initialState: IFilters[] = [
{ name: 'Nostr', state: true, value: 'protocol:nostr' }, { name: 'Default', status: false, value: 'language:default' },
{ name: 'Bluesky', state: true, value: 'protocol:atproto' }, { name: 'Nostr', status: true, value: 'protocol:nostr' },
{ name: 'Fediverse', state: true, value: 'protocol:activitypub' }, { name: 'Bluesky', status: true, value: 'protocol:atproto' },
{ name: 'Global', state: false, value: 'language' }, { name: 'Fediverse', status: true, value: 'protocol:activitypub' },
{ name: 'Reply', state: false, value: 'reply:true' }, { name: 'Reply', status: false, value: 'reply:true' },
{ name: 'Media', state: false, value: 'media:true' }, { name: 'Media', status: false, value: 'media:true' },
{ name: 'Video', state: false, value: 'video:true' }, { name: 'Video', status: false, value: 'video:true' },
]; ];
const search_filter = createSlice({ const search_filter = createSlice({
name: 'search_filter', name: 'search_filter',
initialState, initialState,
reducers: { reducers: {
handleToggleReplies: (state, action: PayloadAction<boolean>) => { /**
return state.map((prevEstate) => { * Toggles the status of a filter.
const checked = action.payload; */
return prevEstate.name.toLowerCase() === 'reply' handleToggle: (state, action: PayloadAction<IToggle>) => {
? return state.map((currentState) => {
{ const checked = action.payload.checked;
...prevEstate, const type = action.payload.type.toLowerCase();
state: checked, return currentState.name.toLowerCase() === type
value: `reply:${checked}`, ? {
...currentState,
status: checked,
value: `${type}:${checked}`,
} }
: : currentState;
prevEstate; });
}, },
/**
* Changes the language filter.
*/
changeLanguage: (state, action: PayloadAction<string>) => {
const selected = action.payload.toLowerCase();
return state.map((currentState) =>
currentState.value.includes('language:')
? {
name: selected.toUpperCase(),
status: selected !== 'default',
value: `language:${selected}`,
}
: currentState,
); );
}, },
/**
* Toggles the status of a protocol filter.
*/
selectProtocol: (state, action: PayloadAction<string>) => {
const protocol = action.payload.toLowerCase();
return state.map((currentState) => {
const newStatus = !currentState.status;
if (currentState.name.toLowerCase() !== protocol) return currentState;
return {
...currentState,
status: newStatus,
value: newStatus ? currentState.value.slice(1) : `-${currentState.value}`,
};
});
},
/**
* Creates a new filter.
*/
createFilter: (state, action: PayloadAction<INewFilter>) => {
const filterWords = action.payload.name.trim();
const status = action.payload.status;
const value = status ? filterWords : `-${filterWords.split(' ').join(' -')}`;
return state.some((currentState) => currentState.name === filterWords)
? state
: [...state, { name: filterWords, status: status, value: value }];
},
/**
* Removes a filter.
*/
removeFilter: (state, action: PayloadAction<string>) => {
return state.filter((filter) => filter.name !== action.payload);
},
/**
* Resets the filters to the initial state.
*/
resetFilters: () => initialState, resetFilters: () => initialState,
}, },
}); });
export const { handleToggleReplies, resetFilters } = search_filter.actions; export const { handleToggle, changeLanguage, selectProtocol, createFilter, removeFilter, resetFilters } = search_filter.actions;
export default search_filter.reducer; export default search_filter.reducer;