You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

151 lines
5.4 KiB

import NoteList, { type TNoteListRef } from '@/components/NoteList'
import NoteCard from '@/components/NoteCard'
import KindFilter from '@/components/KindFilter'
import { RefreshButton } from '@/components/RefreshButton'
import { Skeleton } from '@/components/ui/skeleton'
import { ExtendedKind, PROFILE_POSTS_TAB_KINDS } from '@/constants'
import { useProfileAuthorFeedSubRequests } from '@/hooks/useProfileAuthorFeedSubRequests'
import { useProfilePins } from '@/hooks/useProfilePins'
import { useKindFilterOrDefaults } from '@/providers/KindFilterProvider'
import { useDeletedEvent } from '@/providers/DeletedEventProvider'
import client from '@/services/client.service'
import { nip19, kinds } from 'nostr-tools'
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
const ProfileFeedWithPins = forwardRef<{ refresh: () => void }, { pubkey: string }>(({ pubkey }, ref) => {
const { t } = useTranslation()
const { isEventDeleted } = useDeletedEvent()
const { showKinds, showKind1OPs, showKind1Replies, showKind1111, feedKindFilterBypass } =
useKindFilterOrDefaults()
const profileTimelineShowKinds = useMemo(() => {
if (showKinds.includes(kinds.Repost) && showKinds.includes(ExtendedKind.GENERIC_REPOST)) {
return showKinds
}
const next = [...showKinds]
if (!next.includes(kinds.Repost)) next.push(kinds.Repost)
if (!next.includes(ExtendedKind.GENERIC_REPOST)) next.push(ExtendedKind.GENERIC_REPOST)
return next.sort((a, b) => a - b)
}, [showKinds])
const [isRefreshing, setIsRefreshing] = useState(false)
const noteListRef = useRef<TNoteListRef>(null)
const { pinEvents, loadingPins, refreshPins } = useProfilePins(pubkey)
const postsTabKinds = useMemo(() => [...PROFILE_POSTS_TAB_KINDS], [])
const { subRequests, followingFeedDeltaSubRequests, feedSubscriptionKey, refresh: refreshAuthorRelayLayers } =
useProfileAuthorFeedSubRequests({
pubkey,
kinds: postsTabKinds,
limit: 200
})
const pinnedEventIds = useMemo(
() =>
pinEvents.map((e) =>
nip19.neventEncode({ id: e.id, author: e.pubkey, kind: e.kind })
),
[pinEvents]
)
const refreshAll = useCallback(() => {
setIsRefreshing(true)
refreshPins()
refreshAuthorRelayLayers()
noteListRef.current?.refresh()
void client.fetchDeletionEventsForPubkey(pubkey)
}, [refreshPins, refreshAuthorRelayLayers, pubkey])
useImperativeHandle(ref, () => ({ refresh: refreshAll }), [refreshAll])
useEffect(() => {
if (!isRefreshing) return
const id = window.setTimeout(() => setIsRefreshing(false), 600)
return () => clearTimeout(id)
}, [isRefreshing])
const handleShowKindsChange = useCallback(() => {
noteListRef.current?.scrollToTop()
}, [])
const showPinsOnlySkeleton = pinEvents.length === 0 && loadingPins && subRequests.length === 0
if (showPinsOnlySkeleton) {
return (
<div className="mt-4 space-y-2 px-1">
<div className="space-y-2">
{Array.from({ length: 4 }).map((_, i) => (
<Skeleton key={i} className="h-32 w-full" />
))}
</div>
</div>
)
}
if (!subRequests.length) {
return (
<div className="mt-4 px-2">
<p className="py-8 text-center text-sm text-muted-foreground">{t('Nothing to load for this feed.')}</p>
</div>
)
}
return (
<div className="mt-4 min-w-0">
{isRefreshing && (
<div
className="mb-2 flex items-center justify-center gap-2 px-4 py-2 text-center text-sm text-green-500"
role="status"
aria-live="polite"
>
{t('Refreshing posts...')}
</div>
)}
<div className="mb-2 flex flex-wrap items-center justify-end gap-2 px-2">
<RefreshButton onClick={refreshAll} />
<KindFilter showKinds={showKinds} onShowKindsChange={handleShowKindsChange} />
</div>
{pinEvents.filter((e) => !isEventDeleted(e)).length > 0 && (
<div className="mb-3 space-y-2 px-1" aria-label={t('Pinned posts')}>
{pinEvents
.filter((e) => !isEventDeleted(e))
.map((event) => (
<NoteCard key={event.id} className="w-full" event={event} filterMutedNotes={false} pinned />
))}
<div className="border-t border-border/60 px-2 py-1 text-xs text-muted-foreground">{t('Feed')}</div>
</div>
)}
<div className="min-h-[min(40vh,320px)] min-w-0">
<NoteList
ref={noteListRef}
subRequests={subRequests}
followingFeedDeltaSubRequests={followingFeedDeltaSubRequests}
feedSubscriptionKey={feedSubscriptionKey}
hostPrimaryPageName="profile"
showKinds={profileTimelineShowKinds}
seeAllFeedEvents={feedKindFilterBypass}
withKindFilter
useFilterAsIs
clientSideKindFilter
preserveTimelineOnSubRequestsChange
mergeTimelineWhenSubRequestFiltersMatch
pinnedEventIds={pinnedEventIds}
hideReplies={false}
hideUntrustedNotes={false}
filterMutedNotes={false}
showKind1OPs={showKind1OPs}
showKind1Replies={showKind1Replies}
showKind1111={showKind1111}
showFeedClientFilter
timelinePublicReadFallback={false}
revealBatchSize={48}
/>
</div>
</div>
)
})
ProfileFeedWithPins.displayName = 'ProfileFeedWithPins'
export default ProfileFeedWithPins