Browse Source

remove the tabs

imwald
Silberengel 2 weeks ago
parent
commit
df2af59b34
  1. 4
      package-lock.json
  2. 2
      package.json
  3. 44
      src/components/FeedFilterToolbarRow/index.tsx
  4. 235
      src/components/NormalFeed/index.tsx
  5. 20
      src/components/Profile/ProfileFeed.tsx
  6. 2
      src/pages/primary/NoteListPage/RelaysFeed.tsx
  7. 2
      src/services/local-storage.service.ts

4
package-lock.json generated

@ -1,12 +1,12 @@
{ {
"name": "imwald", "name": "imwald",
"version": "23.17.5", "version": "23.18.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "imwald", "name": "imwald",
"version": "23.17.5", "version": "23.18.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@asciidoctor/core": "^3.0.4", "@asciidoctor/core": "^3.0.4",

2
package.json

@ -1,6 +1,6 @@
{ {
"name": "imwald", "name": "imwald",
"version": "23.17.5", "version": "23.18.0",
"description": "Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery", "description": "Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery",
"private": true, "private": true,
"type": "module", "type": "module",

44
src/components/FeedFilterToolbarRow/index.tsx

@ -0,0 +1,44 @@
import KindFilter from '@/components/KindFilter'
import { RefreshButton } from '@/components/RefreshButton'
import { cn } from '@/lib/utils'
import type { Ref } from 'react'
/** Sticky/subheader chrome around the feed tool row (home subHeader, in-feed sticky). */
export const feedFilterRowChromeClass =
'w-full min-w-0 border-b border-border/80 bg-background/95 pb-1.5 pt-0.5 backdrop-blur supports-[backdrop-filter]:bg-background/80'
/**
* Single-row feed controls: refresh (optional), kind filter, and 🔍 slot (portaled from {@link NoteList}).
* Use `flex-nowrap` so large text / narrow viewports do not wrap tools onto a second line.
*/
export default function FeedFilterToolbarRow({
showKinds,
onShowKindsChange,
onRefresh,
feedFilterTabRowSlotRef,
includeFeedSearchSlot = false,
className
}: {
showKinds: number[]
onShowKindsChange: (kinds: number[]) => void
onRefresh?: () => void
/** Host element for {@link NoteList} feed-client-filter toggle via portal. */
feedFilterTabRowSlotRef?: Ref<HTMLDivElement>
includeFeedSearchSlot?: boolean
className?: string
}) {
return (
<div
className={cn(
'flex w-full min-w-0 flex-nowrap items-center justify-end gap-0 py-1',
className
)}
>
{onRefresh != null ? <RefreshButton onClick={onRefresh} /> : null}
<KindFilter showKinds={showKinds} onShowKindsChange={onShowKindsChange} />
{includeFeedSearchSlot ? (
<div ref={feedFilterTabRowSlotRef} className="flex shrink-0 flex-nowrap items-center" />
) : null}
</div>
)
}

235
src/components/NormalFeed/index.tsx

@ -1,15 +1,11 @@
import storage from '@/services/local-storage.service' import storage from '@/services/local-storage.service'
import NoteList, { TNoteListRef } from '@/components/NoteList' import NoteList, { TNoteListRef } from '@/components/NoteList'
import { RefreshButton } from '@/components/RefreshButton' import FeedFilterToolbarRow, { feedFilterRowChromeClass } from '@/components/FeedFilterToolbarRow'
import Tabs, { TabDefinition } from '@/components/Tabs'
import { useGlobalRelayBootstrapDefaults } from '@/hooks/use-global-relay-bootstrap-defaults'
import { useKindFilterOrDefaults } from '@/providers/KindFilterProvider' import { useKindFilterOrDefaults } from '@/providers/KindFilterProvider'
import { HOME_GALLERY_TAB_KINDS, FAST_READ_RELAY_URLS } from '@/constants'
import { isWispTrendingNotesRelayUrl } from '@/lib/wisp-trending-relay' import { isWispTrendingNotesRelayUrl } from '@/lib/wisp-trending-relay'
import type { TPrimaryPageName } from '@/PageManager' import type { TPrimaryPageName } from '@/PageManager'
import { TFeedSubRequest, TNoteListMode } from '@/types' import { TFeedSubRequest, TNoteListMode } from '@/types'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { normalizeAnyRelayUrl } from '@/lib/url'
import type { Event } from 'nostr-tools' import type { Event } from 'nostr-tools'
import { import {
forwardRef, forwardRef,
@ -20,32 +16,6 @@ import {
useState, useState,
type ReactNode type ReactNode
} from 'react' } from 'react'
import KindFilter from '../KindFilter'
/**
* Home Gallery: favorites (or chip relays) first, then {@link FAST_READ_RELAY_URLS} so NIP-71 / picture
* events are not starved when the users relay set is mostly text timelines. Deduped by normalized URL.
*/
function galleryRelayUrlsMergedWithReadLayer(
favoriteUrls: readonly string[],
mergeGlobalFastRead: boolean
): string[] {
const seen = new Set<string>()
const out: string[] = []
const add = (raw: string) => {
const n = normalizeAnyRelayUrl(raw.trim()) || raw.trim()
if (!n) return
const k = n.toLowerCase()
if (seen.has(k)) return
seen.add(k)
out.push(n)
}
for (const u of favoriteUrls) add(u)
if (mergeGlobalFastRead) {
for (const u of FAST_READ_RELAY_URLS) add(u)
}
return out
}
const NormalFeed = forwardRef<TNoteListRef, { const NormalFeed = forwardRef<TNoteListRef, {
subRequests: TFeedSubRequest[] subRequests: TFeedSubRequest[]
@ -53,9 +23,9 @@ const NormalFeed = forwardRef<TNoteListRef, {
/** When false, NoteList waits before opening timeline REQs (relay algo probe). */ /** When false, NoteList waits before opening timeline REQs (relay algo probe). */
relayCapabilityReady?: boolean relayCapabilityReady?: boolean
isMainFeed?: boolean isMainFeed?: boolean
/** When set (e.g. on Home), tabs are rendered in layout subHeader instead of in-feed; avoids overlap */ /** When set (e.g. on Home), filter toolbar is rendered in layout subHeader instead of in-feed. */
setSubHeader?: (node: React.ReactNode) => void setSubHeader?: (node: React.ReactNode) => void
/** Shown in the subHeader row to the left of the kind filter (mobile primary feed). */ /** Shown in the subHeader row beside the kind filter (mobile primary feed). */
onSubHeaderRefresh?: () => void onSubHeaderRefresh?: () => void
/** /**
* When true with {@link mergeTimelineWhenSubRequestFiltersMatch}, relay URL list can change (e.g. favorites * When true with {@link mergeTimelineWhenSubRequestFiltersMatch}, relay URL list can change (e.g. favorites
@ -63,15 +33,8 @@ const NormalFeed = forwardRef<TNoteListRef, {
*/ */
preserveTimelineOnSubRequestsChange?: boolean preserveTimelineOnSubRequestsChange?: boolean
mergeTimelineWhenSubRequestFiltersMatch?: boolean mergeTimelineWhenSubRequestFiltersMatch?: boolean
/** Home Replies can widen relays without changing Notes/Gallery. */ /** Home feed: widened relay stack (replies); Notes-only uses {@link subRequests}. */
repliesSubRequests?: TFeedSubRequest[] repliesSubRequests?: TFeedSubRequest[]
/**
* When set on the home main feed, Gallery tab REQ uses this relay stack (same as {@link repliesSubRequests})
* instead of OP-only {@link subRequests} URLs.
*/
mainFeedGalleryRelayUrls?: string[]
/** Main Gallery historically widened with fast read relays; home can opt out to stay favorites+trending only. */
widenMainGalleryRelays?: boolean
/** Home following: second subscribe wave (delta relays / new authors); see {@link NoteList}. */ /** Home following: second subscribe wave (delta relays / new authors); see {@link NoteList}. */
followingFeedDeltaSubRequests?: TFeedSubRequest[] followingFeedDeltaSubRequests?: TFeedSubRequest[]
/** Stable subscription identity; see {@link NoteList} `feedSubscriptionKey`. */ /** Stable subscription identity; see {@link NoteList} `feedSubscriptionKey`. */
@ -113,8 +76,6 @@ const NormalFeed = forwardRef<TNoteListRef, {
oneShotAfterMergeComparator?: (a: Event, b: Event) => number oneShotAfterMergeComparator?: (a: Event, b: Event) => number
extraShouldHideEvent?: (ev: Event) => boolean extraShouldHideEvent?: (ev: Event) => boolean
extraShouldHideRepliesEvent?: (ev: Event) => boolean extraShouldHideRepliesEvent?: (ev: Event) => boolean
/** When set with home Gallery, filters rows (e.g. aggr-only) using the widened relay stack. */
extraShouldHideGalleryEvent?: (ev: Event) => boolean
/** Override default cap for merged one-shot batches (wide d-tag / search merges). */ /** Override default cap for merged one-shot batches (wide d-tag / search merges). */
oneShotMergedCap?: number oneShotMergedCap?: number
/** When every relay in the subscribe wave fails before EOSE, merge a one-shot fetch from default read relays (home multi-relay feeds). */ /** When every relay in the subscribe wave fails before EOSE, merge a one-shot fetch from default read relays (home multi-relay feeds). */
@ -122,12 +83,12 @@ const NormalFeed = forwardRef<TNoteListRef, {
/** When the feed is empty and terminal, {@link NoteList} can show an Alexandria search link (hashtag / d-tag pages). */ /** When the feed is empty and terminal, {@link NoteList} can show an Alexandria search link (hashtag / d-tag pages). */
alexandriaEmptyUrl?: string | null alexandriaEmptyUrl?: string | null
/** /**
* Single-relay explore: only events from that relays live REQ (no session/IDB prime, no prefetch to other relays). * Single-relay explore: only events from that relay's live REQ (no session/IDB prime, no prefetch to other relays).
*/ */
relayAuthoritativeFeedOnly?: boolean relayAuthoritativeFeedOnly?: boolean
/** Home favorites Notes tab: favorites + trending relays for stats / “Seen on”. */ /** Home favorites: favorites + trending relays for stats / "Seen on". */
homeFeedSeenOnAllowlistOp?: string[] homeFeedSeenOnAllowlistOp?: string[]
/** Home favorites Replies / Gallery: adds NIP-65, cache, and HTTP index read relays. */ /** Home replies surface: adds NIP-65, cache, and HTTP index read relays. */
homeFeedSeenOnAllowlistReplies?: string[] homeFeedSeenOnAllowlistReplies?: string[]
}>(function NormalFeed( }>(function NormalFeed(
{ {
@ -140,8 +101,6 @@ const NormalFeed = forwardRef<TNoteListRef, {
preserveTimelineOnSubRequestsChange = false, preserveTimelineOnSubRequestsChange = false,
mergeTimelineWhenSubRequestFiltersMatch = false, mergeTimelineWhenSubRequestFiltersMatch = false,
repliesSubRequests, repliesSubRequests,
mainFeedGalleryRelayUrls,
widenMainGalleryRelays = true,
followingFeedDeltaSubRequests, followingFeedDeltaSubRequests,
feedSubscriptionKey, feedSubscriptionKey,
feedTimelineScopeKey, feedTimelineScopeKey,
@ -162,7 +121,6 @@ const NormalFeed = forwardRef<TNoteListRef, {
oneShotAfterMergeComparator, oneShotAfterMergeComparator,
extraShouldHideEvent, extraShouldHideEvent,
extraShouldHideRepliesEvent, extraShouldHideRepliesEvent,
extraShouldHideGalleryEvent,
oneShotMergedCap, oneShotMergedCap,
timelinePublicReadFallback = false, timelinePublicReadFallback = false,
alexandriaEmptyUrl = null, alexandriaEmptyUrl = null,
@ -172,23 +130,8 @@ const NormalFeed = forwardRef<TNoteListRef, {
}, },
ref ref
) { ) {
const useGlobalRelayBootstrap = useGlobalRelayBootstrapDefaults()
const { showKinds, showKind1OPs, showKind1Replies, showKind1111, feedKindFilterBypass } = const { showKinds, showKind1OPs, showKind1Replies, showKind1111, feedKindFilterBypass } =
useKindFilterOrDefaults() useKindFilterOrDefaults()
const [listMode, setListMode] = useState<TNoteListMode>(() => {
const storedMode = storage.getNoteListMode()
if (isMainFeed) {
if (storedMode === 'posts' || storedMode === 'postsAndReplies') {
return storedMode
}
return 'posts'
}
// Non-main feeds only expose Notes / Replies tabs — ignore stored "media" from the home gallery tab.
if (storedMode === 'posts' || storedMode === 'postsAndReplies') {
return storedMode
}
return 'posts'
})
const internalNoteListRef = useRef<TNoteListRef>(null) const internalNoteListRef = useRef<TNoteListRef>(null)
const noteListRef = ref || internalNoteListRef const noteListRef = ref || internalNoteListRef
const [feedFilterTabRowHost, setFeedFilterTabRowHost] = useState<HTMLDivElement | null>(null) const [feedFilterTabRowHost, setFeedFilterTabRowHost] = useState<HTMLDivElement | null>(null)
@ -196,9 +139,7 @@ const NormalFeed = forwardRef<TNoteListRef, {
setFeedFilterTabRowHost((prev) => (Object.is(prev, node) ? prev : node)) setFeedFilterTabRowHost((prev) => (Object.is(prev, node) ? prev : node))
}, []) }, [])
const MEDIA_KINDS = useMemo(() => [...HOME_GALLERY_TAB_KINDS], []) /** Every shard URL is a nostrarchives Wisp "trending notes" stream — OP-only timeline. */
/** Every shard URL is a nostrarchives Wisp “trending notes” stream — replies/gallery tabs are not applicable. */
const isWispTrendingOnlyFeed = useMemo( const isWispTrendingOnlyFeed = useMemo(
() => () =>
subRequests.length > 0 && subRequests.length > 0 &&
@ -208,70 +149,27 @@ const NormalFeed = forwardRef<TNoteListRef, {
[subRequests] [subRequests]
) )
useEffect(() => { /** Replies feed by default; Wisp trending stays notes-only. Kind filter can hide replies client-side. */
if (!isWispTrendingOnlyFeed) return const listMode: TNoteListMode = isWispTrendingOnlyFeed ? 'posts' : 'postsAndReplies'
setListMode((m) => (m === 'posts' ? m : 'posts'))
}, [isWispTrendingOnlyFeed])
const tabs = useMemo((): TabDefinition[] => { useEffect(() => {
if (isMainFeed && isWispTrendingOnlyFeed) { if (!isMainFeed) return
return [{ value: 'posts', label: 'Notes' }] if (storage.getNoteListMode() === listMode) return
} storage.setNoteListMode(listMode)
const base: TabDefinition[] = [ window.dispatchEvent(new CustomEvent('noteListModeChanged'))
{ value: 'posts', label: 'Notes' }, }, [isMainFeed, listMode])
{ value: 'postsAndReplies', label: 'Replies' }
]
if (isMainFeed) base.push({ value: 'media', label: 'Gallery' })
return base
}, [isMainFeed, isWispTrendingOnlyFeed])
/** Replies may widen relays; Gallery swaps kinds and may use {@link mainFeedGalleryRelayUrls} on home. */
const effectiveSubRequests = useMemo(() => { const effectiveSubRequests = useMemo(() => {
if (listMode === 'postsAndReplies' && repliesSubRequests) { if (listMode === 'postsAndReplies' && repliesSubRequests) {
return repliesSubRequests return repliesSubRequests
} }
if (listMode !== 'media') return subRequests return subRequests
return subRequests.map((req) => ({ }, [listMode, subRequests, repliesSubRequests])
...req,
urls:
isMainFeed && mainFeedGalleryRelayUrls && mainFeedGalleryRelayUrls.length > 0
? mainFeedGalleryRelayUrls
: isMainFeed && widenMainGalleryRelays
? galleryRelayUrlsMergedWithReadLayer(req.urls, useGlobalRelayBootstrap)
: req.urls,
filter: { ...req.filter, kinds: MEDIA_KINDS }
}))
}, [
listMode,
subRequests,
repliesSubRequests,
MEDIA_KINDS,
isMainFeed,
widenMainGalleryRelays,
mainFeedGalleryRelayUrls,
useGlobalRelayBootstrap
])
const noteListExtraShouldHide = useMemo(() => { const noteListExtraShouldHide = useMemo(() => {
if (listMode === 'postsAndReplies') return extraShouldHideRepliesEvent if (listMode === 'postsAndReplies') return extraShouldHideRepliesEvent
if (listMode === 'media' && extraShouldHideGalleryEvent) return extraShouldHideGalleryEvent
return extraShouldHideEvent return extraShouldHideEvent
}, [listMode, extraShouldHideRepliesEvent, extraShouldHideGalleryEvent, extraShouldHideEvent]) }, [listMode, extraShouldHideRepliesEvent, extraShouldHideEvent])
const handleListModeChange = useCallback(
(mode: TNoteListMode | string) => {
const noteListMode = mode as TNoteListMode
setListMode(noteListMode)
if (isMainFeed) {
storage.setNoteListMode(noteListMode)
window.dispatchEvent(new CustomEvent('noteListModeChanged'))
}
if (noteListRef && typeof noteListRef !== 'function') {
noteListRef.current?.scrollToTop('smooth')
}
},
[isMainFeed, noteListRef]
)
const handleShowKindsChange = useCallback((_newShowKinds: number[]) => { const handleShowKindsChange = useCallback((_newShowKinds: number[]) => {
if (noteListRef && typeof noteListRef !== 'function') { if (noteListRef && typeof noteListRef !== 'function') {
@ -298,76 +196,40 @@ const NormalFeed = forwardRef<TNoteListRef, {
/** Include kind picker deps for single-relay chips (kindless REQ + client-side kinds). */ /** Include kind picker deps for single-relay chips (kindless REQ + client-side kinds). */
const subHeaderFilterDepsKey = `${allowKindlessRelayExplore ? 'kle' : 'std'}|${showKindsKey}|${feedKindFilterBypass}|${showAllKindsProp ? 'allProp' : 'k'}` const subHeaderFilterDepsKey = `${allowKindlessRelayExplore ? 'kle' : 'std'}|${showKindsKey}|${feedKindFilterBypass}|${showAllKindsProp ? 'allProp' : 'k'}`
const renderTabsInFeed = !(isMainFeed && setSubHeader) && !allowKindlessRelayExplore const renderFilterToolbarInFeed = !(isMainFeed && setSubHeader)
const mergeFilterWithTabsRow =
showFeedClientFilter && ((isMainFeed && !!setSubHeader) || renderTabsInFeed)
/** Notes / Replies / Gallery switch, plus refresh + kind filter — on Wisp trending only the tool row (no mode tabs). */ const filterToolbarRow = useMemo(
const tabsElement = useMemo(() => { () => (
const kindRowOptions = ( <FeedFilterToolbarRow
<div className="flex shrink-0 flex-nowrap items-center gap-0"> showKinds={showKinds}
{onSubHeaderRefresh != null && <RefreshButton onClick={onSubHeaderRefresh} />} onShowKindsChange={handleShowKindsChange}
<KindFilter showKinds={showKinds} onShowKindsChange={handleShowKindsChange} /> onRefresh={onSubHeaderRefresh}
{mergeFilterWithTabsRow ? ( feedFilterTabRowSlotRef={onFeedFilterTabRowSlotRef}
<div ref={onFeedFilterTabRowSlotRef} className="flex items-center" /> includeFeedSearchSlot={showFeedClientFilter}
) : null}
</div>
)
if (isMainFeed && isWispTrendingOnlyFeed) {
return (
<div className="flex w-full min-w-0 items-center justify-end gap-0 py-1">{kindRowOptions}</div>
)
}
return (
<Tabs
value={listMode}
tabs={tabs}
onTabChange={handleListModeChange}
options={kindRowOptions}
pinnedToLayout={isMainFeed && !!setSubHeader}
/> />
),
[onSubHeaderRefresh, showKinds, handleShowKindsChange, showFeedClientFilter]
) )
}, [
isMainFeed,
isWispTrendingOnlyFeed,
listMode,
tabs,
handleListModeChange,
showKinds,
onSubHeaderRefresh,
handleShowKindsChange,
mergeFilterWithTabsRow
])
const tabRowChromeClass =
'w-full min-w-0 border-b border-border/80 bg-background/95 pb-1.5 pt-0.5 backdrop-blur supports-[backdrop-filter]:bg-background/80'
/** /**
* Push the tab row into {@link PrimaryPageLayout} subHeader. Use `useEffect` (not `useLayoutEffect`) so * Push the filter toolbar into {@link PrimaryPageLayout} subHeader. Use `useEffect` (not `useLayoutEffect`) so
* parent `setHomeSubHeader` runs after paint; synchronous layout updates here caused React #185 * parent `setHomeSubHeader` runs after paint; synchronous layout updates here caused React #185
* (maximum update depth) when navigating onto the home feed after other primaries (e.g. notifications). * (maximum update depth) when navigating onto the home feed after other primaries (e.g. notifications).
* Intentionally omit `tabsElement` from deps covered by `listMode` + `subHeaderFilterDepsKey`. * Intentionally omit `filterToolbarRow` from deps covered by `subHeaderFilterDepsKey`.
* Omit `onSubHeaderRefresh` / `onFeedFilterTabRowSlotRef`: only embedded in `tabsElement`; unstable * Omit `onSubHeaderRefresh` / `onFeedFilterTabRowSlotRef`: only embedded in `filterToolbarRow`; unstable
* identities there would retrigger every render and loop with parent state. * identities there would retrigger every render and loop with parent state.
* Do not clear subHeader between dep updates nulling remounts the filter portal slot and retriggers * Do not clear subHeader between dep updates nulling remounts the filter portal slot and retriggers
* NoteList subscriptions / layout churn on the home feed. * NoteList subscriptions / layout churn on the home feed.
*/ */
useEffect(() => { useEffect(() => {
if (!isMainFeed || !setSubHeader) return if (!isMainFeed || !setSubHeader) return
if (mergeFilterWithTabsRow) { setSubHeader(<div className={feedFilterRowChromeClass}>{filterToolbarRow}</div>)
setSubHeader(<div className={tabRowChromeClass}>{tabsElement}</div>)
} else {
setSubHeader(tabsElement)
}
}, [ }, [
isMainFeed, isMainFeed,
setSubHeader, setSubHeader,
listMode,
isWispTrendingOnlyFeed, isWispTrendingOnlyFeed,
subHeaderFilterDepsKey, subHeaderFilterDepsKey,
allowKindlessRelayExplore, allowKindlessRelayExplore
mergeFilterWithTabsRow
]) ])
useEffect(() => { useEffect(() => {
@ -377,15 +239,10 @@ const NormalFeed = forwardRef<TNoteListRef, {
return ( return (
<> <>
{renderTabsInFeed && {renderFilterToolbarInFeed ? (
(mergeFilterWithTabsRow ? ( <div className={cn('sticky top-0 z-20', feedFilterRowChromeClass)}>{filterToolbarRow}</div>
<div className={cn('sticky top-0 z-20', tabRowChromeClass)}>{tabsElement}</div> ) : null}
) : ( <div className={cn('min-w-0', renderFilterToolbarInFeed ? 'pt-0' : 'pt-2')}>
tabsElement
))}
<div
className={cn('min-w-0', mergeFilterWithTabsRow && renderTabsInFeed ? 'pt-0' : 'pt-2')}
>
<NoteList <NoteList
ref={noteListRef} ref={noteListRef}
showKinds={showKinds} showKinds={showKinds}
@ -406,15 +263,15 @@ const NormalFeed = forwardRef<TNoteListRef, {
homeFeedListMode={isMainFeed ? listMode : undefined} homeFeedListMode={isMainFeed ? listMode : undefined}
homeFeedSeenOnAllowlistOp={homeFeedSeenOnAllowlistOp} homeFeedSeenOnAllowlistOp={homeFeedSeenOnAllowlistOp}
homeFeedSeenOnAllowlistReplies={homeFeedSeenOnAllowlistReplies} homeFeedSeenOnAllowlistReplies={homeFeedSeenOnAllowlistReplies}
gridLayout={listMode === 'media'} useFilterAsIs={useFilterAsIs}
revealBatchSize={listMode === 'media' && isMainFeed ? 96 : undefined} clientSideKindFilter={clientSideKindFilter}
useFilterAsIs={listMode === 'media' ? true : useFilterAsIs} allowKindlessRelayExplore={allowKindlessRelayExplore}
clientSideKindFilter={listMode === 'media' ? false : clientSideKindFilter} showAllKinds={listShowAllKinds}
allowKindlessRelayExplore={listMode === 'media' ? false : allowKindlessRelayExplore}
showAllKinds={listMode === 'media' ? true : listShowAllKinds}
showFeedClientFilter={showFeedClientFilter} showFeedClientFilter={showFeedClientFilter}
hostPrimaryPageName={hostPrimaryPageName} hostPrimaryPageName={hostPrimaryPageName}
feedClientFilterTabRowHost={mergeFilterWithTabsRow ? feedFilterTabRowHost : undefined} feedClientFilterTabRowHost={
showFeedClientFilter ? feedFilterTabRowHost : undefined
}
onSingleRelayKindlessEmpty={onSingleRelayKindlessEmpty} onSingleRelayKindlessEmpty={onSingleRelayKindlessEmpty}
onSingleRelayBrowseEmpty={onSingleRelayBrowseEmpty} onSingleRelayBrowseEmpty={onSingleRelayBrowseEmpty}
feedTopNotice={feedTopNotice} feedTopNotice={feedTopNotice}

20
src/components/Profile/ProfileFeed.tsx

@ -1,7 +1,6 @@
import NoteList, { type TNoteListRef } from '@/components/NoteList' import NoteList, { type TNoteListRef } from '@/components/NoteList'
import NoteCard from '@/components/NoteCard' import NoteCard from '@/components/NoteCard'
import KindFilter from '@/components/KindFilter' import FeedFilterToolbarRow, { feedFilterRowChromeClass } from '@/components/FeedFilterToolbarRow'
import { RefreshButton } from '@/components/RefreshButton'
import { Skeleton } from '@/components/ui/skeleton' import { Skeleton } from '@/components/ui/skeleton'
import { ExtendedKind, PROFILE_FEED_KINDS, PROFILE_TIMELINE_REQ_LIMIT } from '@/constants' import { ExtendedKind, PROFILE_FEED_KINDS, PROFILE_TIMELINE_REQ_LIMIT } from '@/constants'
import { useProfileAuthorFeedSubRequests } from '@/hooks/useProfileAuthorFeedSubRequests' import { useProfileAuthorFeedSubRequests } from '@/hooks/useProfileAuthorFeedSubRequests'
@ -10,6 +9,7 @@ import { useKindFilterOrDefaults } from '@/providers/KindFilterProvider'
import { useDeletedEventSafe } from '@/providers/DeletedEventProvider' import { useDeletedEventSafe } from '@/providers/DeletedEventProvider'
import client from '@/services/client.service' import client from '@/services/client.service'
import { nip19, kinds } from 'nostr-tools' import { nip19, kinds } from 'nostr-tools'
import { cn } from '@/lib/utils'
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react' import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -34,6 +34,10 @@ const ProfileFeed = forwardRef<
}, [showKinds]) }, [showKinds])
const [isRefreshing, setIsRefreshing] = useState(false) const [isRefreshing, setIsRefreshing] = useState(false)
const noteListRef = useRef<TNoteListRef>(null) const noteListRef = useRef<TNoteListRef>(null)
const [feedFilterTabRowHost, setFeedFilterTabRowHost] = useState<HTMLDivElement | null>(null)
const onFeedFilterTabRowSlotRef = useCallback((node: HTMLDivElement | null) => {
setFeedFilterTabRowHost((prev) => (Object.is(prev, node) ? prev : node))
}, [])
const { pinEvents, loadingPins, refreshPins } = useProfilePins(pubkey) const { pinEvents, loadingPins, refreshPins } = useProfilePins(pubkey)
@ -106,9 +110,14 @@ const ProfileFeed = forwardRef<
{t('Refreshing posts...')} {t('Refreshing posts...')}
</div> </div>
)} )}
<div className="mb-2 flex flex-wrap items-center justify-end gap-2 px-2"> <div className={cn('mb-2 px-1', feedFilterRowChromeClass)}>
<RefreshButton onClick={refreshAll} /> <FeedFilterToolbarRow
<KindFilter showKinds={showKinds} onShowKindsChange={handleShowKindsChange} /> showKinds={showKinds}
onShowKindsChange={handleShowKindsChange}
onRefresh={refreshAll}
feedFilterTabRowSlotRef={onFeedFilterTabRowSlotRef}
includeFeedSearchSlot
/>
</div> </div>
{pinEvents.filter((e) => !isEventDeleted(e)).length > 0 && ( {pinEvents.filter((e) => !isEventDeleted(e)).length > 0 && (
<div className="mb-3 space-y-2 px-1" aria-label={t('Pinned posts')}> <div className="mb-3 space-y-2 px-1" aria-label={t('Pinned posts')}>
@ -142,6 +151,7 @@ const ProfileFeed = forwardRef<
showKind1Replies={showKind1Replies} showKind1Replies={showKind1Replies}
showKind1111={showKind1111} showKind1111={showKind1111}
showFeedClientFilter showFeedClientFilter
feedClientFilterTabRowHost={feedFilterTabRowHost}
timelinePublicReadFallback timelinePublicReadFallback
revealBatchSize={48} revealBatchSize={48}
/> />

2
src/pages/primary/NoteListPage/RelaysFeed.tsx

@ -144,8 +144,6 @@ const RelaysFeed = forwardRef<
onSubHeaderRefresh={onSubHeaderRefresh} onSubHeaderRefresh={onSubHeaderRefresh}
preserveTimelineOnSubRequestsChange preserveTimelineOnSubRequestsChange
repliesSubRequests={repliesSubRequests} repliesSubRequests={repliesSubRequests}
mainFeedGalleryRelayUrls={stableReplyRelayUrls}
widenMainGalleryRelays={false}
feedSubscriptionKey="home-all-favorites" feedSubscriptionKey="home-all-favorites"
feedTimelineScopeKey="all-favorites" feedTimelineScopeKey="all-favorites"
homeFeedSeenOnAllowlistOp={homeFeedSeenOnAllowlistOp} homeFeedSeenOnAllowlistOp={homeFeedSeenOnAllowlistOp}

2
src/services/local-storage.service.ts

@ -93,7 +93,7 @@ class LocalStorageService {
private fontSize: TFontSize = DEFAULT_FONT_SIZE private fontSize: TFontSize = DEFAULT_FONT_SIZE
private accounts: TAccount[] = [] private accounts: TAccount[] = []
private currentAccount: TAccount | null = null private currentAccount: TAccount | null = null
private noteListMode: TNoteListMode = 'posts' private noteListMode: TNoteListMode = 'postsAndReplies'
private defaultZapSats: number = DEFAULT_ZAP_SATS private defaultZapSats: number = DEFAULT_ZAP_SATS
private defaultZapComment: string = 'Zap!' private defaultZapComment: string = 'Zap!'
private preferredPaytoCategory: PaytoCategory | null = null private preferredPaytoCategory: PaytoCategory | null = null

Loading…
Cancel
Save