Browse Source

remove feed switcher

imwald
Silberengel 4 weeks ago
parent
commit
de8a7768da
  1. 2
      src/components/NoteList/index.tsx
  2. 65
      src/lib/following-feed-delta.ts
  3. 199
      src/pages/primary/NoteListPage/FollowingFeed.tsx
  4. 48
      src/pages/primary/NoteListPage/index.tsx
  5. 54
      src/providers/FeedProvider.tsx
  6. 2
      src/types/index.d.ts

2
src/components/NoteList/index.tsx

@ -408,7 +408,7 @@ const NoteList = forwardRef( @@ -408,7 +408,7 @@ const NoteList = forwardRef(
/**
* When true, load events with parallel {@link client.fetchEvents} per subRequest instead of
* {@link client.subscribeTimeline}. No live stream or `loadMore` timeline pagination; use for faux spells
* (except Following). Refresh re-fetches.
* Refresh re-fetches.
*/
oneShotFetch = false,
/** Override {@link client.fetchEvents} / query global timeout (default 14s). */

65
src/lib/following-feed-delta.ts

@ -1,65 +0,0 @@ @@ -1,65 +0,0 @@
import { stableSpellFeedFilterKey } from '@/lib/spell-feed-request-identity'
import type { TFeedSubRequest } from '@/types'
import { normalizeUrl, subtractNormalizedRelayUrls } from '@/lib/url'
import type { Filter } from 'nostr-tools'
function normalizedRelayUrlSet(requests: TFeedSubRequest[]): Set<string> {
const s = new Set<string>()
for (const r of requests) {
for (const u of r.urls) {
const n = normalizeUrl(u) || u.trim()
if (n) s.add(n)
}
}
return s
}
function dedupeShardKey(urls: string[], filter: Filter): string {
const nu = [...urls].map((u) => normalizeUrl(u) || u).filter(Boolean).sort()
return `${nu.join('\0')}|${stableSpellFeedFilterKey(filter)}`
}
/**
* Second-wave REQ shards for the home following feed: relays and/or author groups not covered by the
* provisional (kind-3 tags) subscription. Keeps the first subscription open and avoids "closed by caller" churn.
*/
export function buildFollowingFeedDeltaSubRequests(
fullAugmented: TFeedSubRequest[],
provisionalAugmented: TFeedSubRequest[],
provisionalAuthorHexes: string[]
): TFeedSubRequest[] {
if (fullAugmented.length === 0) return []
const rProv = normalizedRelayUrlSet(provisionalAugmented)
const rProvList = [...rProv]
const aProv = new Set(provisionalAuthorHexes.map((p) => p.toLowerCase()))
const out: TFeedSubRequest[] = []
const seen = new Set<string>()
for (const req of fullAugmented) {
const filter = req.filter as Filter
const authorsRaw = Array.isArray(filter.authors) ? filter.authors : []
const authors = authorsRaw.map((x) => (typeof x === 'string' ? x.toLowerCase() : x)) as string[]
const uDelta = subtractNormalizedRelayUrls(req.urls, rProvList)
const authorsNew = authors.filter((a) => typeof a === 'string' && a.length === 64 && !aProv.has(a))
const pushIfNew = (urls: string[], f: Filter) => {
if (urls.length === 0) return
const k = dedupeShardKey(urls, f)
if (seen.has(k)) return
seen.add(k)
out.push({ ...req, urls, filter: f })
}
if (uDelta.length > 0) {
pushIfNew(uDelta, { ...filter, authors } as Filter)
}
if (authorsNew.length > 0) {
pushIfNew(req.urls, { ...filter, authors: authorsNew } as Filter)
}
}
return out
}

199
src/pages/primary/NoteListPage/FollowingFeed.tsx

@ -1,199 +0,0 @@ @@ -1,199 +0,0 @@
import NormalFeed from '@/components/NormalFeed'
import type { TNoteListRef } from '@/components/NoteList'
import {
augmentSubRequestsWithFavoritesFastReadAndInbox,
userReadRelaysWithHttp
} from '@/lib/favorites-feed-relays'
import { buildFollowingFeedDeltaSubRequests } from '@/lib/following-feed-delta'
import { getPubkeysFromPTags } from '@/lib/tag'
import { normalizeUrl } from '@/lib/url'
import logger from '@/lib/logger'
import { useFeed } from '@/providers/FeedProvider'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useNostr } from '@/providers/NostrProvider'
import {
buildWispTrendingNotesRelayUrl,
WISP_TRENDING_FEED_KINDS
} from '@/lib/wisp-trending-relay'
import client from '@/services/client.service'
import { TFeedSubRequest } from '@/types'
import type { ReactNode } from 'react'
import { forwardRef, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
const FollowingFeed = forwardRef<
TNoteListRef,
{
setSubHeader?: (node: ReactNode) => void
onSubHeaderRefresh?: () => void
}
>(function FollowingFeed({ setSubHeader, onSubHeaderRefresh }, ref) {
const { t, i18n } = useTranslation()
const { pubkey, relayList, followListEvent } = useNostr()
const { favoriteRelays, blockedRelays } = useFavoriteRelays()
const { feedInfo } = useFeed()
const [subRequests, setSubRequests] = useState<TFeedSubRequest[]>([])
const [deltaSubRequests, setDeltaSubRequests] = useState<TFeedSubRequest[]>([])
const favoriteRelaysKey = useMemo(
() =>
[...favoriteRelays]
.map((u) => normalizeUrl(u) || u)
.filter(Boolean)
.sort()
.join('\0'),
[favoriteRelays]
)
const blockedRelaysKey = useMemo(
() =>
[...blockedRelays]
.map((u) => normalizeUrl(u) || u)
.filter(Boolean)
.sort()
.join('\0'),
[blockedRelays]
)
const relayReadKey = useMemo(
() =>
[...userReadRelaysWithHttp(relayList)]
.map((u) => normalizeUrl(u) || u)
.filter(Boolean)
.sort()
.join('\0'),
[relayList]
)
const relayWriteKey = useMemo(
() =>
[...(relayList?.write ?? [])]
.map((u) => normalizeUrl(u) || u)
.filter(Boolean)
.sort()
.join('\0'),
[relayList?.write]
)
const followingFeedSubscriptionKey = useMemo(
() => (pubkey ? `home-following:${pubkey.toLowerCase()}` : undefined),
[pubkey]
)
useEffect(() => {
let cancelled = false
async function init() {
if (feedInfo.feedType !== 'following' || !pubkey) {
setSubRequests([])
setDeltaSubRequests([])
return
}
setDeltaSubRequests([])
const augment = (raw: TFeedSubRequest[]) =>
augmentSubRequestsWithFavoritesFastReadAndInbox(
raw,
favoriteRelays,
blockedRelays,
userReadRelaysWithHttp(relayList),
{ userWriteRelays: relayList?.write ?? [] }
)
const trendingRelayUrl = buildWispTrendingNotesRelayUrl()
const wispTrendingShard: TFeedSubRequest = {
urls: [trendingRelayUrl],
filter: { kinds: [...WISP_TRENDING_FEED_KINDS], limit: 100 },
reasonLabel: t('Trending on Nostr'),
reasonLabelIfSeenOnRelay: trendingRelayUrl
}
const appendTrending = (batch: TFeedSubRequest[]) => [...batch, wispTrendingShard]
const fromTags = followListEvent ? getPubkeysFromPTags(followListEvent.tags) : []
const provisionalAuthors = [...new Set([pubkey, ...fromTags])]
const provisionalAuthorLower = provisionalAuthors.map((p) => p.toLowerCase())
let rawProv: TFeedSubRequest[] = []
try {
rawProv = await client.generateSubRequestsForPubkeys(provisionalAuthors, pubkey)
} catch (error) {
logger.warn('[FollowingFeed] provisional generateSubRequestsForPubkeys failed', { error })
}
const provAugCore = augment(rawProv)
const provAug = appendTrending(provAugCore)
if (!cancelled) setSubRequests(provAug)
let followings: string[] = fromTags
try {
followings = await client.fetchFollowings(pubkey)
} catch (error) {
followings = followListEvent ? getPubkeysFromPTags(followListEvent.tags) : []
logger.warn('[FollowingFeed] fetchFollowings failed; using cached follow list fallback', {
error,
fallbackCount: followings.length
})
}
const fullAuthors = [...new Set([pubkey, ...followings])]
try {
const rawFull = await client.generateSubRequestsForPubkeys(fullAuthors, pubkey)
if (cancelled) return
const fullAugCore = augment(rawFull)
const delta = buildFollowingFeedDeltaSubRequests(fullAugCore, provAugCore, provisionalAuthorLower)
if (!cancelled) {
setDeltaSubRequests(delta)
if (delta.length > 0) {
logger.info('[FollowingFeed] delta wave subRequests', {
deltaShardCount: delta.length,
provisionalShardCount: provAugCore.length,
fullShardCount: fullAugCore.length
})
}
}
} catch (error) {
logger.error('[FollowingFeed] full generateSubRequestsForPubkeys failed', error)
if (!cancelled) setDeltaSubRequests([])
}
}
void init()
return () => {
cancelled = true
}
}, [
feedInfo.feedType,
pubkey,
followListEvent?.id,
favoriteRelaysKey,
blockedRelaysKey,
relayReadKey,
relayWriteKey,
i18n.language
])
const trendingFeedNotice = useMemo(
() => (
<p className="mb-2 px-1 text-xs text-muted-foreground leading-snug">
{t('Home trending slice notice')}
</p>
),
[t]
)
return (
<NormalFeed
ref={ref}
subRequests={subRequests}
followingFeedDeltaSubRequests={deltaSubRequests}
feedSubscriptionKey={followingFeedSubscriptionKey}
preserveTimelineOnSubRequestsChange
isMainFeed
setSubHeader={setSubHeader}
onSubHeaderRefresh={onSubHeaderRefresh}
showFeedClientFilter={false}
hostPrimaryPageName="feed"
feedTopNotice={trendingFeedNotice}
/>
)
})
FollowingFeed.displayName = 'FollowingFeed'
export default FollowingFeed

48
src/pages/primary/NoteListPage/index.tsx

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
import BookmarkList from '@/components/BookmarkList'
import RelayInfo from '@/components/RelayInfo'
import { RefreshButton } from '@/components/RefreshButton'
import { Button } from '@/components/ui/button'
@ -27,7 +26,6 @@ import { FavoriteRelaysActiveStripMobileBar } from '@/components/FavoriteRelaysA @@ -27,7 +26,6 @@ import { FavoriteRelaysActiveStripMobileBar } from '@/components/FavoriteRelaysA
import FavoriteRelaysFeedPicker from '@/components/FavoriteRelaysFeedPicker'
import HelpAndAccountMenu from '@/components/HelpAndAccountMenu'
import Logo from '@/assets/Logo'
import FollowingFeed from './FollowingFeed'
import RelaysFeed from './RelaysFeed'
import { usePrimaryPage } from '@/contexts/primary-page-context'
import { usePrimaryNoteView } from '@/contexts/primary-note-view-context'
@ -36,8 +34,6 @@ const NoteListPage = forwardRef<TPageRef>((_, ref) => { @@ -36,8 +34,6 @@ const NoteListPage = forwardRef<TPageRef>((_, ref) => {
const { addRelayUrls, removeRelayUrls } = useCurrentRelays()
const layoutRef = useRef<TPageRef>(null)
const feedRef = useRef<TNoteListRef>(null)
const bookmarkRef = useRef<{ refresh: () => void }>(null)
const { pubkey, checkLogin } = useNostr()
const { feedInfo, relayUrls, isReady } = useFeed()
const { isSmallScreen } = useScreenSize()
const [showRelayDetails, setShowRelayDetails] = useState(false)
@ -46,16 +42,11 @@ const NoteListPage = forwardRef<TPageRef>((_, ref) => { @@ -46,16 +42,11 @@ const NoteListPage = forwardRef<TPageRef>((_, ref) => {
const usesSubHeader =
feedInfo.feedType === 'relay' ||
feedInfo.feedType === 'relays' ||
feedInfo.feedType === 'all-favorites' ||
feedInfo.feedType === 'following'
feedInfo.feedType === 'all-favorites'
const runFeedRefresh = useCallback(() => {
if (feedInfo.feedType === 'bookmarks') {
void bookmarkRef.current?.refresh()
} else {
feedRef.current?.refresh()
}
}, [feedInfo.feedType])
}, [])
useImperativeHandle(
ref,
@ -70,7 +61,6 @@ const NoteListPage = forwardRef<TPageRef>((_, ref) => { @@ -70,7 +61,6 @@ const NoteListPage = forwardRef<TPageRef>((_, ref) => {
setHomeSubHeader(node)
}, [])
// Clear subHeader when switching to a feed that doesn't use it (e.g. bookmarks)
useEffect(() => {
if (!usesSubHeader) setHomeSubHeader(null)
}, [usesSubHeader])
@ -106,34 +96,6 @@ const NoteListPage = forwardRef<TPageRef>((_, ref) => { @@ -106,34 +96,6 @@ const NoteListPage = forwardRef<TPageRef>((_, ref) => {
))}
</div>
)
} else if (feedInfo.feedType === 'following' && !pubkey) {
content = (
<div className="flex justify-center w-full">
<Button size="lg" onClick={() => checkLogin()}>
{t('Please login to view following feed')}
</Button>
</div>
)
} else if (feedInfo.feedType === 'bookmarks') {
if (!pubkey) {
content = (
<div className="flex justify-center w-full">
<Button size="lg" onClick={() => checkLogin()}>
{t('Please login to view bookmarks')}
</Button>
</div>
)
} else {
content = <BookmarkList ref={bookmarkRef} />
}
} else if (feedInfo.feedType === 'following') {
content = (
<FollowingFeed
ref={feedRef}
setSubHeader={setHomeSubHeaderStable}
onSubHeaderRefresh={runFeedRefresh}
/>
)
} else {
content = (
<>
@ -157,11 +119,7 @@ const NoteListPage = forwardRef<TPageRef>((_, ref) => { @@ -157,11 +119,7 @@ const NoteListPage = forwardRef<TPageRef>((_, ref) => {
const feedPageTitle = useMemo(
() =>
feedInfo.feedType === 'following'
? t('Following')
: feedInfo.feedType === 'bookmarks'
? t('Bookmarks')
: feedInfo.feedType === 'relays'
feedInfo.feedType === 'relays'
? t('relayType_relay_set')
: t('Favorite Relays'),
[feedInfo.feedType, t]

54
src/providers/FeedProvider.tsx

@ -100,20 +100,6 @@ export function FeedProvider({ children }: { children: React.ReactNode }) { @@ -100,20 +100,6 @@ export function FeedProvider({ children }: { children: React.ReactNode }) {
setIsReady(true)
return
}
if (feedType === 'following') {
if (!options.pubkey) {
setIsReady(true)
return
}
const newFeedInfo = { feedType }
setFeedInfo(newFeedInfo)
feedInfoRef.current = newFeedInfo
storage.setFeedInfo(newFeedInfo, pubkey)
setRelayUrls([])
setIsReady(true)
return
}
if (feedType === 'all-favorites') {
const finalRelays = getFavoritesFeedRelayUrls(favoriteRelays, blockedRelays)
logger.debug('Switching to all-favorites, finalRelays:', finalRelays)
@ -127,21 +113,6 @@ export function FeedProvider({ children }: { children: React.ReactNode }) { @@ -127,21 +113,6 @@ export function FeedProvider({ children }: { children: React.ReactNode }) {
setIsReady(true)
return
}
if (feedType === 'bookmarks') {
if (!options.pubkey) {
setIsReady(true)
return
}
const newFeedInfo = { feedType }
setFeedInfo(newFeedInfo)
feedInfoRef.current = newFeedInfo
storage.setFeedInfo(newFeedInfo, pubkey)
setRelayUrls([])
setIsReady(true)
return
}
setIsReady(true)
}, [pubkey, favoriteRelays, blockedRelays, relaySets])
@ -187,6 +158,22 @@ export function FeedProvider({ children }: { children: React.ReactNode }) { @@ -187,6 +158,22 @@ export function FeedProvider({ children }: { children: React.ReactNode }) {
}
}
// Pre-rewrite main feeds (`following`, `bookmarks`) are no longer supported; migrate persisted state.
const storedFeedType = (feedInfo as { feedType?: string }).feedType
const deprecatedMainFeed = storedFeedType === 'following' || storedFeedType === 'bookmarks'
if (deprecatedMainFeed) {
const previousMainFeed = storedFeedType
const migrated: TFeedInfo = { feedType: 'all-favorites' }
feedInfo = migrated
if (pubkey) {
storage.setFeedInfo(migrated, pubkey)
}
logger.info('[FeedProvider] Migrated deprecated feed type to all-favorites', {
previous: previousMainFeed
})
return await switchFeed('all-favorites')
}
if (feedInfo.feedType === 'relays') {
return await switchFeed('relays', { activeRelaySetId: feedInfo.id })
}
@ -201,15 +188,6 @@ export function FeedProvider({ children }: { children: React.ReactNode }) { @@ -201,15 +188,6 @@ export function FeedProvider({ children }: { children: React.ReactNode }) {
return await switchFeed('relay', { relay: feedInfo.id })
}
// update following feed if pubkey changes
if (feedInfo.feedType === 'following' && pubkey) {
return await switchFeed('following', { pubkey })
}
if (feedInfo.feedType === 'bookmarks' && pubkey) {
return await switchFeed('bookmarks', { pubkey })
}
if (feedInfo.feedType === 'all-favorites') {
logger.debug('Initializing all-favorites feed')
return await switchFeed('all-favorites')

2
src/types/index.d.ts vendored

@ -160,7 +160,7 @@ export type TAccount = { @@ -160,7 +160,7 @@ export type TAccount = {
export type TAccountPointer = Pick<TAccount, 'pubkey' | 'signerType'>
export type TFeedType = 'following' | 'relays' | 'relay' | 'bookmarks' | 'all-favorites'
export type TFeedType = 'relays' | 'relay' | 'all-favorites'
export type TFeedInfo = { feedType: TFeedType; id?: string }
export type TLanguage = 'en' | 'zh' | 'pl'

Loading…
Cancel
Save