Browse Source

bug-fixes

imwald
Silberengel 2 weeks ago
parent
commit
9aa5724c4e
  1. 32
      src/components/ProfileList/index.tsx
  2. 36
      src/components/PubkeyListSearchField/index.tsx
  3. 58
      src/hooks/usePubkeyListSearchProfiles.ts
  4. 1
      src/i18n/locales/de.ts
  5. 1
      src/i18n/locales/en.ts
  6. 39
      src/lib/filter-pubkeys-by-profile-search.test.ts
  7. 87
      src/lib/filter-pubkeys-by-profile-search.ts
  8. 33
      src/pages/secondary/FollowersListPage/index.tsx
  9. 11
      src/pages/secondary/FollowingListPage/index.tsx
  10. 41
      src/pages/secondary/MuteListPage/index.tsx
  11. 1
      src/services/indexed-db.service.ts

32
src/components/ProfileList/index.tsx

@ -6,7 +6,14 @@ import UserItem from '../UserItem' @@ -6,7 +6,14 @@ import UserItem from '../UserItem'
const PROFILE_CHUNK = 80
export default function ProfileList({ pubkeys }: { pubkeys: string[] }) {
export default function ProfileList({
pubkeys,
seedProfiles
}: {
pubkeys: string[]
/** Profiles from list search (IndexedDB kind 0) — shown immediately without another fetch. */
seedProfiles?: Map<string, TProfile>
}) {
const [visiblePubkeys, setVisiblePubkeys] = useState<string[]>([])
const [profilesByPubkey, setProfilesByPubkey] = useState<Map<string, TProfile>>(() => new Map())
const bottomRef = useRef<HTMLDivElement>(null)
@ -104,8 +111,27 @@ export default function ProfileList({ pubkeys }: { pubkeys: string[] }) { @@ -104,8 +111,27 @@ export default function ProfileList({ pubkeys }: { pubkeys: string[] }) {
useEffect(() => {
batchGenRef.current += 1
loadedRef.current.clear()
setProfilesByPubkey(new Map())
}, [pubkeysKey])
const next = new Map<string, TProfile>()
if (seedProfiles) {
for (const [pk, p] of seedProfiles) {
next.set(pk.toLowerCase(), p)
loadedRef.current.add(pk.toLowerCase())
}
}
setProfilesByPubkey(next)
}, [pubkeysKey, seedProfiles])
useEffect(() => {
if (!seedProfiles?.size) return
setProfilesByPubkey((prev) => {
const next = new Map(prev)
for (const [pk, p] of seedProfiles) {
const pkNorm = pk.toLowerCase()
if (!next.has(pkNorm)) next.set(pkNorm, p)
}
return next
})
}, [seedProfiles])
return (
<div className="px-4 pt-2">

36
src/components/PubkeyListSearchField/index.tsx

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
import { Input } from '@/components/ui/input'
import { cn } from '@/lib/utils'
import { Search } from 'lucide-react'
import { useTranslation } from 'react-i18next'
export default function PubkeyListSearchField({
value,
onChange,
className
}: {
value: string
onChange: (value: string) => void
className?: string
}) {
const { t } = useTranslation()
return (
<div className={cn('relative px-4 pb-2', className)}>
<Search
className="pointer-events-none absolute left-7 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground"
aria-hidden
/>
<Input
type="search"
enterKeyHint="search"
autoComplete="off"
spellCheck={false}
placeholder={t('Pubkey list search placeholder')}
value={value}
onChange={(e) => onChange(e.target.value)}
className="w-full pl-8"
aria-label={t('Pubkey list search placeholder')}
/>
</div>
)
}

58
src/hooks/usePubkeyListSearchProfiles.ts

@ -0,0 +1,58 @@ @@ -0,0 +1,58 @@
import { getProfileFromEvent } from '@/lib/event-metadata'
import { filterPubkeysByListSearch } from '@/lib/filter-pubkeys-by-profile-search'
import indexedDb from '@/services/indexed-db.service'
import type { TProfile } from '@/types'
import { useEffect, useMemo, useState } from 'react'
/** Filter a pubkey list by search; enriches matches from cached kind-0 rows in IndexedDB. */
export function usePubkeyListSearchProfiles(pubkeys: string[]) {
const [searchQuery, setSearchQuery] = useState('')
const [searchProfileMap, setSearchProfileMap] = useState<Map<string, TProfile>>(() => new Map())
const pubkeysKey = useMemo(() => pubkeys.join('\u0001'), [pubkeys])
const pubkeysSet = useMemo(
() => new Set(pubkeys.map((pk) => pk.toLowerCase())),
[pubkeysKey]
)
useEffect(() => {
const q = searchQuery.trim()
if (!q) {
setSearchProfileMap(new Map())
return
}
let cancelled = false
void indexedDb
.searchProfileEventsInCache(q, Math.min(Math.max(pubkeys.length, 50), 500))
.then((events) => {
if (cancelled) return
const next = new Map<string, TProfile>()
for (const ev of events) {
const pk = ev.pubkey.toLowerCase()
if (!pubkeysSet.has(pk)) continue
next.set(pk, { ...getProfileFromEvent(ev), pubkey: pk })
}
setSearchProfileMap(next)
})
.catch(() => {
if (!cancelled) setSearchProfileMap(new Map())
})
return () => {
cancelled = true
}
}, [searchQuery, pubkeysSet, pubkeysKey, pubkeys.length])
const filteredPubkeys = useMemo(
() => filterPubkeysByListSearch(pubkeys, searchProfileMap, searchQuery),
[pubkeys, pubkeysKey, searchProfileMap, searchQuery]
)
return {
searchQuery,
setSearchQuery,
filteredPubkeys,
searchProfileMap
}
}

1
src/i18n/locales/de.ts

@ -456,6 +456,7 @@ export default { @@ -456,6 +456,7 @@ export default {
'Show more...': 'Mehr anzeigen...',
'Search dropdown profile search': 'PROFILES',
'Profile search no results': 'No matching profiles were found for this search.',
'Pubkey list search placeholder': 'Nach Name, npub oder Pubkey suchen…',
'Profile search failed':
'Profile search could not complete. Check your connection or try again.',
'All users': 'Alle Benutzer',

1
src/i18n/locales/en.ts

@ -459,6 +459,7 @@ export default { @@ -459,6 +459,7 @@ export default {
'Show more...': 'Show more...',
'Search dropdown profile search': 'PROFILES',
'Profile search no results': 'No matching profiles were found for this search.',
'Pubkey list search placeholder': 'Search by name, npub, or pubkey…',
'Profile search failed':
'Profile search could not complete. Check your connection or try again.',
'All users': 'All users',

39
src/lib/filter-pubkeys-by-profile-search.test.ts

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
import { describe, expect, it } from 'vitest'
import {
filterPubkeysByListSearch,
profileMatchesListSearch,
pubkeyMatchesListSearch
} from './filter-pubkeys-by-profile-search'
import type { TProfile } from '@/types'
const PK_A = 'a'.repeat(64)
const PK_B = 'b'.repeat(64)
describe('pubkeyMatchesListSearch', () => {
it('matches hex pubkey prefix', () => {
expect(pubkeyMatchesListSearch(PK_A, PK_A.slice(0, 8))).toBe(true)
expect(pubkeyMatchesListSearch(PK_B, PK_A.slice(0, 8))).toBe(false)
})
})
describe('profileMatchesListSearch', () => {
it('matches display name and nip05', () => {
const profile: TProfile = {
pubkey: PK_A,
npub: 'npub1test',
username: 'Alice Display',
original_username: 'Alice Display',
nip05: 'alice@example.com'
}
expect(profileMatchesListSearch(profile, 'alice')).toBe(true)
expect(profileMatchesListSearch(profile, 'example.com')).toBe(true)
expect(profileMatchesListSearch(profile, 'bob')).toBe(false)
})
})
describe('filterPubkeysByListSearch', () => {
it('returns only exact pubkey when query decodes to hex', () => {
const out = filterPubkeysByListSearch([PK_A, PK_B], new Map(), PK_A)
expect(out).toEqual([PK_A])
})
})

87
src/lib/filter-pubkeys-by-profile-search.ts

@ -0,0 +1,87 @@ @@ -0,0 +1,87 @@
import { normalizeProfileSearchQueryForMatch } from '@/lib/profile-metadata-search'
import { decodeProfileSearchQueryToPubkeyHex } from '@/lib/profile-search-query'
import { formatPubkey, pubkeyToNpub } from '@/lib/pubkey'
import type { TProfile } from '@/types'
function haystackIncludes(haystack: string, needle: string, needleNoAt: string): boolean {
const h = haystack.toLowerCase()
if (needle && h.includes(needle)) return true
if (needleNoAt.length > 0 && h.includes(needleNoAt)) return true
return false
}
function searchNeedles(rawQuery: string): { needle: string; needleNoAt: string } | null {
const trimmed = rawQuery.trim()
if (!trimmed) return null
const needle = normalizeProfileSearchQueryForMatch(trimmed)
if (!needle) return null
const needleNoAt = needle.startsWith('@') ? needle.slice(1).trim() : needle
return { needle, needleNoAt }
}
/** Match hex pubkey, npub, or formatted pubkey substring without loaded metadata. */
export function pubkeyMatchesListSearch(pubkey: string, rawQuery: string): boolean {
const trimmed = rawQuery.trim()
if (!trimmed) return true
const decoded = decodeProfileSearchQueryToPubkeyHex(trimmed)
const pkLower = pubkey.toLowerCase()
if (decoded && pkLower === decoded) return true
const needles = searchNeedles(trimmed)
if (!needles) return true
const { needle, needleNoAt } = needles
if (haystackIncludes(pkLower, needle, needleNoAt)) return true
const npub = pubkeyToNpub(pkLower)
if (npub && haystackIncludes(npub, needle, needleNoAt)) return true
if (haystackIncludes(formatPubkey(pkLower), needle, needleNoAt)) return true
return false
}
/** Match display name (`username`), name (`original_username`), or nip05. */
export function profileMatchesListSearch(profile: TProfile, rawQuery: string): boolean {
if (!rawQuery.trim()) return true
if (pubkeyMatchesListSearch(profile.pubkey, rawQuery)) return true
const needles = searchNeedles(rawQuery)
if (!needles) return true
const { needle, needleNoAt } = needles
const blobs = [
profile.username,
profile.original_username,
profile.nip05,
...(profile.nip05List ?? [])
].filter(Boolean) as string[]
for (const b of blobs) {
if (haystackIncludes(b, needle, needleNoAt)) return true
}
return false
}
export function filterPubkeysByListSearch(
pubkeys: string[],
profilesByPubkey: Map<string, TProfile>,
rawQuery: string
): string[] {
const q = rawQuery.trim()
if (!q) return pubkeys
const decoded = decodeProfileSearchQueryToPubkeyHex(q)
if (decoded) {
const hit = pubkeys.find((pk) => pk.toLowerCase() === decoded)
return hit ? [hit] : []
}
return pubkeys.filter((pk) => {
const pkNorm = pk.toLowerCase()
if (pubkeyMatchesListSearch(pk, q)) return true
const profile = profilesByPubkey.get(pkNorm)
return profile ? profileMatchesListSearch(profile, q) : false
})
}

33
src/pages/secondary/FollowersListPage/index.tsx

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
import JsonViewDialog from '@/components/JsonViewDialog'
import ProfileList from '@/components/ProfileList'
import PubkeyListSearchField from '@/components/PubkeyListSearchField'
import { RefreshButton } from '@/components/RefreshButton'
import { Button } from '@/components/ui/button'
import {
@ -10,11 +11,13 @@ import { @@ -10,11 +11,13 @@ import {
} from '@/components/ui/dropdown-menu'
import { useFetchProfile } from '@/hooks'
import { useNostrArchivesAvailable } from '@/hooks/useNostrArchivesAvailable'
import { usePubkeyListSearchProfiles } from '@/hooks/usePubkeyListSearchProfiles'
import { userIdToPubkey } from '@/lib/pubkey'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
import { usePrimaryNoteView } from '@/contexts/primary-note-view-context'
import nostrArchivesApi from '@/services/nostr-archives-api.service'
import { Code, MoreVertical } from 'lucide-react'
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react'
import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
const FOLLOWERS_PAGE_SIZE = 100
@ -31,6 +34,11 @@ const FollowersListPage = forwardRef( @@ -31,6 +34,11 @@ const FollowersListPage = forwardRef(
const archivesAvailable = useNostrArchivesAvailable()
const [listRefreshNonce, setListRefreshNonce] = useState(0)
const { profile } = useFetchProfile(id)
const profilePubkey = useMemo(() => {
if (!id) return null
const pk = userIdToPubkey(id)
return pk.length === 64 && /^[0-9a-f]{64}$/i.test(pk) ? pk.toLowerCase() : null
}, [id])
const [followers, setFollowers] = useState<string[]>([])
const [totalCount, setTotalCount] = useState<number | null>(null)
const [hasMore, setHasMore] = useState(false)
@ -40,6 +48,8 @@ const FollowersListPage = forwardRef( @@ -40,6 +48,8 @@ const FollowersListPage = forwardRef(
const bottomRef = useRef<HTMLDivElement>(null)
const loadMoreInFlight = useRef(false)
const offsetRef = useRef(0)
const { searchQuery, setSearchQuery, filteredPubkeys, searchProfileMap } =
usePubkeyListSearchProfiles(followers)
const bumpList = useCallback(() => {
offsetRef.current = 0
@ -48,7 +58,7 @@ const FollowersListPage = forwardRef( @@ -48,7 +58,7 @@ const FollowersListPage = forwardRef(
const openFollowersJson = useCallback(() => {
setFollowersJsonPayload({
pubkey: profile?.pubkey ?? null,
pubkey: profilePubkey ?? profile?.pubkey ?? null,
source: 'nostr-archives',
endpoint: '/v1/social/{pubkey}',
followersOffset: offsetRef.current,
@ -57,7 +67,7 @@ const FollowersListPage = forwardRef( @@ -57,7 +67,7 @@ const FollowersListPage = forwardRef(
totalCount
})
setJsonOpen(true)
}, [profile?.pubkey, followers, totalCount])
}, [profilePubkey, profile?.pubkey, followers, totalCount])
useEffect(() => {
if (!hideTitlebar) {
@ -70,7 +80,7 @@ const FollowersListPage = forwardRef( @@ -70,7 +80,7 @@ const FollowersListPage = forwardRef(
const fetchPage = useCallback(
async (offset: number, append: boolean) => {
const pk = profile?.pubkey
const pk = profilePubkey
if (!pk || !archivesAvailable) return false
const res = await nostrArchivesApi.getSocialGraph(pk, {
@ -101,12 +111,12 @@ const FollowersListPage = forwardRef( @@ -101,12 +111,12 @@ const FollowersListPage = forwardRef(
setHasMore(offsetRef.current < res.data.followers.count && batch.length > 0)
return true
},
[profile?.pubkey, archivesAvailable]
[profilePubkey, archivesAvailable]
)
useEffect(() => {
let cancelled = false
const pk = profile?.pubkey
const pk = profilePubkey
if (!pk) {
setFollowers([])
@ -136,7 +146,7 @@ const FollowersListPage = forwardRef( @@ -136,7 +146,7 @@ const FollowersListPage = forwardRef(
return () => {
cancelled = true
}
}, [profile?.pubkey, listRefreshNonce, archivesAvailable, fetchPage])
}, [profilePubkey, listRefreshNonce, archivesAvailable, fetchPage])
useEffect(() => {
const el = bottomRef.current
@ -201,9 +211,14 @@ const FollowersListPage = forwardRef( @@ -201,9 +211,14 @@ const FollowersListPage = forwardRef(
) : (
<>
{totalCount != null ? (
<p className="text-xs text-muted-foreground mb-3">{t('Nostr Archives followers hint')}</p>
<p className="text-xs text-muted-foreground mb-3 px-4">{t('Nostr Archives followers hint')}</p>
) : null}
<ProfileList pubkeys={followers} />
<PubkeyListSearchField value={searchQuery} onChange={setSearchQuery} />
{searchQuery.trim() && filteredPubkeys.length === 0 ? (
<p className="px-4 text-sm text-muted-foreground">{t('Profile search no results')}</p>
) : (
<ProfileList pubkeys={filteredPubkeys} seedProfiles={searchProfileMap} />
)}
</>
)}
<div ref={bottomRef} className="h-1" />

11
src/pages/secondary/FollowingListPage/index.tsx

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
import JsonViewDialog from '@/components/JsonViewDialog'
import ProfileList from '@/components/ProfileList'
import PubkeyListSearchField from '@/components/PubkeyListSearchField'
import { RefreshButton } from '@/components/RefreshButton'
import {
AlertDialog,
@ -19,6 +20,7 @@ import { @@ -19,6 +20,7 @@ import {
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { useFetchFollowings, useFetchProfile } from '@/hooks'
import { usePubkeyListSearchProfiles } from '@/hooks/usePubkeyListSearchProfiles'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
import { usePrimaryNoteView } from '@/contexts/primary-note-view-context'
import { buildAccountListRelayUrlsForMerge } from '@/lib/account-list-relay-urls'
@ -40,6 +42,8 @@ const FollowingListPage = forwardRef(({ id, index, hideTitlebar = false }: { id? @@ -40,6 +42,8 @@ const FollowingListPage = forwardRef(({ id, index, hideTitlebar = false }: { id?
const [listRefreshNonce, setListRefreshNonce] = useState(0)
const { profile } = useFetchProfile(id)
const { followings, followListEvent } = useFetchFollowings(profile?.pubkey, listRefreshNonce)
const { searchQuery, setSearchQuery, filteredPubkeys, searchProfileMap } =
usePubkeyListSearchProfiles(followings)
const [jsonOpen, setJsonOpen] = useState(false)
const [followJsonPayload, setFollowJsonPayload] = useState<unknown>(null)
const [cleanConfirmOpen, setCleanConfirmOpen] = useState(false)
@ -156,7 +160,12 @@ const FollowingListPage = forwardRef(({ id, index, hideTitlebar = false }: { id? @@ -156,7 +160,12 @@ const FollowingListPage = forwardRef(({ id, index, hideTitlebar = false }: { id?
displayScrollToTopButton
>
<JsonViewDialog value={followJsonPayload} isOpen={jsonOpen} onClose={() => setJsonOpen(false)} />
<ProfileList pubkeys={followings} />
<PubkeyListSearchField value={searchQuery} onChange={setSearchQuery} />
{searchQuery.trim() && filteredPubkeys.length === 0 ? (
<p className="px-4 text-sm text-muted-foreground">{t('Profile search no results')}</p>
) : (
<ProfileList pubkeys={filteredPubkeys} seedProfiles={searchProfileMap} />
)}
<AlertDialog open={cleanConfirmOpen} onOpenChange={setCleanConfirmOpen}>
<AlertDialogContent>
<AlertDialogHeader>

41
src/pages/secondary/MuteListPage/index.tsx

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
import JsonViewDialog from '@/components/JsonViewDialog'
import MuteButton from '@/components/MuteButton'
import PubkeyListSearchField from '@/components/PubkeyListSearchField'
import Nip05 from '@/components/Nip05'
import ProfileAbout from '@/components/ProfileAbout'
import { RefreshButton } from '@/components/RefreshButton'
@ -24,6 +25,7 @@ import { Skeleton } from '@/components/ui/skeleton' @@ -24,6 +25,7 @@ import { Skeleton } from '@/components/ui/skeleton'
import UserAvatar from '@/components/UserAvatar'
import Username from '@/components/Username'
import { useFetchProfile } from '@/hooks'
import { usePubkeyListSearchProfiles } from '@/hooks/usePubkeyListSearchProfiles'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
import { usePrimaryNoteView } from '@/contexts/primary-note-view-context'
import { buildAccountListRelayUrlsForMerge } from '@/lib/account-list-relay-urls'
@ -32,6 +34,7 @@ import { useMuteList } from '@/contexts/mute-list-context' @@ -32,6 +34,7 @@ import { useMuteList } from '@/contexts/mute-list-context'
import indexedDb from '@/services/indexed-db.service'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useNostr } from '@/providers/NostrProvider'
import type { TProfile } from '@/types'
import { Code, Eraser, Lock, MoreVertical, Unlock } from 'lucide-react'
import dayjs from 'dayjs'
import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react'
@ -48,6 +51,8 @@ const MuteListPage = forwardRef(({ index, hideTitlebar = false }: { index?: numb @@ -48,6 +51,8 @@ const MuteListPage = forwardRef(({ index, hideTitlebar = false }: { index?: numb
const [jsonOpen, setJsonOpen] = useState(false)
const [jsonPayload, setJsonPayload] = useState<unknown>(null)
const mutePubkeys = useMemo(() => getMutePubkeys(), [getMutePubkeys])
const { searchQuery, setSearchQuery, filteredPubkeys, searchProfileMap } =
usePubkeyListSearchProfiles(mutePubkeys)
const [visibleMutePubkeys, setVisibleMutePubkeys] = useState<string[]>([])
const [listRefreshKey, setListRefreshKey] = useState(0)
const [cleanConfirmOpen, setCleanConfirmOpen] = useState(false)
@ -86,8 +91,8 @@ const MuteListPage = forwardRef(({ index, hideTitlebar = false }: { index?: numb @@ -86,8 +91,8 @@ const MuteListPage = forwardRef(({ index, hideTitlebar = false }: { index?: numb
}, [hideTitlebar, registerPrimaryPanelRefresh, bumpList])
useEffect(() => {
setVisibleMutePubkeys(mutePubkeys.slice(0, 10))
}, [mutePubkeys, listRefreshKey])
setVisibleMutePubkeys(filteredPubkeys.slice(0, 10))
}, [filteredPubkeys, listRefreshKey])
useEffect(() => {
const options = {
@ -97,10 +102,10 @@ const MuteListPage = forwardRef(({ index, hideTitlebar = false }: { index?: numb @@ -97,10 +102,10 @@ const MuteListPage = forwardRef(({ index, hideTitlebar = false }: { index?: numb
}
const observerInstance = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && mutePubkeys.length > visibleMutePubkeys.length) {
if (entries[0].isIntersecting && filteredPubkeys.length > visibleMutePubkeys.length) {
setVisibleMutePubkeys((prev) => [
...prev,
...mutePubkeys.slice(prev.length, prev.length + 10)
...filteredPubkeys.slice(prev.length, prev.length + 10)
])
}
}, options)
@ -115,7 +120,7 @@ const MuteListPage = forwardRef(({ index, hideTitlebar = false }: { index?: numb @@ -115,7 +120,7 @@ const MuteListPage = forwardRef(({ index, hideTitlebar = false }: { index?: numb
observerInstance.unobserve(currentBottomRef)
}
}
}, [visibleMutePubkeys, mutePubkeys])
}, [visibleMutePubkeys, filteredPubkeys])
const handleCleanList = useCallback(async () => {
if (!pubkey || cleaning) return
@ -207,21 +212,37 @@ const MuteListPage = forwardRef(({ index, hideTitlebar = false }: { index?: numb @@ -207,21 +212,37 @@ const MuteListPage = forwardRef(({ index, hideTitlebar = false }: { index?: numb
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<div key={listRefreshKey} className="space-y-2 px-4 pt-2">
<PubkeyListSearchField value={searchQuery} onChange={setSearchQuery} className="pt-2" />
{searchQuery.trim() && filteredPubkeys.length === 0 ? (
<p className="px-4 pb-2 text-sm text-muted-foreground">{t('Profile search no results')}</p>
) : (
<div key={listRefreshKey} className="space-y-2 px-4 pt-0">
{visibleMutePubkeys.map((pubkey, index) => (
<UserItem key={`${index}-${pubkey}`} pubkey={pubkey} />
<UserItem
key={`${index}-${pubkey}`}
pubkey={pubkey}
prefetchedProfile={searchProfileMap.get(pubkey.toLowerCase())}
/>
))}
{mutePubkeys.length > visibleMutePubkeys.length && <div ref={bottomRef} />}
{filteredPubkeys.length > visibleMutePubkeys.length && <div ref={bottomRef} />}
</div>
)}
</SecondaryPageLayout>
)
})
MuteListPage.displayName = 'MuteListPage'
export default MuteListPage
function UserItem({ pubkey }: { pubkey: string }) {
function UserItem({
pubkey,
prefetchedProfile
}: {
pubkey: string
prefetchedProfile?: TProfile
}) {
const { changing, getMuteType, switchToPrivateMute, switchToPublicMute } = useMuteList()
const { profile } = useFetchProfile(pubkey)
const { profile: fetchedProfile } = useFetchProfile(pubkey)
const profile = prefetchedProfile ?? fetchedProfile
const muteType = useMemo(() => getMuteType(pubkey), [pubkey, getMuteType])
const [switching, setSwitching] = useState(false)

1
src/services/indexed-db.service.ts

@ -1184,6 +1184,7 @@ class IndexedDbService { @@ -1184,6 +1184,7 @@ class IndexedDbService {
case 10001: // Pin list
return StoreNames.PIN_LIST_EVENTS
case ExtendedKind.PROFILE_BADGES_LIST:
case ExtendedKind.PROFILE_BADGES: // deprecated NIP-58 list (d=profile_badges); same store as 10008
return StoreNames.PROFILE_BADGES_LIST_EVENTS
case 10015: // Interest list
return StoreNames.INTEREST_LIST_EVENTS

Loading…
Cancel
Save