From b6a8fc435df73918c8732b8b038bd7d2a2b8ffe2 Mon Sep 17 00:00:00 2001 From: daniyal Date: Mon, 7 Oct 2024 15:45:21 +0500 Subject: [PATCH] chore: refactor code for mod filter --- src/components/ModsFilter.tsx | 155 ++++++++++++++++++++ src/hooks/index.ts | 1 + src/hooks/useFilteredMods.ts | 77 ++++++++++ src/pages/game.tsx | 177 ++++------------------- src/pages/mods.tsx | 265 +++------------------------------- src/pages/search.tsx | 223 ++++++++-------------------- src/types/index.ts | 1 + src/types/modsFilter.ts | 25 ++++ 8 files changed, 370 insertions(+), 554 deletions(-) create mode 100644 src/components/ModsFilter.tsx create mode 100644 src/hooks/useFilteredMods.ts create mode 100644 src/types/modsFilter.ts diff --git a/src/components/ModsFilter.tsx b/src/components/ModsFilter.tsx new file mode 100644 index 0000000..26ed98d --- /dev/null +++ b/src/components/ModsFilter.tsx @@ -0,0 +1,155 @@ +import { useAppSelector } from 'hooks' +import React from 'react' +import { Dispatch, SetStateAction } from 'react' +import { FilterOptions, ModeratedFilter, NSFWFilter, SortBy } from 'types' + +type Props = { + filterOptions: FilterOptions + setFilterOptions: Dispatch> +} + +export const ModFilter = React.memo( + ({ filterOptions, setFilterOptions }: Props) => { + const userState = useAppSelector((state) => state.user) + + return ( +
+
+
+
+ + +
+ {Object.values(SortBy).map((item, index) => ( +
+ setFilterOptions((prev) => ({ + ...prev, + sort: item + })) + } + > + {item} +
+ ))} +
+
+
+
+
+ +
+ {Object.values(ModeratedFilter).map((item, index) => { + if (item === ModeratedFilter.Unmoderated_Fully) { + const isAdmin = + userState.user?.npub === + import.meta.env.VITE_REPORTING_NPUB + + if (!isAdmin) return null + } + + return ( +
+ setFilterOptions((prev) => ({ + ...prev, + moderated: item + })) + } + > + {item} +
+ ) + })} +
+
+
+
+
+ +
+ {Object.values(NSFWFilter).map((item, index) => ( +
+ setFilterOptions((prev) => ({ + ...prev, + nsfw: item + })) + } + > + {item} +
+ ))} +
+
+
+
+
+ +
+
+ setFilterOptions((prev) => ({ + ...prev, + source: window.location.host + })) + } + > + Show From: {window.location.host} +
+
+ setFilterOptions((prev) => ({ + ...prev, + source: 'Show All' + })) + } + > + Show All +
+
+
+
+
+
+ ) + } +) diff --git a/src/hooks/index.ts b/src/hooks/index.ts index d654893..70dfe7f 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,5 +1,6 @@ export * from './redux' export * from './useDidMount' +export * from './useFilteredMods' export * from './useGames' export * from './useMuteLists' export * from './useNSFWList' diff --git a/src/hooks/useFilteredMods.ts b/src/hooks/useFilteredMods.ts new file mode 100644 index 0000000..6156727 --- /dev/null +++ b/src/hooks/useFilteredMods.ts @@ -0,0 +1,77 @@ +import { useMemo } from 'react' +import { IUserState } from 'store/reducers/user' +import { + FilterOptions, + ModDetails, + ModeratedFilter, + MuteLists, + NSFWFilter, + SortBy +} from 'types' + +export const useFilteredMods = ( + mods: ModDetails[], + userState: IUserState, + filterOptions: FilterOptions, + nsfwList: string[], + muteLists: { + admin: MuteLists + user: MuteLists + } +) => { + return useMemo(() => { + const nsfwFilter = (mods: ModDetails[]) => { + // Determine the filtering logic based on the NSFW filter option + switch (filterOptions.nsfw) { + case NSFWFilter.Hide_NSFW: + // If 'Hide_NSFW' is selected, filter out NSFW mods + return mods.filter((mod) => !mod.nsfw && !nsfwList.includes(mod.aTag)) + case NSFWFilter.Show_NSFW: + // If 'Show_NSFW' is selected, return all mods (no filtering) + return mods + case NSFWFilter.Only_NSFW: + // If 'Only_NSFW' is selected, filter to show only NSFW mods + return mods.filter((mod) => mod.nsfw || nsfwList.includes(mod.aTag)) + } + } + + let filtered = nsfwFilter(mods) + + const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB + const isUnmoderatedFully = + filterOptions.moderated === ModeratedFilter.Unmoderated_Fully + + // Only apply filtering if the user is not an admin or the admin has not selected "Unmoderated Fully" + if (!(isAdmin && isUnmoderatedFully)) { + filtered = filtered.filter( + (mod) => + !muteLists.admin.authors.includes(mod.author) && + !muteLists.admin.replaceableEvents.includes(mod.aTag) + ) + } + + if (filterOptions.moderated === ModeratedFilter.Moderated) { + filtered = filtered.filter( + (mod) => + !muteLists.user.authors.includes(mod.author) && + !muteLists.user.replaceableEvents.includes(mod.aTag) + ) + } + + if (filterOptions.sort === SortBy.Latest) { + filtered.sort((a, b) => b.published_at - a.published_at) + } else if (filterOptions.sort === SortBy.Oldest) { + filtered.sort((a, b) => a.published_at - b.published_at) + } + + return filtered + }, [ + userState.user?.npub, + filterOptions.sort, + filterOptions.moderated, + filterOptions.nsfw, + mods, + muteLists, + nsfwList + ]) +} diff --git a/src/pages/game.tsx b/src/pages/game.tsx index bbeaef5..6a60ba2 100644 --- a/src/pages/game.tsx +++ b/src/pages/game.tsx @@ -1,50 +1,40 @@ import { LoadingSpinner } from 'components/LoadingSpinner' import { ModCard } from 'components/ModCard' +import { ModFilter } from 'components/ModsFilter' import { PaginationWithPageNumbers } from 'components/Pagination' import { MAX_MODS_PER_PAGE, T_TAG_VALUE } from 'constants.ts' import { RelayController } from 'controllers' -import { useAppSelector, useMuteLists } from 'hooks' +import { + useAppSelector, + useFilteredMods, + useMuteLists, + useNSFWList +} from 'hooks' import { Filter, kinds } from 'nostr-tools' import { Subscription } from 'nostr-tools/abstract-relay' -import React, { - Dispatch, - SetStateAction, - useEffect, - useMemo, - useRef, - useState -} from 'react' +import { useEffect, useRef, useState } from 'react' import { useParams } from 'react-router-dom' import { toast } from 'react-toastify' -import { ModDetails } from 'types' +import { + FilterOptions, + ModDetails, + ModeratedFilter, + NSFWFilter, + SortBy +} from 'types' import { extractModData, isModDataComplete, log, LogType } from 'utils' -enum SortByEnum { - Latest = 'Latest', - Oldest = 'Oldest', - Best_Rated = 'Best Rated', - Worst_Rated = 'Worst Rated' -} - -enum ModeratedFilterEnum { - Moderated = 'Moderated', - Unmoderated = 'Unmoderated', - Unmoderated_Fully = 'Unmoderated Fully' -} - -interface FilterOptions { - sort: SortByEnum - moderated: ModeratedFilterEnum -} - export const GamePage = () => { const params = useParams() const { name: gameName } = params const muteLists = useMuteLists() + const nsfwList = useNSFWList() const [filterOptions, setFilterOptions] = useState({ - sort: SortByEnum.Latest, - moderated: ModeratedFilterEnum.Moderated + sort: SortBy.Latest, + nsfw: NSFWFilter.Hide_NSFW, + source: window.location.host, + moderated: ModeratedFilter.Moderated }) const [mods, setMods] = useState([]) @@ -54,43 +44,13 @@ export const GamePage = () => { const userState = useAppSelector((state) => state.user) - const filteredMods = useMemo(() => { - let filtered: ModDetails[] = [...mods] - const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB - const isUnmoderatedFully = - filterOptions.moderated === ModeratedFilterEnum.Unmoderated_Fully - - // Only apply filtering if the user is not an admin or the admin has not selected "Unmoderated Fully" - if (!(isAdmin && isUnmoderatedFully)) { - filtered = filtered.filter( - (mod) => - !muteLists.admin.authors.includes(mod.author) && - !muteLists.admin.replaceableEvents.includes(mod.aTag) - ) - } - - if (filterOptions.moderated === ModeratedFilterEnum.Moderated) { - filtered = filtered.filter( - (mod) => - !muteLists.user.authors.includes(mod.author) && - !muteLists.user.replaceableEvents.includes(mod.aTag) - ) - } - - if (filterOptions.sort === SortByEnum.Latest) { - filtered.sort((a, b) => b.published_at - a.published_at) - } else if (filterOptions.sort === SortByEnum.Oldest) { - filtered.sort((a, b) => a.published_at - b.published_at) - } - - return filtered - }, [ + const filteredMods = useFilteredMods( mods, - userState.user?.npub, - filterOptions.sort, - filterOptions.moderated, + userState, + filterOptions, + nsfwList, muteLists - ]) + ) // Pagination logic const totalGames = filteredMods.length @@ -172,7 +132,7 @@ export const GamePage = () => { - @@ -194,88 +154,3 @@ export const GamePage = () => { ) } - -type FiltersProps = { - filterOptions: FilterOptions - setFilterOptions: Dispatch> -} - -const Filters = React.memo( - ({ filterOptions, setFilterOptions }: FiltersProps) => { - const userState = useAppSelector((state) => state.user) - - return ( -
-
-
-
- - -
- {Object.values(SortByEnum).map((item, index) => ( -
- setFilterOptions((prev) => ({ - ...prev, - sort: item - })) - } - > - {item} -
- ))} -
-
-
-
-
- -
- {Object.values(ModeratedFilterEnum).map((item, index) => { - if (item === ModeratedFilterEnum.Unmoderated_Fully) { - const isAdmin = - userState.user?.npub === - import.meta.env.VITE_REPORTING_NPUB - - if (!isAdmin) return null - } - - return ( -
- setFilterOptions((prev) => ({ - ...prev, - moderated: item - })) - } - > - {item} -
- ) - })} -
-
-
-
-
- ) - } -) diff --git a/src/pages/mods.tsx b/src/pages/mods.tsx index 87157bc..760d449 100644 --- a/src/pages/mods.tsx +++ b/src/pages/mods.tsx @@ -1,52 +1,30 @@ +import { ModFilter } from 'components/ModsFilter' import { Pagination } from 'components/Pagination' -import React, { - Dispatch, - SetStateAction, - useCallback, - useEffect, - useMemo, - useRef, - useState -} from 'react' +import React, { useCallback, useEffect, useRef, useState } from 'react' import { createSearchParams, useNavigate } from 'react-router-dom' import { LoadingSpinner } from '../components/LoadingSpinner' import { ModCard } from '../components/ModCard' import { MOD_FILTER_LIMIT } from '../constants' -import { useAppSelector, useMuteLists, useNSFWList } from '../hooks' +import { + useAppSelector, + useFilteredMods, + useMuteLists, + useNSFWList +} from '../hooks' import { appRoutes } from '../routes' import '../styles/filters.css' import '../styles/pagination.css' import '../styles/search.css' import '../styles/styles.css' -import { ModDetails } from '../types' +import { + FilterOptions, + ModDetails, + ModeratedFilter, + NSFWFilter, + SortBy +} from '../types' import { fetchMods } from '../utils' -enum SortBy { - Latest = 'Latest', - Oldest = 'Oldest', - Best_Rated = 'Best Rated', - Worst_Rated = 'Worst Rated' -} - -enum NSFWFilter { - Hide_NSFW = 'Hide NSFW', - Show_NSFW = 'Show NSFW', - Only_NSFW = 'Only NSFW' -} - -enum ModeratedFilter { - Moderated = 'Moderated', - Unmoderated = 'Unmoderated', - Unmoderated_Fully = 'Unmoderated Fully' -} - -interface FilterOptions { - sort: SortBy - nsfw: NSFWFilter - source: string - moderated: ModeratedFilter -} - export const ModsPage = () => { const [isFetching, setIsFetching] = useState(false) const [mods, setMods] = useState([]) @@ -111,61 +89,13 @@ export const ModsPage = () => { }) }, [filterOptions.source, mods]) - const filteredModList = useMemo(() => { - const nsfwFilter = (mods: ModDetails[]) => { - // Determine the filtering logic based on the NSFW filter option - switch (filterOptions.nsfw) { - case NSFWFilter.Hide_NSFW: - // If 'Hide_NSFW' is selected, filter out NSFW mods - return mods.filter((mod) => !mod.nsfw && !nsfwList.includes(mod.aTag)) - case NSFWFilter.Show_NSFW: - // If 'Show_NSFW' is selected, return all mods (no filtering) - return mods - case NSFWFilter.Only_NSFW: - // If 'Only_NSFW' is selected, filter to show only NSFW mods - return mods.filter((mod) => mod.nsfw || nsfwList.includes(mod.aTag)) - } - } - - let filtered = nsfwFilter(mods) - - const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB - const isUnmoderatedFully = - filterOptions.moderated === ModeratedFilter.Unmoderated_Fully - - // Only apply filtering if the user is not an admin or the admin has not selected "Unmoderated Fully" - if (!(isAdmin && isUnmoderatedFully)) { - filtered = filtered.filter( - (mod) => - !muteLists.admin.authors.includes(mod.author) && - !muteLists.admin.replaceableEvents.includes(mod.aTag) - ) - } - - if (filterOptions.moderated === ModeratedFilter.Moderated) { - filtered = filtered.filter( - (mod) => - !muteLists.user.authors.includes(mod.author) && - !muteLists.user.replaceableEvents.includes(mod.aTag) - ) - } - - if (filterOptions.sort === SortBy.Latest) { - filtered.sort((a, b) => b.published_at - a.published_at) - } else if (filterOptions.sort === SortBy.Oldest) { - filtered.sort((a, b) => a.published_at - b.published_at) - } - - return filtered - }, [ - userState.user?.npub, - filterOptions.sort, - filterOptions.moderated, - filterOptions.nsfw, + const filteredModList = useFilteredMods( mods, - muteLists, - nsfwList - ]) + userState, + filterOptions, + nsfwList, + muteLists + ) return ( <> @@ -174,7 +104,7 @@ export const ModsPage = () => {
- @@ -261,154 +191,3 @@ const PageTitleRow = React.memo(() => {
) }) - -type FiltersProps = { - filterOptions: FilterOptions - setFilterOptions: Dispatch> -} - -const Filters = React.memo( - ({ filterOptions, setFilterOptions }: FiltersProps) => { - const userState = useAppSelector((state) => state.user) - - return ( -
-
-
-
- - -
- {Object.values(SortBy).map((item, index) => ( -
- setFilterOptions((prev) => ({ - ...prev, - sort: item - })) - } - > - {item} -
- ))} -
-
-
-
-
- -
- {Object.values(ModeratedFilter).map((item, index) => { - if (item === ModeratedFilter.Unmoderated_Fully) { - const isAdmin = - userState.user?.npub === - import.meta.env.VITE_REPORTING_NPUB - - if (!isAdmin) return null - } - - return ( -
- setFilterOptions((prev) => ({ - ...prev, - moderated: item - })) - } - > - {item} -
- ) - })} -
-
-
-
-
- -
- {Object.values(NSFWFilter).map((item, index) => ( -
- setFilterOptions((prev) => ({ - ...prev, - nsfw: item - })) - } - > - {item} -
- ))} -
-
-
-
-
- -
-
- setFilterOptions((prev) => ({ - ...prev, - source: window.location.host - })) - } - > - Show From: {window.location.host} -
-
- setFilterOptions((prev) => ({ - ...prev, - source: 'Show All' - })) - } - > - Show All -
-
-
-
-
-
- ) - } -) diff --git a/src/pages/search.tsx b/src/pages/search.tsx index 3c01ef0..7b60cb2 100644 --- a/src/pages/search.tsx +++ b/src/pages/search.tsx @@ -3,6 +3,7 @@ import { ErrorBoundary } from 'components/ErrorBoundary' import { GameCard } from 'components/GameCard' import { LoadingSpinner } from 'components/LoadingSpinner' import { ModCard } from 'components/ModCard' +import { ModFilter } from 'components/ModsFilter' import { Pagination } from 'components/Pagination' import { Profile } from 'components/ProfileSection' import { @@ -11,7 +12,13 @@ import { T_TAG_VALUE } from 'constants.ts' import { RelayController } from 'controllers' -import { useAppSelector, useGames, useMuteLists } from 'hooks' +import { + useAppSelector, + useFilteredMods, + useGames, + useMuteLists, + useNSFWList +} from 'hooks' import { Filter, kinds } from 'nostr-tools' import { Subscription } from 'nostr-tools/abstract-relay' import React, { @@ -24,48 +31,40 @@ import React, { } from 'react' import { useSearchParams } from 'react-router-dom' import { toast } from 'react-toastify' -import { ModDetails, MuteLists } from 'types' +import { + FilterOptions, + ModDetails, + ModeratedFilter, + MuteLists, + NSFWFilter, + SortBy +} from 'types' import { extractModData, isModDataComplete, log, LogType } from 'utils' -enum SortByEnum { - Latest = 'Latest', - Oldest = 'Oldest', - Best_Rated = 'Best Rated', - Worst_Rated = 'Worst Rated' -} - -enum ModeratedFilterEnum { - Moderated = 'Moderated', - Unmoderated = 'Unmoderated', - Unmoderated_Fully = 'Unmoderated Fully' -} - -enum SearchingFilterEnum { +enum SearchKindEnum { Mods = 'Mods', Games = 'Games', Users = 'Users' } -interface FilterOptions { - sort: SortByEnum - moderated: ModeratedFilterEnum - searching: SearchingFilterEnum - source: string -} - export const SearchPage = () => { const [searchParams] = useSearchParams() const muteLists = useMuteLists() + const nsfwList = useNSFWList() const searchTermRef = useRef(null) + + const [searchKind, setSearchKind] = useState( + (searchParams.get('searching') as SearchKindEnum) || SearchKindEnum.Mods + ) + const [filterOptions, setFilterOptions] = useState({ - sort: SortByEnum.Latest, - moderated: ModeratedFilterEnum.Moderated, + sort: SortBy.Latest, + nsfw: NSFWFilter.Hide_NSFW, source: window.location.host, - searching: - (searchParams.get('searching') as SearchingFilterEnum) || - SearchingFilterEnum.Mods + moderated: ModeratedFilter.Moderated }) + const [searchTerm, setSearchTerm] = useState( searchParams.get('searchTerm') || '' ) @@ -129,22 +128,25 @@ export const SearchPage = () => { - {filterOptions.searching === SearchingFilterEnum.Mods && ( + {searchKind === SearchKindEnum.Mods && ( )} - {filterOptions.searching === SearchingFilterEnum.Users && ( + {searchKind === SearchKindEnum.Users && ( )} - {filterOptions.searching === SearchingFilterEnum.Games && ( + {searchKind === SearchKindEnum.Games && ( )}
@@ -156,49 +158,30 @@ export const SearchPage = () => { type FiltersProps = { filterOptions: FilterOptions setFilterOptions: Dispatch> + searchKind: SearchKindEnum + setSearchKind: Dispatch> } const Filters = React.memo( - ({ filterOptions, setFilterOptions }: FiltersProps) => { + ({ + filterOptions, + setFilterOptions, + searchKind, + setSearchKind + }: FiltersProps) => { const userState = useAppSelector((state) => state.user) return (
- {filterOptions.searching === SearchingFilterEnum.Mods && ( -
-
- - -
- {Object.values(SortByEnum).map((item, index) => ( -
- setFilterOptions((prev) => ({ - ...prev, - sort: item - })) - } - > - {item} -
- ))} -
-
-
+ {searchKind === SearchKindEnum.Mods && ( + )} - {(filterOptions.searching === SearchingFilterEnum.Mods || - filterOptions.searching === SearchingFilterEnum.Users) && ( + {searchKind === SearchKindEnum.Users && (
-
-
- setFilterOptions((prev) => ({ - ...prev, - source: window.location.host - })) - } - > - Show From: {window.location.host} -
-
- setFilterOptions((prev) => ({ - ...prev, - source: 'Show All' - })) - } - > - Show All -
-
-
-
- )} -
- {Object.values(SearchingFilterEnum).map((item, index) => ( + {Object.values(SearchKindEnum).map((item, index) => (
- setFilterOptions((prev) => ({ - ...prev, - searching: item - })) - } + onClick={() => setSearchKind(item)} > {item}
@@ -321,12 +258,14 @@ type ModsResultProps = { admin: MuteLists user: MuteLists } + nsfwList: string[] } const ModsResult = ({ filterOptions, searchTerm, - muteLists + muteLists, + nsfwList }: ModsResultProps) => { const hasEffectRun = useRef(false) const [isSubscribing, setIsSubscribing] = useState(false) @@ -400,49 +339,13 @@ const ModsResult = ({ return mods.filter(filterFn) }, [mods, searchTerm]) - const filteredModList = useMemo(() => { - let filtered: ModDetails[] = [...filteredMods] - - if (filterOptions.source === window.location.host) { - filtered = filtered.filter((mod) => mod.rTag === window.location.host) - } - - const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB - const isUnmoderatedFully = - filterOptions.moderated === ModeratedFilterEnum.Unmoderated_Fully - - // Only apply filtering if the user is not an admin or the admin has not selected "Unmoderated Fully" - if (!(isAdmin && isUnmoderatedFully)) { - filtered = filtered.filter( - (mod) => - !muteLists.admin.authors.includes(mod.author) && - !muteLists.admin.replaceableEvents.includes(mod.aTag) - ) - } - - if (filterOptions.moderated === ModeratedFilterEnum.Moderated) { - filtered = filtered.filter( - (mod) => - !muteLists.user.authors.includes(mod.author) && - !muteLists.user.replaceableEvents.includes(mod.aTag) - ) - } - - if (filterOptions.sort === SortByEnum.Latest) { - filtered.sort((a, b) => b.published_at - a.published_at) - } else if (filterOptions.sort === SortByEnum.Oldest) { - filtered.sort((a, b) => a.published_at - b.published_at) - } - - return filtered - }, [ + const filteredModList = useFilteredMods( filteredMods, - userState.user?.npub, - filterOptions.sort, - filterOptions.moderated, - filterOptions.source, + userState, + filterOptions, + nsfwList, muteLists - ]) + ) const handleNext = () => { setPage((prev) => prev + 1) @@ -478,7 +381,7 @@ const ModsResult = ({ type UsersResultProps = { searchTerm: string - moderationFilter: ModeratedFilterEnum + moderationFilter: ModeratedFilter muteLists: { admin: MuteLists user: MuteLists @@ -528,7 +431,7 @@ const UsersResult = ({ let filtered = [...profiles] const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB const isUnmoderatedFully = - moderationFilter === ModeratedFilterEnum.Unmoderated_Fully + moderationFilter === ModeratedFilter.Unmoderated_Fully // Only apply filtering if the user is not an admin or the admin has not selected "Unmoderated Fully" if (!(isAdmin && isUnmoderatedFully)) { @@ -537,7 +440,7 @@ const UsersResult = ({ ) } - if (moderationFilter === ModeratedFilterEnum.Moderated) { + if (moderationFilter === ModeratedFilter.Moderated) { filtered = filtered.filter( (profile) => !muteLists.user.authors.includes(profile.pubkey as string) ) diff --git a/src/types/index.ts b/src/types/index.ts index 7b04179..c589a79 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,4 +1,5 @@ export * from './mod' +export * from './modsFilter' export * from './nostr' export * from './user' export * from './zap' diff --git a/src/types/modsFilter.ts b/src/types/modsFilter.ts new file mode 100644 index 0000000..8d724ec --- /dev/null +++ b/src/types/modsFilter.ts @@ -0,0 +1,25 @@ +export enum SortBy { + Latest = 'Latest', + Oldest = 'Oldest', + Best_Rated = 'Best Rated', + Worst_Rated = 'Worst Rated' +} + +export enum NSFWFilter { + Hide_NSFW = 'Hide NSFW', + Show_NSFW = 'Show NSFW', + Only_NSFW = 'Only NSFW' +} + +export enum ModeratedFilter { + Moderated = 'Moderated', + Unmoderated = 'Unmoderated', + Unmoderated_Fully = 'Unmoderated Fully' +} + +export interface FilterOptions { + sort: SortBy + nsfw: NSFWFilter + source: string + moderated: ModeratedFilter +}