feat: add filtering, split mods and blog filters
This commit is contained in:
parent
376164cbf4
commit
b1d578c329
165
src/components/Filters/BlogsFilter.tsx
Normal file
165
src/components/Filters/BlogsFilter.tsx
Normal file
@ -0,0 +1,165 @@
|
||||
import { useAppSelector, useLocalStorage } from 'hooks'
|
||||
import React from 'react'
|
||||
import {
|
||||
FilterOptions,
|
||||
ModeratedFilter,
|
||||
NSFWFilter,
|
||||
SortBy,
|
||||
WOTFilterOptions
|
||||
} from 'types'
|
||||
import { DEFAULT_FILTER_OPTIONS } from 'utils'
|
||||
import { Dropdown } from './Dropdown'
|
||||
import { Option } from './Option'
|
||||
import { Filter } from '.'
|
||||
|
||||
type Props = {
|
||||
author?: string | undefined
|
||||
filterKey?: string | undefined
|
||||
}
|
||||
|
||||
export const BlogsFilter = React.memo(
|
||||
({ author, filterKey = 'filter-blog' }: Props) => {
|
||||
const userState = useAppSelector((state) => state.user)
|
||||
const [filterOptions, setFilterOptions] = useLocalStorage<FilterOptions>(
|
||||
filterKey,
|
||||
DEFAULT_FILTER_OPTIONS
|
||||
)
|
||||
|
||||
return (
|
||||
<Filter>
|
||||
{/* sort filter options */}
|
||||
<Dropdown label={filterOptions.sort}>
|
||||
{Object.values(SortBy).map((item, index) => (
|
||||
<div
|
||||
key={`sortByItem-${index}`}
|
||||
className='dropdown-item dropdownMainMenuItem'
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
sort: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</Dropdown>
|
||||
|
||||
{/* moderation filter options */}
|
||||
<Dropdown label={filterOptions.moderated}>
|
||||
{Object.values(ModeratedFilter).map((item) => {
|
||||
if (item === ModeratedFilter.Unmoderated_Fully) {
|
||||
const isAdmin =
|
||||
userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB
|
||||
|
||||
const isOwnProfile =
|
||||
author && userState.auth && userState.user?.pubkey === author
|
||||
|
||||
if (!(isAdmin || isOwnProfile)) return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Option
|
||||
key={`sort-${item}`}
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
moderated: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</Option>
|
||||
)
|
||||
})}
|
||||
</Dropdown>
|
||||
|
||||
{/* wot filter options */}
|
||||
<Dropdown label={<>Trust: {filterOptions.wot}</>}>
|
||||
{Object.values(WOTFilterOptions).map((item, index) => {
|
||||
// when user is not logged in
|
||||
if (item === WOTFilterOptions.Site_And_Mine && !userState.auth) {
|
||||
return null
|
||||
}
|
||||
|
||||
// when logged in user not admin
|
||||
if (
|
||||
item === WOTFilterOptions.None ||
|
||||
item === WOTFilterOptions.Mine_Only ||
|
||||
item === WOTFilterOptions.Exclude
|
||||
) {
|
||||
const isWoTNpub =
|
||||
userState.user?.npub === import.meta.env.VITE_SITE_WOT_NPUB
|
||||
|
||||
const isOwnProfile =
|
||||
author && userState.auth && userState.user?.pubkey === author
|
||||
|
||||
if (!(isWoTNpub || isOwnProfile)) return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Option
|
||||
key={`wotFilterOption-${index}`}
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
wot: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</Option>
|
||||
)
|
||||
})}
|
||||
</Dropdown>
|
||||
|
||||
{/* nsfw filter options */}
|
||||
<Dropdown label={filterOptions.nsfw}>
|
||||
{Object.values(NSFWFilter).map((item, index) => (
|
||||
<Option
|
||||
key={`nsfwFilterItem-${index}`}
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
nsfw: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</Option>
|
||||
))}
|
||||
</Dropdown>
|
||||
|
||||
{/* source filter options */}
|
||||
<Dropdown
|
||||
label={
|
||||
filterOptions.source === window.location.host
|
||||
? `Show From: ${filterOptions.source}`
|
||||
: 'Show All'
|
||||
}
|
||||
>
|
||||
<Option
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
source: window.location.host
|
||||
}))
|
||||
}
|
||||
>
|
||||
Show From: {window.location.host}
|
||||
</Option>
|
||||
<Option
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
source: 'Show All'
|
||||
}))
|
||||
}
|
||||
>
|
||||
Show All
|
||||
</Option>
|
||||
</Dropdown>
|
||||
</Filter>
|
||||
)
|
||||
}
|
||||
)
|
25
src/components/Filters/Dropdown.tsx
Normal file
25
src/components/Filters/Dropdown.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { PropsWithChildren } from 'react'
|
||||
|
||||
interface DropdownProps {
|
||||
label: React.ReactNode
|
||||
}
|
||||
export const Dropdown = ({
|
||||
label,
|
||||
children
|
||||
}: PropsWithChildren<DropdownProps>) => {
|
||||
return (
|
||||
<div className='FiltersMainElement'>
|
||||
<div className='dropdown dropdownMain'>
|
||||
<button
|
||||
className='btn dropdown-toggle btnMain btnMainDropdown'
|
||||
aria-expanded='false'
|
||||
data-bs-toggle='dropdown'
|
||||
type='button'
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
<div className='dropdown-menu dropdownMainMenu'>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
182
src/components/Filters/ModsFilter.tsx
Normal file
182
src/components/Filters/ModsFilter.tsx
Normal file
@ -0,0 +1,182 @@
|
||||
import { useAppSelector, useLocalStorage } from 'hooks'
|
||||
import React from 'react'
|
||||
import {
|
||||
FilterOptions,
|
||||
SortBy,
|
||||
ModeratedFilter,
|
||||
WOTFilterOptions,
|
||||
NSFWFilter,
|
||||
RepostFilter
|
||||
} from 'types'
|
||||
import { DEFAULT_FILTER_OPTIONS } from 'utils'
|
||||
import { Filter } from '.'
|
||||
import { Dropdown } from './Dropdown'
|
||||
import { Option } from './Option'
|
||||
|
||||
type Props = {
|
||||
author?: string | undefined
|
||||
filterKey?: string | undefined
|
||||
}
|
||||
|
||||
export const ModFilter = React.memo(
|
||||
({ author, filterKey = 'filter' }: Props) => {
|
||||
const userState = useAppSelector((state) => state.user)
|
||||
const [filterOptions, setFilterOptions] = useLocalStorage<FilterOptions>(
|
||||
filterKey,
|
||||
DEFAULT_FILTER_OPTIONS
|
||||
)
|
||||
|
||||
return (
|
||||
<Filter>
|
||||
{/* sort filter options */}
|
||||
<Dropdown label={filterOptions.sort}>
|
||||
{Object.values(SortBy).map((item, index) => (
|
||||
<Option
|
||||
key={`sortByItem-${index}`}
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
sort: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</Option>
|
||||
))}
|
||||
</Dropdown>
|
||||
|
||||
{/* moderation filter options */}
|
||||
<Dropdown label={filterOptions.moderated}>
|
||||
{Object.values(ModeratedFilter).map((item, index) => {
|
||||
if (item === ModeratedFilter.Unmoderated_Fully) {
|
||||
const isAdmin =
|
||||
userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB
|
||||
|
||||
const isOwnProfile =
|
||||
author && userState.auth && userState.user?.pubkey === author
|
||||
|
||||
if (!(isAdmin || isOwnProfile)) return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Option
|
||||
key={`moderatedFilterItem-${index}`}
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
moderated: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</Option>
|
||||
)
|
||||
})}
|
||||
</Dropdown>
|
||||
|
||||
{/* wot filter options */}
|
||||
<Dropdown label={<>Trust: {filterOptions.wot}</>}>
|
||||
{Object.values(WOTFilterOptions).map((item, index) => {
|
||||
// when user is not logged in
|
||||
if (item === WOTFilterOptions.Site_And_Mine && !userState.auth) {
|
||||
return null
|
||||
}
|
||||
|
||||
// when logged in user not admin
|
||||
if (
|
||||
item === WOTFilterOptions.None ||
|
||||
item === WOTFilterOptions.Mine_Only ||
|
||||
item === WOTFilterOptions.Exclude
|
||||
) {
|
||||
const isWoTNpub =
|
||||
userState.user?.npub === import.meta.env.VITE_SITE_WOT_NPUB
|
||||
|
||||
const isOwnProfile =
|
||||
author && userState.auth && userState.user?.pubkey === author
|
||||
|
||||
if (!(isWoTNpub || isOwnProfile)) return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Option
|
||||
key={`wotFilterOption-${index}`}
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
wot: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</Option>
|
||||
)
|
||||
})}
|
||||
</Dropdown>
|
||||
|
||||
{/* nsfw filter options */}
|
||||
<Dropdown label={filterOptions.nsfw}>
|
||||
{Object.values(NSFWFilter).map((item, index) => (
|
||||
<Option
|
||||
key={`nsfwFilterItem-${index}`}
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
nsfw: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</Option>
|
||||
))}
|
||||
</Dropdown>
|
||||
|
||||
{/* repost filter options */}
|
||||
<Dropdown label={filterOptions.repost}>
|
||||
{Object.values(RepostFilter).map((item, index) => (
|
||||
<Option
|
||||
key={`repostFilterItem-${index}`}
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
repost: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</Option>
|
||||
))}
|
||||
</Dropdown>
|
||||
|
||||
{/* source filter options */}
|
||||
<Dropdown
|
||||
label={
|
||||
filterOptions.source === window.location.host
|
||||
? `Show From: ${filterOptions.source}`
|
||||
: 'Show All'
|
||||
}
|
||||
>
|
||||
<Option
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
source: window.location.host
|
||||
}))
|
||||
}
|
||||
>
|
||||
Show From: {window.location.host}
|
||||
</Option>
|
||||
<Option
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
source: 'Show All'
|
||||
}))
|
||||
}
|
||||
>
|
||||
Show All
|
||||
</Option>
|
||||
</Dropdown>
|
||||
</Filter>
|
||||
)
|
||||
}
|
||||
)
|
16
src/components/Filters/Option.tsx
Normal file
16
src/components/Filters/Option.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { PropsWithChildren } from 'react'
|
||||
|
||||
interface OptionProps {
|
||||
onClick: React.MouseEventHandler<HTMLDivElement>
|
||||
}
|
||||
|
||||
export const Option = ({
|
||||
onClick,
|
||||
children
|
||||
}: PropsWithChildren<OptionProps>) => {
|
||||
return (
|
||||
<div className='dropdown-item dropdownMainMenuItem' onClick={onClick}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
9
src/components/Filters/index.tsx
Normal file
9
src/components/Filters/index.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { PropsWithChildren } from 'react'
|
||||
|
||||
export const Filter = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<div className='IBMSecMain'>
|
||||
<div className='FiltersMain'>{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,235 +0,0 @@
|
||||
import { useAppSelector, useLocalStorage } from 'hooks'
|
||||
import React from 'react'
|
||||
import {
|
||||
FilterOptions,
|
||||
ModeratedFilter,
|
||||
NSFWFilter,
|
||||
SortBy,
|
||||
WOTFilterOptions
|
||||
} from 'types'
|
||||
import { DEFAULT_FILTER_OPTIONS } from 'utils'
|
||||
|
||||
type Props = {
|
||||
author?: string | undefined
|
||||
filterKey?: string | undefined
|
||||
}
|
||||
|
||||
export const ModFilter = React.memo(
|
||||
({ author, filterKey = 'filter' }: Props) => {
|
||||
const userState = useAppSelector((state) => state.user)
|
||||
const [filterOptions, setFilterOptions] = useLocalStorage<FilterOptions>(
|
||||
filterKey,
|
||||
DEFAULT_FILTER_OPTIONS
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='IBMSecMain'>
|
||||
<div className='FiltersMain'>
|
||||
{/* sort filter options */}
|
||||
<div className='FiltersMainElement'>
|
||||
<div className='dropdown dropdownMain'>
|
||||
<button
|
||||
className='btn dropdown-toggle btnMain btnMainDropdown'
|
||||
aria-expanded='false'
|
||||
data-bs-toggle='dropdown'
|
||||
type='button'
|
||||
>
|
||||
{filterOptions.sort}
|
||||
</button>
|
||||
|
||||
<div className='dropdown-menu dropdownMainMenu'>
|
||||
{Object.values(SortBy).map((item, index) => (
|
||||
<div
|
||||
key={`sortByItem-${index}`}
|
||||
className='dropdown-item dropdownMainMenuItem'
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
sort: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* moderation filter options */}
|
||||
<div className='FiltersMainElement'>
|
||||
<div className='dropdown dropdownMain'>
|
||||
<button
|
||||
className='btn dropdown-toggle btnMain btnMainDropdown'
|
||||
aria-expanded='false'
|
||||
data-bs-toggle='dropdown'
|
||||
type='button'
|
||||
>
|
||||
{filterOptions.moderated}
|
||||
</button>
|
||||
<div className='dropdown-menu dropdownMainMenu'>
|
||||
{Object.values(ModeratedFilter).map((item, index) => {
|
||||
if (item === ModeratedFilter.Unmoderated_Fully) {
|
||||
const isAdmin =
|
||||
userState.user?.npub ===
|
||||
import.meta.env.VITE_REPORTING_NPUB
|
||||
|
||||
const isOwnProfile =
|
||||
author &&
|
||||
userState.auth &&
|
||||
userState.user?.pubkey === author
|
||||
|
||||
if (!(isAdmin || isOwnProfile)) return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`moderatedFilterItem-${index}`}
|
||||
className='dropdown-item dropdownMainMenuItem'
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
moderated: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* wot filter options */}
|
||||
<div className='FiltersMainElement'>
|
||||
<div className='dropdown dropdownMain'>
|
||||
<button
|
||||
className='btn dropdown-toggle btnMain btnMainDropdown'
|
||||
aria-expanded='false'
|
||||
data-bs-toggle='dropdown'
|
||||
type='button'
|
||||
>
|
||||
Trust: {filterOptions.wot}
|
||||
</button>
|
||||
<div className='dropdown-menu dropdownMainMenu'>
|
||||
{Object.values(WOTFilterOptions).map((item, index) => {
|
||||
// when user is not logged in
|
||||
if (
|
||||
item === WOTFilterOptions.Site_And_Mine &&
|
||||
!userState.auth
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
// when logged in user not admin
|
||||
if (
|
||||
item === WOTFilterOptions.None ||
|
||||
item === WOTFilterOptions.Mine_Only ||
|
||||
item === WOTFilterOptions.Exclude
|
||||
) {
|
||||
const isWoTNpub =
|
||||
userState.user?.npub ===
|
||||
import.meta.env.VITE_SITE_WOT_NPUB
|
||||
|
||||
const isOwnProfile =
|
||||
author &&
|
||||
userState.auth &&
|
||||
userState.user?.pubkey === author
|
||||
|
||||
if (!(isWoTNpub || isOwnProfile)) return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`wotFilterOption-${index}`}
|
||||
className='dropdown-item dropdownMainMenuItem'
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
wot: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* nsfw filter options */}
|
||||
<div className='FiltersMainElement'>
|
||||
<div className='dropdown dropdownMain'>
|
||||
<button
|
||||
className='btn dropdown-toggle btnMain btnMainDropdown'
|
||||
aria-expanded='false'
|
||||
data-bs-toggle='dropdown'
|
||||
type='button'
|
||||
>
|
||||
{filterOptions.nsfw}
|
||||
</button>
|
||||
<div className='dropdown-menu dropdownMainMenu'>
|
||||
{Object.values(NSFWFilter).map((item, index) => (
|
||||
<div
|
||||
key={`nsfwFilterItem-${index}`}
|
||||
className='dropdown-item dropdownMainMenuItem'
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
nsfw: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* source filter options */}
|
||||
<div className='FiltersMainElement'>
|
||||
<div className='dropdown dropdownMain'>
|
||||
<button
|
||||
className='btn dropdown-toggle btnMain btnMainDropdown'
|
||||
aria-expanded='false'
|
||||
data-bs-toggle='dropdown'
|
||||
type='button'
|
||||
>
|
||||
{filterOptions.source === window.location.host
|
||||
? `Show From: ${filterOptions.source}`
|
||||
: 'Show All'}
|
||||
</button>
|
||||
<div className='dropdown-menu dropdownMainMenu'>
|
||||
<div
|
||||
className='dropdown-item dropdownMainMenuItem'
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
source: window.location.host
|
||||
}))
|
||||
}
|
||||
>
|
||||
Show From: {window.location.host}
|
||||
</div>
|
||||
<div
|
||||
className='dropdown-item dropdownMainMenuItem'
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
source: 'Show All'
|
||||
}))
|
||||
}
|
||||
>
|
||||
Show All
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
@ -6,6 +6,7 @@ import {
|
||||
ModeratedFilter,
|
||||
MuteLists,
|
||||
NSFWFilter,
|
||||
RepostFilter,
|
||||
SortBy,
|
||||
WOTFilterOptions
|
||||
} from 'types'
|
||||
@ -22,6 +23,7 @@ export const useFilteredMods = (
|
||||
admin: MuteLists
|
||||
user: MuteLists
|
||||
},
|
||||
repostList: string[],
|
||||
author?: string | undefined
|
||||
) => {
|
||||
const { siteWot, siteWotLevel, userWot, userWotLevel } = useAppSelector(
|
||||
@ -53,6 +55,30 @@ export const useFilteredMods = (
|
||||
}
|
||||
}
|
||||
|
||||
const repostFilter = (mods: ModDetails[]) => {
|
||||
if (filterOptions.repost !== RepostFilter.Hide_Repost) {
|
||||
// Add repost tag to mods included in repostList
|
||||
mods = mods.map((mod) => {
|
||||
return !mod.repost && repostList.includes(mod.aTag)
|
||||
? { ...mod, repost: true }
|
||||
: mod
|
||||
})
|
||||
}
|
||||
// Determine the filtering logic based on the Repost filter option
|
||||
switch (filterOptions.repost) {
|
||||
case RepostFilter.Hide_Repost:
|
||||
return mods.filter(
|
||||
(mod) => !mod.repost && !repostList.includes(mod.aTag)
|
||||
)
|
||||
case RepostFilter.Show_Repost:
|
||||
return mods
|
||||
case RepostFilter.Only_Repost:
|
||||
return mods.filter(
|
||||
(mod) => mod.repost || repostList.includes(mod.aTag)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const wotFilter = (mods: ModDetails[]) => {
|
||||
// Determine the filtering logic based on the WOT filter option and user state
|
||||
// when user is not logged in use Site_Only
|
||||
@ -93,7 +119,7 @@ export const useFilteredMods = (
|
||||
}
|
||||
|
||||
let filtered = nsfwFilter(mods)
|
||||
|
||||
filtered = repostFilter(filtered)
|
||||
filtered = wotFilter(filtered)
|
||||
|
||||
const isAdmin = userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB
|
||||
@ -135,10 +161,12 @@ export const useFilteredMods = (
|
||||
filterOptions.moderated,
|
||||
filterOptions.wot,
|
||||
filterOptions.nsfw,
|
||||
filterOptions.repost,
|
||||
author,
|
||||
mods,
|
||||
muteLists,
|
||||
nsfwList,
|
||||
repostList,
|
||||
siteWot,
|
||||
siteWotLevel,
|
||||
userWot,
|
||||
|
@ -11,6 +11,9 @@ import '../../styles/styles.css'
|
||||
import { PaginationWithPageNumbers } from 'components/Pagination'
|
||||
import { scrollIntoView } from 'utils'
|
||||
import { LoadingSpinner } from 'components/LoadingSpinner'
|
||||
import { Filter } from 'components/Filters'
|
||||
import { Dropdown } from 'components/Filters/Dropdown'
|
||||
import { Option } from 'components/Filters/Option'
|
||||
|
||||
export const BlogsPage = () => {
|
||||
const navigation = useNavigation()
|
||||
@ -126,66 +129,39 @@ export const BlogsPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='IBMSecMain'>
|
||||
<div className='FiltersMain'>
|
||||
<div className='FiltersMainElement'>
|
||||
<div className='dropdown dropdownMain'>
|
||||
<button
|
||||
className='btn dropdown-toggle btnMain btnMainDropdown'
|
||||
aria-expanded='false'
|
||||
data-bs-toggle='dropdown'
|
||||
type='button'
|
||||
>
|
||||
{filterOptions.sort}
|
||||
</button>
|
||||
<div className='dropdown-menu dropdownMainMenu'>
|
||||
{Object.values(SortBy).map((item, index) => (
|
||||
<div
|
||||
key={`sortByItem-${index}`}
|
||||
className='dropdown-item dropdownMainMenuItem'
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
sort: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='FiltersMainElement'>
|
||||
<div className='dropdown dropdownMain'>
|
||||
<button
|
||||
className='btn dropdown-toggle btnMain btnMainDropdown'
|
||||
aria-expanded='false'
|
||||
data-bs-toggle='dropdown'
|
||||
type='button'
|
||||
>
|
||||
{filterOptions.nsfw}
|
||||
</button>
|
||||
<div className='dropdown-menu dropdownMainMenu'>
|
||||
{Object.values(NSFWFilter).map((item, index) => (
|
||||
<div
|
||||
key={`nsfwFilterItem-${index}`}
|
||||
className='dropdown-item dropdownMainMenuItem'
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
nsfw: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Filter>
|
||||
<Dropdown label={filterOptions.sort}>
|
||||
{Object.values(SortBy).map((item, index) => (
|
||||
<Option
|
||||
key={`sortByItem-${index}`}
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
sort: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</Option>
|
||||
))}
|
||||
</Dropdown>
|
||||
|
||||
<Dropdown label={filterOptions.nsfw}>
|
||||
{Object.values(NSFWFilter).map((item, index) => (
|
||||
<Option
|
||||
key={`nsfwFilterItem-${index}`}
|
||||
onClick={() =>
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
nsfw: item
|
||||
}))
|
||||
}
|
||||
>
|
||||
{item}
|
||||
</Option>
|
||||
))}
|
||||
</Dropdown>
|
||||
</Filter>
|
||||
|
||||
<div className='IBMSecMain IBMSMListWrapper'>
|
||||
<div className='IBMSMList'>
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
NDKSubscriptionCacheUsage
|
||||
} from '@nostr-dev-kit/ndk'
|
||||
import { ModCard } from 'components/ModCard'
|
||||
import { ModFilter } from 'components/ModsFilter'
|
||||
import { ModFilter } from 'components/Filters/ModsFilter'
|
||||
import { PaginationWithPageNumbers } from 'components/Pagination'
|
||||
import { SearchInput } from 'components/SearchInput'
|
||||
import { MAX_MODS_PER_PAGE, T_TAG_VALUE } from 'constants.ts'
|
||||
@ -20,11 +20,13 @@ import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useParams, useSearchParams } from 'react-router-dom'
|
||||
import { FilterOptions, ModDetails } from 'types'
|
||||
import {
|
||||
CurationSetIdentifiers,
|
||||
DEFAULT_FILTER_OPTIONS,
|
||||
extractModData,
|
||||
isModDataComplete,
|
||||
scrollIntoView
|
||||
} from 'utils'
|
||||
import { useCuratedSet } from 'hooks/useCuratedSet'
|
||||
|
||||
export const GamePage = () => {
|
||||
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
||||
@ -33,6 +35,7 @@ export const GamePage = () => {
|
||||
const { ndk } = useNDKContext()
|
||||
const muteLists = useMuteLists()
|
||||
const nsfwList = useNSFWList()
|
||||
const repostList = useCuratedSet(CurationSetIdentifiers.Repost)
|
||||
|
||||
const [filterOptions] = useLocalStorage<FilterOptions>(
|
||||
'filter',
|
||||
@ -101,7 +104,8 @@ export const GamePage = () => {
|
||||
userState,
|
||||
filterOptions,
|
||||
nsfwList,
|
||||
muteLists
|
||||
muteLists,
|
||||
repostList
|
||||
)
|
||||
|
||||
// Pagination logic
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ModFilter } from 'components/ModsFilter'
|
||||
import { ModFilter } from 'components/Filters/ModsFilter'
|
||||
import { Pagination } from 'components/Pagination'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { createSearchParams, useNavigate } from 'react-router-dom'
|
||||
@ -39,6 +39,7 @@ export const ModsPage = () => {
|
||||
)
|
||||
const muteLists = useMuteLists()
|
||||
const nsfwList = useNSFWList()
|
||||
const repostList = useCuratedSet(CurationSetIdentifiers.Repost)
|
||||
|
||||
const [page, setPage] = useState(1)
|
||||
|
||||
@ -99,17 +100,10 @@ export const ModsPage = () => {
|
||||
userState,
|
||||
filterOptions,
|
||||
nsfwList,
|
||||
muteLists
|
||||
muteLists,
|
||||
repostList
|
||||
)
|
||||
|
||||
// Add repost tag to mods included in repostList
|
||||
const repostList = useCuratedSet(CurationSetIdentifiers.Repost)
|
||||
const filteredModListRepost = filteredModList.map((mod) => {
|
||||
return !mod.repost && repostList.includes(mod.aTag)
|
||||
? { ...mod, repost: true }
|
||||
: mod
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
{isFetching && <LoadingSpinner desc='Fetching mod details from relays' />}
|
||||
@ -124,7 +118,7 @@ export const ModsPage = () => {
|
||||
|
||||
<div className='IBMSecMain IBMSMListWrapper'>
|
||||
<div className='IBMSMList'>
|
||||
{filteredModListRepost.map((mod) => (
|
||||
{filteredModList.map((mod) => (
|
||||
<ModCard key={mod.id} {...mod} />
|
||||
))}
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { NDKFilter, NDKKind } from '@nostr-dev-kit/ndk'
|
||||
import { LoadingSpinner } from 'components/LoadingSpinner'
|
||||
import { ModCard } from 'components/ModCard'
|
||||
import { ModFilter } from 'components/ModsFilter'
|
||||
import { ModFilter } from 'components/Filters/ModsFilter'
|
||||
import { Pagination } from 'components/Pagination'
|
||||
import { ProfileSection } from 'components/ProfileSection'
|
||||
import { Tabs } from 'components/Tabs'
|
||||
@ -39,6 +39,7 @@ import {
|
||||
import { CheckboxField } from 'components/Inputs'
|
||||
import { ProfilePageLoaderResult } from './loader'
|
||||
import { BlogCard } from 'components/BlogCard'
|
||||
import { BlogsFilter } from 'components/Filters/BlogsFilter'
|
||||
|
||||
export const ProfilePage = () => {
|
||||
const {
|
||||
@ -269,16 +270,10 @@ export const ProfilePage = () => {
|
||||
filterOptions,
|
||||
nsfwList,
|
||||
muteLists,
|
||||
repostList,
|
||||
profilePubkey
|
||||
)
|
||||
|
||||
// Add repost tag to mods included in repostList
|
||||
const filteredModListRepost = filteredModList.map((mod) => {
|
||||
return !mod.repost && repostList.includes(mod.aTag)
|
||||
? { ...mod, repost: true }
|
||||
: mod
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='InnerBodyMain'>
|
||||
<div className='ContainerMain'>
|
||||
@ -429,7 +424,7 @@ export const ProfilePage = () => {
|
||||
<ModFilter filterKey={filterKey} author={profilePubkey} />
|
||||
|
||||
<div className='IBMSMList IBMSMListAlt'>
|
||||
{filteredModListRepost.map((mod) => (
|
||||
{filteredModList.map((mod) => (
|
||||
<ModCard key={mod.id} {...mod} />
|
||||
))}
|
||||
</div>
|
||||
@ -831,7 +826,10 @@ const ProfileTabBlogs = () => {
|
||||
<LoadingSpinner desc={'Loading...'} />
|
||||
)}
|
||||
|
||||
<ModFilter filterKey={'filter-blog'} author={profile?.pubkey as string} />
|
||||
<BlogsFilter
|
||||
filterKey={'filter-blog'}
|
||||
author={profile?.pubkey as string}
|
||||
/>
|
||||
|
||||
<div className='IBMSMList IBMSMListAlt'>
|
||||
{moderatedAndSortedBlogs.map((b) => (
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
import { ErrorBoundary } from 'components/ErrorBoundary'
|
||||
import { GameCard } from 'components/GameCard'
|
||||
import { ModCard } from 'components/ModCard'
|
||||
import { ModFilter } from 'components/ModsFilter'
|
||||
import { ModFilter } from 'components/Filters/ModsFilter'
|
||||
import { Pagination } from 'components/Pagination'
|
||||
import { Profile } from 'components/ProfileSection'
|
||||
import { SearchInput } from 'components/SearchInput'
|
||||
@ -32,11 +32,13 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { FilterOptions, ModDetails, ModeratedFilter, MuteLists } from 'types'
|
||||
import {
|
||||
CurationSetIdentifiers,
|
||||
DEFAULT_FILTER_OPTIONS,
|
||||
extractModData,
|
||||
isModDataComplete,
|
||||
scrollIntoView
|
||||
} from 'utils'
|
||||
import { useCuratedSet } from 'hooks/useCuratedSet'
|
||||
|
||||
enum SearchKindEnum {
|
||||
Mods = 'Mods',
|
||||
@ -50,6 +52,7 @@ export const SearchPage = () => {
|
||||
|
||||
const muteLists = useMuteLists()
|
||||
const nsfwList = useNSFWList()
|
||||
const repostList = useCuratedSet(CurationSetIdentifiers.Repost)
|
||||
const searchTermRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const searchKind =
|
||||
@ -115,6 +118,7 @@ export const SearchPage = () => {
|
||||
filterOptions={filterOptions}
|
||||
muteLists={muteLists}
|
||||
nsfwList={nsfwList}
|
||||
repostList={repostList}
|
||||
el={scrollTargetRef.current}
|
||||
/>
|
||||
)}
|
||||
@ -233,6 +237,7 @@ type ModsResultProps = {
|
||||
user: MuteLists
|
||||
}
|
||||
nsfwList: string[]
|
||||
repostList: string[]
|
||||
el: HTMLElement | null
|
||||
}
|
||||
|
||||
@ -241,6 +246,7 @@ const ModsResult = ({
|
||||
searchTerm,
|
||||
muteLists,
|
||||
nsfwList,
|
||||
repostList,
|
||||
el
|
||||
}: ModsResultProps) => {
|
||||
const { ndk } = useNDKContext()
|
||||
@ -313,7 +319,8 @@ const ModsResult = ({
|
||||
userState,
|
||||
filterOptions,
|
||||
nsfwList,
|
||||
muteLists
|
||||
muteLists,
|
||||
repostList
|
||||
)
|
||||
|
||||
const handleNext = () => {
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { SortBy, NSFWFilter, ModeratedFilter } from './modsFilter'
|
||||
|
||||
export interface BlogForm {
|
||||
title: string
|
||||
content: string
|
||||
@ -40,3 +42,10 @@ export interface BlogPageLoaderResult {
|
||||
isAddedToNSFW: boolean
|
||||
isBlocked: boolean
|
||||
}
|
||||
|
||||
export interface BlogsFilterOptions {
|
||||
sort: SortBy
|
||||
nsfw: NSFWFilter
|
||||
source: string
|
||||
moderated: ModeratedFilter
|
||||
}
|
||||
|
@ -25,10 +25,17 @@ export enum WOTFilterOptions {
|
||||
Exclude = 'Exclude'
|
||||
}
|
||||
|
||||
export enum RepostFilter {
|
||||
Hide_Repost = 'Hide Repost',
|
||||
Show_Repost = 'Show Repost',
|
||||
Only_Repost = 'Only Repost'
|
||||
}
|
||||
|
||||
export interface FilterOptions {
|
||||
sort: SortBy
|
||||
nsfw: NSFWFilter
|
||||
source: string
|
||||
moderated: ModeratedFilter
|
||||
wot: WOTFilterOptions
|
||||
repost: RepostFilter
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ import {
|
||||
SortBy,
|
||||
NSFWFilter,
|
||||
ModeratedFilter,
|
||||
WOTFilterOptions
|
||||
WOTFilterOptions,
|
||||
RepostFilter
|
||||
} from 'types'
|
||||
|
||||
export const DEFAULT_FILTER_OPTIONS: FilterOptions = {
|
||||
@ -11,5 +12,6 @@ export const DEFAULT_FILTER_OPTIONS: FilterOptions = {
|
||||
nsfw: NSFWFilter.Hide_NSFW,
|
||||
source: window.location.host,
|
||||
moderated: ModeratedFilter.Moderated,
|
||||
wot: WOTFilterOptions.Site_Only
|
||||
wot: WOTFilterOptions.Site_Only,
|
||||
repost: RepostFilter.Show_Repost
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user