52 changed files with 482 additions and 189 deletions
@ -0,0 +1,128 @@
@@ -0,0 +1,128 @@
|
||||
import NoteList, { type TNoteListRef } from '@/components/NoteList' |
||||
import { buildProfilePageReadRelayUrls } from '@/lib/favorites-feed-relays' |
||||
import { computeSpellSubRequestsIdentityKey } from '@/lib/spell-feed-request-identity' |
||||
import { |
||||
applyFauxSpellCapsToSubRequests, |
||||
appendCuratedReadOnlyRelays, |
||||
buildProfileMediaSpellFilter, |
||||
MEDIA_SPELL_KINDS, |
||||
PROFILE_MEDIA_REQ_LIMIT |
||||
} from '@/pages/primary/SpellsPage/fauxSpellFeeds' |
||||
import { normalizeUrl } from '@/lib/url' |
||||
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' |
||||
import client from '@/services/client.service' |
||||
import { NoteCardLoadingSkeleton } from '@/components/NoteCard' |
||||
import { forwardRef, useEffect, useMemo, useState } from 'react' |
||||
import { useTranslation } from 'react-i18next' |
||||
|
||||
function relayListsContentKey(favoriteRelays: string[], blockedRelays: string[]): string { |
||||
const fav = [...favoriteRelays].map((u) => normalizeUrl(u) || u).filter(Boolean).sort().join('\u0001') |
||||
const blk = [...blockedRelays].map((u) => normalizeUrl(u) || u).filter(Boolean).sort().join('\u0001') |
||||
return `${fav}\u0000${blk}` |
||||
} |
||||
|
||||
const ProfileMediaFeed = forwardRef<TNoteListRef, { pubkey: string }>(({ pubkey }, ref) => { |
||||
const { t } = useTranslation() |
||||
const { favoriteRelays, blockedRelays } = useFavoriteRelays() |
||||
const relayListsKey = useMemo( |
||||
() => relayListsContentKey(favoriteRelays, blockedRelays), |
||||
[favoriteRelays, blockedRelays] |
||||
) |
||||
|
||||
/** `null` = still resolving viewed profile NIP-65 + merged relay stack (same as pins / main profile feed). */ |
||||
const [profileRelayUrls, setProfileRelayUrls] = useState<string[] | null>(null) |
||||
|
||||
useEffect(() => { |
||||
const pk = pubkey?.trim() |
||||
if (!pk) { |
||||
setProfileRelayUrls([]) |
||||
return |
||||
} |
||||
let cancelled = false |
||||
setProfileRelayUrls(null) |
||||
void (async () => { |
||||
const authorRl = await client.fetchRelayList(pk).catch(() => ({ |
||||
read: [] as string[], |
||||
write: [] as string[] |
||||
})) |
||||
if (cancelled) return |
||||
setProfileRelayUrls( |
||||
buildProfilePageReadRelayUrls(favoriteRelays, blockedRelays, authorRl, false) |
||||
) |
||||
})() |
||||
return () => { |
||||
cancelled = true |
||||
} |
||||
}, [pubkey, relayListsKey, favoriteRelays, blockedRelays]) |
||||
|
||||
const subRequests = useMemo(() => { |
||||
const pk = pubkey?.trim() |
||||
if (!pk || profileRelayUrls === null) return [] |
||||
const urls = appendCuratedReadOnlyRelays(profileRelayUrls, blockedRelays) |
||||
if (!urls.length) return [] |
||||
return applyFauxSpellCapsToSubRequests([ |
||||
{ urls, filter: buildProfileMediaSpellFilter(pk) } |
||||
]) |
||||
}, [pubkey, profileRelayUrls, blockedRelays]) |
||||
|
||||
const feedSubscriptionKey = useMemo( |
||||
() => computeSpellSubRequestsIdentityKey(subRequests), |
||||
[subRequests] |
||||
) |
||||
|
||||
const showKinds = useMemo(() => [...MEDIA_SPELL_KINDS], []) |
||||
|
||||
if (!pubkey?.trim()) { |
||||
return ( |
||||
<div className="px-4 py-8 text-center text-sm text-muted-foreground"> |
||||
{t('Nothing to load for this feed.')} |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
if (profileRelayUrls === null) { |
||||
return ( |
||||
<div |
||||
className="min-h-[min(40vh,320px)] space-y-2 px-1 py-4" |
||||
role="status" |
||||
aria-live="polite" |
||||
aria-busy="true" |
||||
> |
||||
{Array.from({ length: 4 }).map((_, i) => ( |
||||
<NoteCardLoadingSkeleton key={i} /> |
||||
))} |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
if (!subRequests.length) { |
||||
return ( |
||||
<div className="px-4 py-8 text-center text-sm text-muted-foreground"> |
||||
{t('Nothing to load for this feed.')} |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
return ( |
||||
<div className="min-h-[min(40vh,320px)] min-w-0"> |
||||
<NoteList |
||||
ref={ref} |
||||
subRequests={subRequests} |
||||
feedSubscriptionKey={feedSubscriptionKey} |
||||
showKinds={showKinds} |
||||
useFilterAsIs |
||||
oneShotFetch |
||||
oneShotMergedCap={PROFILE_MEDIA_REQ_LIMIT} |
||||
revealBatchSize={20} |
||||
showKind1OPs |
||||
showKind1Replies |
||||
showKind1111 |
||||
hideReplies={false} |
||||
/> |
||||
</div> |
||||
) |
||||
}) |
||||
|
||||
ProfileMediaFeed.displayName = 'ProfileMediaFeed' |
||||
|
||||
export default ProfileMediaFeed |
||||
Loading…
Reference in new issue