fix(notes): nsfw filter, nsfw post wrapper

This commit is contained in:
en 2025-02-19 12:55:44 +01:00
parent b84adf3617
commit e92d602f3a
8 changed files with 143 additions and 53 deletions

View File

@ -43,7 +43,14 @@ export const FeedFilter = React.memo(
{/* nsfw filter options */} {/* nsfw filter options */}
<Dropdown label={filterOptions.nsfw}> <Dropdown label={filterOptions.nsfw}>
<NsfwFilterOptions filterKey={filterKey} /> <NsfwFilterOptions
filterKey={filterKey}
{...(tab === 2
? {
skipOnlyNsfw: true
}
: {})}
/>
</Dropdown> </Dropdown>
{/* source filter options */} {/* source filter options */}

View File

@ -7,9 +7,13 @@ import { DEFAULT_FILTER_OPTIONS } from 'utils'
interface NsfwFilterOptionsProps { interface NsfwFilterOptionsProps {
filterKey: string filterKey: string
skipOnlyNsfw?: boolean
} }
export const NsfwFilterOptions = ({ filterKey }: NsfwFilterOptionsProps) => { export const NsfwFilterOptions = ({
filterKey,
skipOnlyNsfw
}: NsfwFilterOptionsProps) => {
const [, setFilterOptions] = useLocalStorage<FilterOptions>( const [, setFilterOptions] = useLocalStorage<FilterOptions>(
filterKey, filterKey,
DEFAULT_FILTER_OPTIONS DEFAULT_FILTER_OPTIONS
@ -30,7 +34,11 @@ export const NsfwFilterOptions = ({ filterKey }: NsfwFilterOptionsProps) => {
return ( return (
<> <>
{Object.values(NSFWFilter).map((item, index) => ( {Object.values(NSFWFilter).map((item, index) => {
// Posts feed filter exception
if (item === NSFWFilter.Only_NSFW && skipOnlyNsfw) return null
return (
<Option <Option
key={`nsfwFilterItem-${index}`} key={`nsfwFilterItem-${index}`}
onClick={() => { onClick={() => {
@ -52,7 +60,8 @@ export const NsfwFilterOptions = ({ filterKey }: NsfwFilterOptionsProps) => {
> >
{item} {item}
</Option> </Option>
))} )
})}
{showNsfwPopup && ( {showNsfwPopup && (
<NsfwAlertPopup <NsfwAlertPopup
handleConfirm={handleConfirm} handleConfirm={handleConfirm}

View File

@ -9,16 +9,22 @@ import { Reactions } from 'components/comment/Reactions'
import { Zap } from 'components/comment/Zap' import { Zap } from 'components/comment/Zap'
import { Dots } from 'components/Spinner' import { Dots } from 'components/Spinner'
import { formatDate } from 'date-fns' import { formatDate } from 'date-fns'
import { useAppSelector, useDidMount, useNDKContext } from 'hooks' import {
useAppSelector,
useDidMount,
useLocalStorage,
useNDKContext
} from 'hooks'
import { useComments } from 'hooks/useComments' import { useComments } from 'hooks/useComments'
import { nip19 } from 'nostr-tools' import { nip19 } from 'nostr-tools'
import { useState } from 'react' import { useState } from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { appRoutes, getProfilePageRoute } from 'routes' import { appRoutes, getProfilePageRoute } from 'routes'
import { UserProfile } from 'types' import { FeedPostsFilter, NSFWFilter, UserProfile } from 'types'
import { hexToNpub } from 'utils' import { DEFAULT_FILTER_OPTIONS, hexToNpub } from 'utils'
import { NoteRepostPopup } from './NoteRepostPopup' import { NoteRepostPopup } from './NoteRepostPopup'
import { NoteQuoteRepostPopup } from './NoteQuoteRepostPopup' import { NoteQuoteRepostPopup } from './NoteQuoteRepostPopup'
import { NsfwCommentWrapper } from 'components/NsfwCommentWrapper'
interface NoteProps { interface NoteProps {
ndkEvent: NDKEvent ndkEvent: NDKEvent
@ -30,6 +36,14 @@ export const Note = ({ ndkEvent }: NoteProps) => {
const userPubkey = userState.user?.pubkey as string | undefined const userPubkey = userState.user?.pubkey as string | undefined
const [eventProfile, setEventProfile] = useState<UserProfile>() const [eventProfile, setEventProfile] = useState<UserProfile>()
const isRepost = ndkEvent.kind === NDKKind.Repost 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 [repostEvent, setRepostEvent] = useState<NDKEvent | undefined>()
const [repostProfile, setRepostProfile] = useState<UserProfile | undefined>() const [repostProfile, setRepostProfile] = useState<UserProfile | undefined>()
const noteEvent = repostEvent ?? ndkEvent const noteEvent = repostEvent ?? ndkEvent
@ -192,9 +206,15 @@ export const Note = ({ ndkEvent }: NoteProps) => {
)} )}
</div> </div>
</div> </div>
<NsfwCommentWrapper
id={ndkEvent.id}
isNsfw={isNsfw}
hideNsfwActive={NSFWFilter.Hide_NSFW === filterOptions.nsfw}
>
<div className='IBMSMSMBSSCL_CommentBottom'> <div className='IBMSMSMBSSCL_CommentBottom'>
<CommentContent content={noteEvent.content} /> <CommentContent content={noteEvent.content} isNsfw={isNsfw} />
</div> </div>
</NsfwCommentWrapper>
<div className='IBMSMSMBSSCL_CommentActions'> <div className='IBMSMSMBSSCL_CommentActions'>
<div className='IBMSMSMBSSCL_CommentActionsInside'> <div className='IBMSMSMBSSCL_CommentActionsInside'>
<Reactions {...noteEvent.rawEvent()} /> <Reactions {...noteEvent.rawEvent()} />

View 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&#39;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)}
/>
)}
</>
)
}

View File

@ -3,9 +3,13 @@ import { useTextLimit } from 'hooks'
interface CommentContentProps { interface CommentContentProps {
content: string content: string
isNsfw?: boolean
} }
export const CommentContent = ({ content }: CommentContentProps) => { export const CommentContent = ({
content,
isNsfw = false
}: CommentContentProps) => {
const { text, isTextOverflowing, isExpanded, toggle } = useTextLimit(content) const { text, isTextOverflowing, isExpanded, toggle } = useTextLimit(content)
return ( return (
@ -26,6 +30,11 @@ export const CommentContent = ({ content }: CommentContentProps) => {
<p>View full post</p> <p>View full post</p>
</div> </div>
)} )}
{isNsfw && (
<div className='IBMSMSMBSSCL_CommentNSWFTag'>
<p>NSFW</p>
</div>
)}
</> </>
) )
} }

View File

@ -1,8 +1,6 @@
import { useLoaderData } from 'react-router-dom' import { useLoaderData } from 'react-router-dom'
import { FeedPageLoaderResult } from './loader' import { FeedPageLoaderResult } from './loader'
import { useAppSelector, useLocalStorage, useNDKContext } from 'hooks' import { useAppSelector, useNDKContext } from 'hooks'
import { FilterOptions, NSFWFilter } from 'types'
import { DEFAULT_FILTER_OPTIONS } from 'utils'
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import { LoadingSpinner } from 'components/LoadingSpinner' import { LoadingSpinner } from 'components/LoadingSpinner'
import { import {
@ -19,10 +17,7 @@ export const FeedTabPosts = () => {
const { followList } = useLoaderData() as FeedPageLoaderResult const { followList } = useLoaderData() as FeedPageLoaderResult
const userState = useAppSelector((state) => state.user) const userState = useAppSelector((state) => state.user)
const userPubkey = userState.user?.pubkey as string | undefined 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 { ndk } = useNDKContext()
const [notes, setNotes] = useState<NDKEvent[]>([]) const [notes, setNotes] = useState<NDKEvent[]>([])
const [isFetching, setIsFetching] = useState(false) const [isFetching, setIsFetching] = useState(false)
@ -58,19 +53,6 @@ export const FeedTabPosts = () => {
const filteredNotes = useMemo(() => { const filteredNotes = useMemo(() => {
let _notes = notes || [] 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 // Filter source
// TODO: Enable source/client filter // TODO: Enable source/client filter
// _notes = _notes.filter( // _notes = _notes.filter(
@ -90,7 +72,7 @@ export const FeedTabPosts = () => {
showing > 0 && _notes.splice(showing) showing > 0 && _notes.splice(showing)
return _notes return _notes
}, [filterOptions.nsfw, notes, showing]) }, [notes, showing])
if (!userPubkey) return null if (!userPubkey) return null

View File

@ -44,6 +44,7 @@
} }
.IBMSMSMBSSCL_CommentBottom { .IBMSMSMBSSCL_CommentBottom {
position: relative;
width: 100%; width: 100%;
padding: 20px; padding: 20px;
color: rgba(255, 255, 255, 0.75); color: rgba(255, 255, 255, 0.75);

View File

@ -40,3 +40,5 @@ export interface FilterOptions {
wot: WOTFilterOptions wot: WOTFilterOptions
repost: RepostFilter repost: RepostFilter
} }
export type FeedPostsFilter = Pick<FilterOptions, 'nsfw'>