Browse Source

more refactor

imwald
Silberengel 1 month ago
parent
commit
6d38d19df9
  1. 385
      src/components/LatestFromFollowsSection/index.tsx
  2. 9
      src/i18n/locales/de.ts
  3. 9
      src/i18n/locales/en.ts
  4. 12
      src/lib/event.ts
  5. 2
      src/pages/primary/SearchPage/index.tsx
  6. 40
      src/pages/primary/SpellsPage/fauxSpellFeeds.ts
  7. 43
      src/pages/primary/SpellsPage/index.tsx
  8. 2
      src/pages/secondary/SearchPage/index.tsx

385
src/components/LatestFromFollowsSection/index.tsx

@ -0,0 +1,385 @@ @@ -0,0 +1,385 @@
import NoteCard from '@/components/NoteCard'
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
import {
FAST_READ_RELAY_URLS,
FAST_WRITE_RELAY_URLS,
ExtendedKind,
SEARCHABLE_RELAY_URLS
} from '@/constants'
import { shouldFilterEvent } from '@/lib/event-filtering'
import { toProfile } from '@/lib/link'
import { getPubkeysFromPTags } from '@/lib/tag'
import { cn } from '@/lib/utils'
import { normalizeUrl } from '@/lib/url'
import { useSecondaryPage } from '@/PageManager'
import { useDeletedEvent } from '@/providers/DeletedEventProvider'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useMuteList } from '@/providers/MuteListProvider'
import { useNostr } from '@/providers/NostrProvider'
import { useUserTrust } from '@/providers/UserTrustProvider'
import { queryService, replaceableEventService } from '@/services/client.service'
import logger from '@/lib/logger'
import { ChevronDown, ChevronRight, Loader2, Star } from 'lucide-react'
import { Event, kinds, nip19, NostrEvent } from 'nostr-tools'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { FormattedTimestamp } from '../FormattedTimestamp'
import UserAvatar from '../UserAvatar'
import Username from '../Username'
/** Curated follow list for guests (hex from npub). */
export const RECOMMENDED_FOLLOW_CURATOR_NPUB =
'npub1m4ny6hjqzepn4rxknuq94c2gpqzr29ufkkw7ttcxyak7v43n6vvsajc2jl' as const
const MAX_FOLLOWS = 1000
const AUTHORS_PER_BATCH = 12
const MAX_POSTS_PER_AUTHOR = 5
/** Enough headroom to often fill 5 notes per author in a batch. */
const BATCH_EVENT_LIMIT = 200
const FEED_KINDS = [
kinds.ShortTextNote,
ExtendedKind.DISCUSSION,
kinds.LongFormArticle,
kinds.Highlights,
ExtendedKind.PICTURE,
ExtendedKind.VIDEO,
ExtendedKind.SHORT_VIDEO,
ExtendedKind.COMMENT,
kinds.Repost
] as number[]
const feedKindSet = new Set(FEED_KINDS)
function mergeBatchPosts(
prev: Map<string, NostrEvent[]>,
incoming: NostrEvent[],
batchAuthors: string[]
): Map<string, NostrEvent[]> {
const next = new Map(prev)
const authorSet = new Set(batchAuthors)
const filtered = incoming.filter((e) => authorSet.has(e.pubkey))
for (const pk of batchAuthors) {
const prevList = next.get(pk) ?? []
const newForPk = filtered.filter((e) => e.pubkey === pk)
const byId = new Map<string, NostrEvent>()
for (const e of prevList) byId.set(e.id, e)
for (const e of newForPk) {
const ex = byId.get(e.id)
if (!ex || e.created_at >= ex.created_at) byId.set(e.id, e)
}
const sorted = [...byId.values()]
.sort((a, b) => b.created_at - a.created_at)
.slice(0, MAX_POSTS_PER_AUTHOR)
next.set(pk, sorted)
}
return next
}
function recommendedCuratorHexPubkey(): string | null {
try {
const dec = nip19.decode(RECOMMENDED_FOLLOW_CURATOR_NPUB)
if (dec.type !== 'npub') return null
return dec.data
} catch {
return null
}
}
export default function LatestFromFollowsSection() {
const { t } = useTranslation()
const { push } = useSecondaryPage()
const { pubkey, followListEvent, isInitialized, relayList } = useNostr()
const { favoriteRelays, blockedRelays } = useFavoriteRelays()
const { mutePubkeySet } = useMuteList()
const { isEventDeleted } = useDeletedEvent()
const { hideUntrustedNotes, isUserTrusted } = useUserTrust()
const loggedInFollowPubkeys = useMemo(() => {
if (!pubkey || !isInitialized) return null
return getPubkeysFromPTags(followListEvent?.tags ?? []).slice(0, MAX_FOLLOWS)
}, [pubkey, isInitialized, followListEvent])
const [guestFollowPubkeys, setGuestFollowPubkeys] = useState<string[]>([])
const [guestListReady, setGuestListReady] = useState(false)
const [postsByPubkey, setPostsByPubkey] = useState<Map<string, NostrEvent[]>>(() => new Map())
const [batchBusy, setBatchBusy] = useState(false)
const [sectionOpen, setSectionOpen] = useState(true)
const abortedRef = useRef(false)
const followPubkeys = pubkey ? (loggedInFollowPubkeys ?? []) : guestFollowPubkeys
const followsLabel: 'self' | 'recommended' = pubkey ? 'self' : 'recommended'
const loadingFollowList = !pubkey && isInitialized && !guestListReady
const searchRelays = useMemo(() => {
const relays: string[] = []
if (relayList) {
relays.push(...(relayList.read || []), ...(relayList.write || []))
}
relays.push(...(favoriteRelays || []))
relays.push(...FAST_READ_RELAY_URLS, ...FAST_WRITE_RELAY_URLS, ...SEARCHABLE_RELAY_URLS)
const normalized = Array.from(
new Set(relays.map((url) => normalizeUrl(url) || url).filter((url): url is string => !!url))
)
return normalized.filter((relay) => !blockedRelays.some((blocked) => relay.includes(blocked)))
}, [relayList, favoriteRelays, blockedRelays])
const acceptEvent = useCallback(
(e: Event) => {
if (!feedKindSet.has(e.kind)) return false
if (isEventDeleted(e)) return false
if (shouldFilterEvent(e)) return false
if (mutePubkeySet.has(e.pubkey)) return false
if (hideUntrustedNotes && !isUserTrusted(e.pubkey)) return false
return true
},
[hideUntrustedNotes, isEventDeleted, isUserTrusted, mutePubkeySet]
)
// Guest: load curated follow list from npub; logged-in list comes from useMemo above.
useEffect(() => {
if (!isInitialized) return
if (pubkey) {
setGuestFollowPubkeys([])
setGuestListReady(false)
return
}
let cancelled = false
setGuestListReady(false)
setGuestFollowPubkeys([])
;(async () => {
const hex = recommendedCuratorHexPubkey()
if (!hex) {
if (!cancelled) {
setGuestFollowPubkeys([])
setGuestListReady(true)
}
return
}
try {
const evt = await replaceableEventService.fetchReplaceableEvent(hex, kinds.Contacts)
if (cancelled) return
const list = evt ? getPubkeysFromPTags(evt.tags).slice(0, MAX_FOLLOWS) : []
setGuestFollowPubkeys(list)
} catch (err) {
logger.warn('[LatestFromFollows] Failed to load recommended follow list', err)
if (!cancelled) setGuestFollowPubkeys([])
} finally {
if (!cancelled) setGuestListReady(true)
}
})()
return () => {
cancelled = true
}
}, [isInitialized, pubkey])
// Batch-fetch posts per slice of authors; update UI after each batch.
useEffect(() => {
if (!isInitialized || loadingFollowList) return
if (followPubkeys.length === 0) return
abortedRef.current = false
let cancelled = false
const run = async () => {
setBatchBusy(true)
setPostsByPubkey(new Map())
for (let i = 0; i < followPubkeys.length; i += AUTHORS_PER_BATCH) {
if (cancelled || abortedRef.current) break
const batch = followPubkeys.slice(i, i + AUTHORS_PER_BATCH)
try {
const raw = await queryService.fetchEvents(
searchRelays,
{
kinds: [...FEED_KINDS],
authors: batch,
limit: BATCH_EVENT_LIMIT
},
{ eoseTimeout: 2800, globalTimeout: 9000 }
)
if (cancelled || abortedRef.current) break
const filtered = raw.filter((e) => acceptEvent(e))
setPostsByPubkey((prev) => mergeBatchPosts(prev, filtered, batch))
} catch (err) {
logger.warn('[LatestFromFollows] Batch fetch failed', { err, batchSize: batch.length })
}
}
if (!cancelled) setBatchBusy(false)
}
void run()
return () => {
cancelled = true
abortedRef.current = true
setBatchBusy(false)
}
}, [followPubkeys, searchRelays, loadingFollowList, isInitialized, acceptEvent])
const sortedRowPubkeys = useMemo(() => {
const withPosts = followPubkeys.filter((pk) => (postsByPubkey.get(pk)?.length ?? 0) > 0)
const withoutPosts = followPubkeys.filter((pk) => (postsByPubkey.get(pk)?.length ?? 0) === 0)
withPosts.sort((a, b) => {
const ta = postsByPubkey.get(a)?.[0]?.created_at ?? 0
const tb = postsByPubkey.get(b)?.[0]?.created_at ?? 0
return tb - ta
})
return [...withPosts, ...withoutPosts]
}, [followPubkeys, postsByPubkey])
const heading =
followsLabel === 'recommended'
? t('Latest from our recommended follows')
: t('Latest from your follows')
if (!isInitialized) {
return null
}
if (loadingFollowList) {
return (
<div className="mb-6 flex items-center gap-2 text-sm text-muted-foreground">
<Loader2 className="size-4 animate-spin" />
{t('Loading follow list…')}
</div>
)
}
if (followPubkeys.length === 0) {
return (
<div className="mb-6 rounded-lg border border-border/80 bg-muted/20 px-4 py-3 text-sm text-muted-foreground">
{followsLabel === 'recommended'
? t('Could not load recommended follows')
: t('Your follow list is empty')}
</div>
)
}
return (
<Collapsible open={sectionOpen} onOpenChange={setSectionOpen} className="mb-6 min-w-0">
<CollapsibleTrigger className="flex w-full items-center justify-between gap-2 rounded-lg border border-border/80 bg-muted/15 px-3 py-2.5 text-left hover:bg-muted/25">
<span className="text-base font-semibold">{heading}</span>
<ChevronDown
className={cn('size-5 shrink-0 text-muted-foreground transition-transform', sectionOpen && 'rotate-180')}
/>
</CollapsibleTrigger>
<CollapsibleContent className="overflow-hidden">
<div className="mt-2 space-y-0 rounded-lg border border-border/60 overflow-hidden">
{batchBusy && postsByPubkey.size === 0 ? (
<div className="flex items-center gap-2 px-4 py-6 text-sm text-muted-foreground">
<Loader2 className="size-4 animate-spin" />
{t('Loading recent posts from follows…')}
</div>
) : null}
{sortedRowPubkeys.map((pk) => {
const posts = postsByPubkey.get(pk) ?? []
const count = posts.length
const latest = posts[0]?.created_at
return (
<FollowPulseRow
key={pk}
pubkey={pk}
count={count}
latestCreatedAt={latest}
posts={posts}
onOpenProfile={() => push(toProfile(pk))}
/>
)
})}
</div>
{batchBusy && postsByPubkey.size > 0 ? (
<div className="mt-2 flex items-center gap-2 text-xs text-muted-foreground px-1">
<Loader2 className="size-3 animate-spin" />
{t('Loading more…')}
</div>
) : null}
</CollapsibleContent>
</Collapsible>
)
}
function FollowRowEmptyPosts() {
const { t } = useTranslation()
return (
<div className="px-4 py-3 text-sm text-muted-foreground">
{t('No recent posts from this user in the current fetch')}
</div>
)
}
function FollowPulseRow({
pubkey,
count,
latestCreatedAt,
posts,
onOpenProfile
}: {
pubkey: string
count: number
latestCreatedAt?: number
posts: NostrEvent[]
onOpenProfile: () => void
}) {
const [open, setOpen] = useState(false)
return (
<Collapsible open={open} onOpenChange={setOpen} className="border-b border-border/60 last:border-b-0">
<div className="flex items-stretch gap-0">
<CollapsibleTrigger
className="flex size-10 shrink-0 items-center justify-center border-r border-border/50 text-muted-foreground hover:bg-muted/40"
aria-label={open ? 'Collapse posts' : 'Expand posts'}
>
<ChevronRight className={cn('size-4 transition-transform', open && 'rotate-90')} />
</CollapsibleTrigger>
<button
type="button"
className="flex min-w-0 flex-1 items-center gap-3 px-3 py-2.5 text-left hover:bg-muted/30"
onClick={(e) => {
e.stopPropagation()
onOpenProfile()
}}
>
<UserAvatar userId={pubkey} size="medium" className="shrink-0" />
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<Username
userId={pubkey}
className="truncate text-sm font-semibold"
skeletonClassName="h-4 w-24"
/>
<Star className="size-3.5 shrink-0 text-muted-foreground/70" strokeWidth={1.5} aria-hidden />
</div>
<div className="text-xs text-muted-foreground">
{latestCreatedAt ? (
<FormattedTimestamp timestamp={latestCreatedAt} short />
) : (
'—'
)}
</div>
</div>
<div
className="flex size-8 shrink-0 items-center justify-center rounded-full bg-primary/15 text-sm font-semibold text-primary tabular-nums"
title={String(count)}
>
{count}
</div>
</button>
</div>
<CollapsibleContent className="overflow-hidden border-t border-border/50 bg-muted/10">
{posts.length === 0 ? (
<FollowRowEmptyPosts />
) : (
<div className="pb-2">
{posts.map((ev) => (
<NoteCard key={ev.id} className="w-full" event={ev} />
))}
</div>
)}
</CollapsibleContent>
</Collapsible>
)
}

9
src/i18n/locales/de.ts

@ -601,6 +601,15 @@ export default { @@ -601,6 +601,15 @@ export default {
'Trending Notes': 'Trendende Notizen',
'Trending on Your Favorite Relays': 'Trending auf deinen Lieblings-Relays',
'Trending on the Default Relays': 'Trending auf den Standard-Relays',
'Latest from your follows': 'Neuestes von deinen Follows',
'Latest from our recommended follows': 'Neuestes von unseren empfohlenen Follows',
'Loading follow list…': 'Follow-Liste wird geladen …',
'Could not load recommended follows': 'Empfohlene Follows konnten nicht geladen werden',
'Your follow list is empty': 'Deine Follow-Liste ist leer',
'Loading recent posts from follows…': 'Neueste Beiträge der Follows werden geladen …',
'Loading more…': 'Weitere werden geladen …',
'No recent posts from this user in the current fetch':
'Keine aktuellen Beiträge von diesem Nutzer in dieser Abfrage',
'Loading trending notes from your relays...': 'Trendende Notizen werden geladen …',
Sort: 'Sortierung',
newest: 'neueste',

9
src/i18n/locales/en.ts

@ -670,6 +670,15 @@ export default { @@ -670,6 +670,15 @@ export default {
'Trending Notes': 'Trending Notes',
'Trending on Your Favorite Relays': 'Trending on Your Favorite Relays',
'Trending on the Default Relays': 'Trending on the Default Relays',
'Latest from your follows': 'Latest from your follows',
'Latest from our recommended follows': 'Latest from our recommended follows',
'Loading follow list…': 'Loading follow list…',
'Could not load recommended follows': 'Could not load recommended follows',
'Your follow list is empty': 'Your follow list is empty',
'Loading recent posts from follows…': 'Loading recent posts from follows…',
'Loading more…': 'Loading more…',
'No recent posts from this user in the current fetch':
'No recent posts from this user in the current fetch',
'Loading trending notes from your relays...': 'Loading trending notes from your relays...',
Sort: 'Sort',
newest: 'newest',

12
src/lib/event.ts

@ -262,6 +262,18 @@ export function getEmbeddedPubkeys(event: Event) { @@ -262,6 +262,18 @@ export function getEmbeddedPubkeys(event: Event) {
return embeddedPubkeys
}
/**
* Whether `userPubkey` is mentioned on the event: any `p` tag and/or
* `nostr:npub…` / `nostr:nprofile…` in content (see {@link getEmbeddedPubkeys}).
* Events authored by the user are excluded (not treated as incoming mentions).
*/
export function isUserInEventMentions(event: Event, userPubkey: string): boolean {
if (event.pubkey === userPubkey) return false
const inPtags = event.tags.some((t) => t[0] === 'p' && t[1] === userPubkey)
if (inPtags) return true
return getEmbeddedPubkeys(event).includes(userPubkey)
}
export function getLatestEvent(events: Event[]): Event | undefined {
return events.sort((a, b) => b.created_at - a.created_at)[0]
}

2
src/pages/primary/SearchPage/index.tsx

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import LatestFromFollowsSection from '@/components/LatestFromFollowsSection'
import SearchBar, { TSearchBarRef } from '@/components/SearchBar'
import SearchResult from '@/components/SearchResult'
import PrimaryPageLayout, { TPrimaryPageLayoutRef } from '@/layouts/PrimaryPageLayout'
@ -68,6 +69,7 @@ const SearchPage = forwardRef((_, ref) => { @@ -68,6 +69,7 @@ const SearchPage = forwardRef((_, ref) => {
</div>
</div>
<div className="h-4"></div>
{!searchParams && <LatestFromFollowsSection />}
<SearchResult searchParams={searchParams} />
</div>
</PrimaryPageLayout>

40
src/pages/primary/SpellsPage/fauxSpellFeeds.ts

@ -10,8 +10,8 @@ import { @@ -10,8 +10,8 @@ import {
} from '@/constants'
import { normalizeTopic } from '@/lib/discussion-topics'
import { normalizeUrl } from '@/lib/url'
import type { TFeedSubRequest, TRelayList, TNotificationType } from '@/types'
import { kinds, type Event, type Filter } from 'nostr-tools'
import type { TFeedSubRequest, TRelayList } from '@/types'
import { type Event, type Filter } from 'nostr-tools'
const NOTIFICATION_LIMIT = 500
const DISCUSSION_LIMIT = 500
@ -57,40 +57,10 @@ function dedupe(urls: string[]): string[] { @@ -57,40 +57,10 @@ function dedupe(urls: string[]): string[] {
return out
}
export function notificationFilterKinds(notificationType: TNotificationType): number[] {
switch (notificationType) {
case 'mentions':
return [
kinds.ShortTextNote,
ExtendedKind.COMMENT,
ExtendedKind.VOICE_COMMENT,
ExtendedKind.POLL,
ExtendedKind.PUBLIC_MESSAGE,
ExtendedKind.DISCUSSION
]
case 'reactions':
return [kinds.Reaction, kinds.Repost, ExtendedKind.POLL_RESPONSE]
case 'zaps':
return [kinds.Zap]
default:
return [
kinds.ShortTextNote,
kinds.Repost,
kinds.Reaction,
kinds.Zap,
ExtendedKind.COMMENT,
ExtendedKind.POLL_RESPONSE,
ExtendedKind.VOICE_COMMENT,
ExtendedKind.POLL,
ExtendedKind.PUBLIC_MESSAGE,
ExtendedKind.DISCUSSION
]
}
}
export function buildNotificationFilter(pubkey: string, notificationType: TNotificationType): Filter {
/** Notifications spell: same kind set as profile-style feeds, restricted to `#p` = you on the relay. */
export function buildMentionsSpellFilter(pubkey: string): Filter {
return {
kinds: notificationFilterKinds(notificationType),
kinds: [...PROFILE_FEED_KINDS],
limit: NOTIFICATION_LIMIT,
'#p': [pubkey]
}

43
src/pages/primary/SpellsPage/index.tsx

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
import HideUntrustedContentButton from '@/components/HideUntrustedContentButton'
import NoteList from '@/components/NoteList'
import Tabs from '@/components/Tabs'
import { Button } from '@/components/ui/button'
import {
Dialog,
@ -32,10 +31,12 @@ import { cn } from '@/lib/utils' @@ -32,10 +31,12 @@ import { cn } from '@/lib/utils'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useKindFilter } from '@/providers/KindFilterProvider'
import { useNostr } from '@/providers/NostrProvider'
import { useUserTrust } from '@/providers/UserTrustProvider'
import client from '@/services/client.service'
import indexedDb from '@/services/indexed-db.service'
import storage from '@/services/local-storage.service'
import { ExtendedKind, FAUX_SPELL_ORDER, PROFILE_FEED_KINDS } from '@/constants'
import { isUserInEventMentions } from '@/lib/event'
import { formatPubkey } from '@/lib/pubkey'
import {
buildSpellCatalogAuthors,
@ -47,7 +48,7 @@ import { @@ -47,7 +48,7 @@ import {
SPELL_CATALOG_SYNC_LIMIT_WITH_FOLLOWS,
spellEventToFilter
} from '@/services/spell.service'
import { TFeedSubRequest, type TNotificationType } from '@/types'
import { TFeedSubRequest } from '@/types'
import {
Bell,
Bookmark,
@ -80,11 +81,10 @@ import { @@ -80,11 +81,10 @@ import {
buildFollowPacksSubRequests,
buildInterestsSubRequests,
buildMediaSpellFilter,
buildNotificationFilter,
buildMentionsSpellFilter,
discussionRelayUrls,
fauxFavoriteRelayUrls,
MEDIA_SPELL_KINDS,
notificationFilterKinds,
notificationRelayUrls
} from './fauxSpellFeeds'
import type { TPageRef } from '@/types'
@ -241,6 +241,7 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -241,6 +241,7 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
const { t } = useTranslation()
const { navigate: navigatePrimary } = usePrimaryPage()
const { pubkey, relayList, attemptDelete, bookmarkListEvent, interestListEvent } = useNostr()
const { hideUntrustedNotifications } = useUserTrust()
const { favoriteRelays, blockedRelays } = useFavoriteRelays()
const {
showKinds: kindFilterShowKinds,
@ -253,7 +254,6 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -253,7 +254,6 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
const [favoriteIds, setFavoriteIds] = useState<Set<string>>(new Set())
const [selectedSpell, setSelectedSpell] = useState<Event | null>(null)
const [selectedFauxSpell, setSelectedFauxSpell] = useState<FauxSpellName | null>(null)
const [notificationType, setNotificationType] = useState<TNotificationType>('all')
const [createOpen, setCreateOpen] = useState(false)
const [spellToEdit, setSpellToEdit] = useState<Event | null>(null)
const [spellToClone, setSpellToClone] = useState<Event | null>(null)
@ -414,9 +414,9 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -414,9 +414,9 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
if (selectedFauxSpell === 'notifications') {
if (!pubkey) return []
const urls = notificationRelayUrls(relayList, favoriteRelays)
const urls = fauxFavoriteRelayUrls(favoriteRelays, blockedRelays)
if (!urls.length) return []
return [{ urls, filter: buildNotificationFilter(pubkey, notificationType) }]
return [{ urls, filter: buildMentionsSpellFilter(pubkey) }]
}
if (selectedFauxSpell === 'discussions') {
const urls = discussionRelayUrls(relayList, favoriteRelays, blockedRelays)
@ -451,7 +451,6 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -451,7 +451,6 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
}, [
selectedFauxSpell,
pubkey,
notificationType,
relayList,
favoriteRelays,
blockedRelays,
@ -557,7 +556,7 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -557,7 +556,7 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
const showKinds = useMemo(() => {
if (selectedFauxSpell === 'notifications') {
return notificationFilterKinds(notificationType)
return PROFILE_FEED_KINDS
}
if (selectedFauxSpell === 'discussions') {
return [ExtendedKind.DISCUSSION]
@ -588,7 +587,6 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -588,7 +587,6 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
return kinds.length ? kinds : [1]
}, [
selectedFauxSpell,
notificationType,
selectedSpell?.id,
showKindsTagKey,
kindFilterShowKinds
@ -634,6 +632,11 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -634,6 +632,11 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
return selectedFauxSpell !== 'following' && selectedFauxSpell !== 'bookmarks'
}, [selectedFauxSpell])
const notificationsMentionExtraHide = useCallback(
(evt: Event) => (pubkey ? !isUserInEventMentions(evt, pubkey) : false),
[pubkey]
)
const fauxFeedEmptyMessage = useMemo(() => {
if (!selectedFauxSpell || fauxSubRequests.length > 0) return null
if (selectedFauxSpell === 'interests') return t('No subscribed interests yet.')
@ -953,17 +956,7 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -953,17 +956,7 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
) : selectedFauxSpell && fauxSubRequests.length > 0 ? (
<>
{selectedFauxSpell === 'notifications' ? (
<div className="shrink-0 flex items-center justify-between gap-2 px-1 pb-2">
<Tabs
value={notificationType}
tabs={[
{ value: 'all', label: t('All') },
{ value: 'mentions', label: t('Mentions') },
{ value: 'reactions', label: t('Reactions') },
{ value: 'zaps', label: t('Zaps') }
]}
onTabChange={(tab) => setNotificationType(tab as TNotificationType)}
/>
<div className="flex shrink-0 justify-end px-1 pb-2">
<HideUntrustedContentButton type="notifications" size="titlebar-icon" />
</div>
) : null}
@ -976,6 +969,14 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -976,6 +969,14 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
showKind1Replies={selectedFauxSpell === 'following' ? showKind1Replies : true}
showKind1111={selectedFauxSpell === 'following' ? showKind1111 : true}
hideReplies={selectedFauxSpell === 'following' ? hideRepliesFollowing : false}
extraShouldHideEvent={
selectedFauxSpell === 'notifications' && pubkey
? notificationsMentionExtraHide
: undefined
}
hideUntrustedNotes={
selectedFauxSpell === 'notifications' ? hideUntrustedNotifications : false
}
/>
</div>
</>

2
src/pages/secondary/SearchPage/index.tsx

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import LatestFromFollowsSection from '@/components/LatestFromFollowsSection'
import SearchBar, { TSearchBarRef } from '@/components/SearchBar'
import SearchResult from '@/components/SearchResult'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
@ -124,6 +125,7 @@ const SearchPage = forwardRef(({ index, hideTitlebar = false }: { index?: number @@ -124,6 +125,7 @@ const SearchPage = forwardRef(({ index, hideTitlebar = false }: { index?: number
</div>
</div>
<div className="h-4"></div>
{!searchParams && <LatestFromFollowsSection />}
<div className="text-xl font-semibold mb-4">Trending Notes</div>
<SearchResult searchParams={searchParams} />
</div>

Loading…
Cancel
Save