From de8a7768dabab3ca0899e4fe461335daa08459d4 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sat, 4 Apr 2026 11:41:03 +0200 Subject: [PATCH] remove feed switcher --- src/components/NoteList/index.tsx | 2 +- src/lib/following-feed-delta.ts | 65 ------ .../primary/NoteListPage/FollowingFeed.tsx | 199 ------------------ src/pages/primary/NoteListPage/index.tsx | 54 +---- src/providers/FeedProvider.tsx | 54 ++--- src/types/index.d.ts | 2 +- 6 files changed, 24 insertions(+), 352 deletions(-) delete mode 100644 src/lib/following-feed-delta.ts delete mode 100644 src/pages/primary/NoteListPage/FollowingFeed.tsx diff --git a/src/components/NoteList/index.tsx b/src/components/NoteList/index.tsx index 31977afe..8db129a3 100644 --- a/src/components/NoteList/index.tsx +++ b/src/components/NoteList/index.tsx @@ -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). */ diff --git a/src/lib/following-feed-delta.ts b/src/lib/following-feed-delta.ts deleted file mode 100644 index 621434c1..00000000 --- a/src/lib/following-feed-delta.ts +++ /dev/null @@ -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 { - const s = new Set() - 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() - - 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 -} diff --git a/src/pages/primary/NoteListPage/FollowingFeed.tsx b/src/pages/primary/NoteListPage/FollowingFeed.tsx deleted file mode 100644 index 123a01ff..00000000 --- a/src/pages/primary/NoteListPage/FollowingFeed.tsx +++ /dev/null @@ -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([]) - const [deltaSubRequests, setDeltaSubRequests] = useState([]) - - 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( - () => ( -

- {t('Home trending slice notice')} -

- ), - [t] - ) - - return ( - - ) -}) - -FollowingFeed.displayName = 'FollowingFeed' -export default FollowingFeed diff --git a/src/pages/primary/NoteListPage/index.tsx b/src/pages/primary/NoteListPage/index.tsx index b57e1293..aa3d94ac 100644 --- a/src/pages/primary/NoteListPage/index.tsx +++ b/src/pages/primary/NoteListPage/index.tsx @@ -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 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((_, ref) => { const { addRelayUrls, removeRelayUrls } = useCurrentRelays() const layoutRef = useRef(null) const feedRef = useRef(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((_, 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]) + feedRef.current?.refresh() + }, []) useImperativeHandle( ref, @@ -70,7 +61,6 @@ const NoteListPage = forwardRef((_, 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((_, ref) => { ))} ) - } else if (feedInfo.feedType === 'following' && !pubkey) { - content = ( -
- -
- ) - } else if (feedInfo.feedType === 'bookmarks') { - if (!pubkey) { - content = ( -
- -
- ) - } else { - content = - } - } else if (feedInfo.feedType === 'following') { - content = ( - - ) } else { content = ( <> @@ -157,13 +119,9 @@ const NoteListPage = forwardRef((_, ref) => { const feedPageTitle = useMemo( () => - feedInfo.feedType === 'following' - ? t('Following') - : feedInfo.feedType === 'bookmarks' - ? t('Bookmarks') - : feedInfo.feedType === 'relays' - ? t('relayType_relay_set') - : t('Favorite Relays'), + feedInfo.feedType === 'relays' + ? t('relayType_relay_set') + : t('Favorite Relays'), [feedInfo.feedType, t] ) diff --git a/src/providers/FeedProvider.tsx b/src/providers/FeedProvider.tsx index 66df1cdb..1f546541 100644 --- a/src/providers/FeedProvider.tsx +++ b/src/providers/FeedProvider.tsx @@ -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 }) { 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 }) { } } + // 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 }) { 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') diff --git a/src/types/index.d.ts b/src/types/index.d.ts index c7f0498d..ce99676f 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -160,7 +160,7 @@ export type TAccount = { export type TAccountPointer = Pick -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'