kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Complete search_filter reducer implementation and refactor code
rodzic
781aaefbe1
commit
bba853893d
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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 };
|
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
Ładowanie…
Reference in New Issue