Feed feedback and fixes #231
@ -43,7 +43,14 @@ export const FeedFilter = React.memo(
|
||||
|
||||
{/* nsfw filter options */}
|
||||
<Dropdown label={filterOptions.nsfw}>
|
||||
<NsfwFilterOptions filterKey={filterKey} />
|
||||
<NsfwFilterOptions
|
||||
filterKey={filterKey}
|
||||
{...(tab === 2
|
||||
? {
|
||||
skipOnlyNsfw: true
|
||||
}
|
||||
: {})}
|
||||
/>
|
||||
</Dropdown>
|
||||
|
||||
{/* source filter options */}
|
||||
|
@ -7,9 +7,13 @@ import { DEFAULT_FILTER_OPTIONS } from 'utils'
|
||||
|
||||
interface NsfwFilterOptionsProps {
|
||||
filterKey: string
|
||||
skipOnlyNsfw?: boolean
|
||||
}
|
||||
|
||||
export const NsfwFilterOptions = ({ filterKey }: NsfwFilterOptionsProps) => {
|
||||
export const NsfwFilterOptions = ({
|
||||
filterKey,
|
||||
skipOnlyNsfw
|
||||
}: NsfwFilterOptionsProps) => {
|
||||
const [, setFilterOptions] = useLocalStorage<FilterOptions>(
|
||||
filterKey,
|
||||
DEFAULT_FILTER_OPTIONS
|
||||
@ -30,29 +34,34 @@ export const NsfwFilterOptions = ({ filterKey }: NsfwFilterOptionsProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.values(NSFWFilter).map((item, index) => (
|
||||
<Option
|
||||
key={`nsfwFilterItem-${index}`}
|
||||
onClick={() => {
|
||||
// Trigger NSFW popup
|
||||
if (
|
||||
(item === NSFWFilter.Only_NSFW ||
|
||||
item === NSFWFilter.Show_NSFW) &&
|
||||
!confirmNsfw
|
||||
) {
|
||||
setSelectedNsfwOption(item)
|
||||
setShowNsfwPopup(true)
|
||||
} else {
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
nsfw: item
|
||||
}))
|
||||
}
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</Option>
|
||||
))}
|
||||
{Object.values(NSFWFilter).map((item, index) => {
|
||||
// Posts feed filter exception
|
||||
if (item === NSFWFilter.Only_NSFW && skipOnlyNsfw) return null
|
||||
|
||||
return (
|
||||
<Option
|
||||
key={`nsfwFilterItem-${index}`}
|
||||
onClick={() => {
|
||||
// Trigger NSFW popup
|
||||
if (
|
||||
(item === NSFWFilter.Only_NSFW ||
|
||||
item === NSFWFilter.Show_NSFW) &&
|
||||
!confirmNsfw
|
||||
) {
|
||||
setSelectedNsfwOption(item)
|
||||
setShowNsfwPopup(true)
|
||||
} else {
|
||||
setFilterOptions((prev) => ({
|
||||
...prev,
|
||||
nsfw: item
|
||||
}))
|
||||
}
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</Option>
|
||||
)
|
||||
})}
|
||||
{showNsfwPopup && (
|
||||
<NsfwAlertPopup
|
||||
handleConfirm={handleConfirm}
|
||||
|
@ -9,16 +9,22 @@ import { Reactions } from 'components/comment/Reactions'
|
||||
import { Zap } from 'components/comment/Zap'
|
||||
import { Dots } from 'components/Spinner'
|
||||
import { formatDate } from 'date-fns'
|
||||
import { useAppSelector, useDidMount, useNDKContext } from 'hooks'
|
||||
import {
|
||||
useAppSelector,
|
||||
useDidMount,
|
||||
useLocalStorage,
|
||||
useNDKContext
|
||||
} from 'hooks'
|
||||
import { useComments } from 'hooks/useComments'
|
||||
import { nip19 } from 'nostr-tools'
|
||||
import { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { appRoutes, getProfilePageRoute } from 'routes'
|
||||
import { UserProfile } from 'types'
|
||||
import { hexToNpub } from 'utils'
|
||||
import { FeedPostsFilter, NSFWFilter, UserProfile } from 'types'
|
||||
import { DEFAULT_FILTER_OPTIONS, hexToNpub } from 'utils'
|
||||
import { NoteRepostPopup } from './NoteRepostPopup'
|
||||
import { NoteQuoteRepostPopup } from './NoteQuoteRepostPopup'
|
||||
import { NsfwCommentWrapper } from 'components/NsfwCommentWrapper'
|
||||
|
||||
interface NoteProps {
|
||||
ndkEvent: NDKEvent
|
||||
@ -30,6 +36,14 @@ export const Note = ({ ndkEvent }: NoteProps) => {
|
||||
const userPubkey = userState.user?.pubkey as string | undefined
|
||||
const [eventProfile, setEventProfile] = useState<UserProfile>()
|
||||
const isRepost = ndkEvent.kind === NDKKind.Repost
|
||||
const filterKey = 'filter-feed-2'
|
||||
const [filterOptions] = useLocalStorage<FeedPostsFilter>(
|
||||
filterKey,
|
||||
DEFAULT_FILTER_OPTIONS
|
||||
)
|
||||
const isNsfw = ndkEvent
|
||||
.getMatchingTags('L')
|
||||
.some((t) => t[1] === 'content-warning')
|
||||
const [repostEvent, setRepostEvent] = useState<NDKEvent | undefined>()
|
||||
const [repostProfile, setRepostProfile] = useState<UserProfile | undefined>()
|
||||
const noteEvent = repostEvent ?? ndkEvent
|
||||
@ -192,9 +206,15 @@ export const Note = ({ ndkEvent }: NoteProps) => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='IBMSMSMBSSCL_CommentBottom'>
|
||||
<CommentContent content={noteEvent.content} />
|
||||
</div>
|
||||
<NsfwCommentWrapper
|
||||
id={ndkEvent.id}
|
||||
isNsfw={isNsfw}
|
||||
hideNsfwActive={NSFWFilter.Hide_NSFW === filterOptions.nsfw}
|
||||
>
|
||||
<div className='IBMSMSMBSSCL_CommentBottom'>
|
||||
<CommentContent content={noteEvent.content} isNsfw={isNsfw} />
|
||||
</div>
|
||||
</NsfwCommentWrapper>
|
||||
<div className='IBMSMSMBSSCL_CommentActions'>
|
||||
<div className='IBMSMSMBSSCL_CommentActionsInside'>
|
||||
<Reactions {...noteEvent.rawEvent()} />
|
||||
|
60
src/components/NsfwCommentWrapper.tsx
Normal file
60
src/components/NsfwCommentWrapper.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import { useLocalStorage, useSessionStorage } from 'hooks'
|
||||
import { PropsWithChildren, useState } from 'react'
|
||||
import { NsfwAlertPopup } from './NsfwAlertPopup'
|
||||
|
||||
interface NsfwCommentWrapperProps {
|
||||
id: string
|
||||
isNsfw: boolean
|
||||
hideNsfwActive: boolean
|
||||
}
|
||||
export const NsfwCommentWrapper = ({
|
||||
id,
|
||||
isNsfw,
|
||||
hideNsfwActive,
|
||||
children
|
||||
}: PropsWithChildren<NsfwCommentWrapperProps>) => {
|
||||
// Have we approved show nsfw comment button
|
||||
const [viewNsfwComment, setViewNsfwComment] = useSessionStorage<boolean>(
|
||||
id,
|
||||
false
|
||||
)
|
||||
|
||||
const [showNsfwPopup, setShowNsfwPopup] = useState<boolean>(false)
|
||||
const [confirmNsfw] = useLocalStorage<boolean>('confirm-nsfw', false)
|
||||
const handleConfirm = (confirm: boolean) => {
|
||||
if (confirm) {
|
||||
setShowNsfwPopup(confirm)
|
||||
setViewNsfwComment(true)
|
||||
}
|
||||
}
|
||||
const handleShowNSFW = () => {
|
||||
if (confirmNsfw) {
|
||||
setViewNsfwComment(true)
|
||||
} else {
|
||||
setShowNsfwPopup(true)
|
||||
}
|
||||
}
|
||||
|
||||
// Skip NSFW wrapper
|
||||
// if comment is not marked as NSFW
|
||||
// if user clicked View NSFW button
|
||||
// if hide filter is not active
|
||||
if (!isNsfw || viewNsfwComment || !hideNsfwActive) return children
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='IBMSMSMBSSCL_CommentNSFW'>
|
||||
<p>This post is hidden as it's marked as NSFW</p>
|
||||
<button className='btnMain' type='button' onClick={handleShowNSFW}>
|
||||
View this NSFW post
|
||||
</button>
|
||||
</div>
|
||||
{showNsfwPopup && (
|
||||
<NsfwAlertPopup
|
||||
handleConfirm={handleConfirm}
|
||||
handleClose={() => setShowNsfwPopup(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
@ -3,9 +3,13 @@ import { useTextLimit } from 'hooks'
|
||||
|
||||
interface CommentContentProps {
|
||||
content: string
|
||||
isNsfw?: boolean
|
||||
}
|
||||
|
||||
export const CommentContent = ({ content }: CommentContentProps) => {
|
||||
export const CommentContent = ({
|
||||
content,
|
||||
isNsfw = false
|
||||
}: CommentContentProps) => {
|
||||
const { text, isTextOverflowing, isExpanded, toggle } = useTextLimit(content)
|
||||
|
||||
return (
|
||||
@ -26,6 +30,11 @@ export const CommentContent = ({ content }: CommentContentProps) => {
|
||||
<p>View full post</p>
|
||||
</div>
|
||||
)}
|
||||
{isNsfw && (
|
||||
<div className='IBMSMSMBSSCL_CommentNSWFTag'>
|
||||
<p>NSFW</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { useLoaderData } from 'react-router-dom'
|
||||
import { FeedPageLoaderResult } from './loader'
|
||||
import { useAppSelector, useLocalStorage, useNDKContext } from 'hooks'
|
||||
import { FilterOptions, NSFWFilter } from 'types'
|
||||
import { DEFAULT_FILTER_OPTIONS } from 'utils'
|
||||
import { useAppSelector, useNDKContext } from 'hooks'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { LoadingSpinner } from 'components/LoadingSpinner'
|
||||
import {
|
||||
@ -19,10 +17,7 @@ export const FeedTabPosts = () => {
|
||||
const { followList } = useLoaderData() as FeedPageLoaderResult
|
||||
const userState = useAppSelector((state) => state.user)
|
||||
const userPubkey = userState.user?.pubkey as string | undefined
|
||||
const filterKey = 'filter-feed-2'
|
||||
const [filterOptions] = useLocalStorage<FilterOptions>(filterKey, {
|
||||
...DEFAULT_FILTER_OPTIONS
|
||||
})
|
||||
|
||||
const { ndk } = useNDKContext()
|
||||
const [notes, setNotes] = useState<NDKEvent[]>([])
|
||||
const [isFetching, setIsFetching] = useState(false)
|
||||
@ -58,19 +53,6 @@ export const FeedTabPosts = () => {
|
||||
const filteredNotes = useMemo(() => {
|
||||
let _notes = notes || []
|
||||
|
||||
// NSFW Filter
|
||||
_notes = _notes.filter((n) => {
|
||||
if (filterOptions.nsfw === NSFWFilter.Only_NSFW) {
|
||||
return n.getMatchingTags('L').some((l) => l[1] === 'content-warning')
|
||||
}
|
||||
|
||||
if (filterOptions.nsfw === NSFWFilter.Hide_NSFW) {
|
||||
return !n.getMatchingTags('L').some((l) => l[1] === 'content-warning')
|
||||
}
|
||||
|
||||
return n
|
||||
})
|
||||
|
||||
// Filter source
|
||||
// TODO: Enable source/client filter
|
||||
// _notes = _notes.filter(
|
||||
@ -90,7 +72,7 @@ export const FeedTabPosts = () => {
|
||||
|
||||
showing > 0 && _notes.splice(showing)
|
||||
return _notes
|
||||
}, [filterOptions.nsfw, notes, showing])
|
||||
}, [notes, showing])
|
||||
|
||||
if (!userPubkey) return null
|
||||
|
||||
|
@ -44,6 +44,7 @@
|
||||
}
|
||||
|
||||
.IBMSMSMBSSCL_CommentBottom {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
|
@ -40,3 +40,5 @@ export interface FilterOptions {
|
||||
wot: WOTFilterOptions
|
||||
repost: RepostFilter
|
||||
}
|
||||
|
||||
export type FeedPostsFilter = Pick<FilterOptions, 'nsfw'>
|
||||
|
Loading…
x
Reference in New Issue
Block a user