Browse Source

bug-fixes

imwald
Silberengel 2 weeks ago
parent
commit
2eeb5ee797
  1. 17
      src/lib/mute-set.test.ts
  2. 19
      src/lib/mute-set.ts
  3. 7
      src/lib/relay-thread-heat-cache.ts
  4. 35
      src/pages/primary/SpellsPage/ProfileInteractionsMap.test.ts
  5. 74
      src/pages/primary/SpellsPage/ProfileInteractionsMap.tsx
  6. 21
      src/pages/primary/SpellsPage/RelayThreadHeatMap.tsx
  7. 21
      src/pages/primary/SpellsPage/TopicKeywordHeatMap.test.ts
  8. 25
      src/pages/primary/SpellsPage/TopicKeywordHeatMap.tsx

17
src/lib/mute-set.test.ts

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest'
import { kinds } from 'nostr-tools'
import { filterEventsExcludingMutedAuthors } from './mute-set'
describe('filterEventsExcludingMutedAuthors', () => {
it('drops events from muted pubkeys', () => {
const muted = 'a'.repeat(64)
const other = 'b'.repeat(64)
const events = [
{ kind: kinds.ShortTextNote, pubkey: muted, id: '1'.repeat(64), sig: 's', tags: [], content: '', created_at: 1 },
{ kind: kinds.ShortTextNote, pubkey: other, id: '2'.repeat(64), sig: 's', tags: [], content: '', created_at: 1 }
]
const out = filterEventsExcludingMutedAuthors(events, new Set([muted]))
expect(out).toHaveLength(1)
expect(out[0]?.pubkey).toBe(other)
})
})

19
src/lib/mute-set.ts

@ -1,7 +1,24 @@ @@ -1,7 +1,24 @@
import type { Event } from 'nostr-tools'
/**
* Mute pubkey sets use lowercase hex so lookups match Nostr events and `p` tags regardless of casing.
*/
export function muteSetHas(mutePubkeySet: Set<string>, pubkey: string | undefined | null): boolean {
export function muteSetHas(mutePubkeySet: ReadonlySet<string>, pubkey: string | undefined | null): boolean {
if (!pubkey) return false
return mutePubkeySet.has(pubkey.toLowerCase())
}
/** Drop notes whose author is in the viewer's public or private mute list. */
export function filterEventsExcludingMutedAuthors(
events: readonly Event[],
mutePubkeySet: ReadonlySet<string>
): Event[] {
if (mutePubkeySet.size === 0) return [...events]
return events.filter((ev) => !muteSetHas(mutePubkeySet, ev.pubkey))
}
/** Stable SETTINGS / cache segment when mute lists change. */
export function mutePubkeySetFingerprint(mutePubkeySet: ReadonlySet<string>): string {
if (mutePubkeySet.size === 0) return '0'
return [...mutePubkeySet].sort().join('\n')
}

7
src/lib/relay-thread-heat-cache.ts

@ -26,7 +26,9 @@ export function relayThreadHeatMapSettingKey( @@ -26,7 +26,9 @@ export function relayThreadHeatMapSettingKey(
relayUrls: readonly string[],
followPubkeys: readonly string[],
/** Serialized home kind-picker state so cache invalidates when feed filters change. */
feedFilterKey: string
feedFilterKey: string,
/** Sorted mute pubkeys so cache invalidates when mutes change. */
muteFingerprint: string
): string {
const pk = pubkey.trim().toLowerCase()
const relayKey = digestHeatMapKeyPart([...relayUrls].sort().join('\n'))
@ -38,7 +40,8 @@ export function relayThreadHeatMapSettingKey( @@ -38,7 +40,8 @@ export function relayThreadHeatMapSettingKey(
.join('\n')
)
const feedKey = digestHeatMapKeyPart(feedFilterKey)
return `relayHeatV${CACHE_V}:${pk}:${relayKey}:${followKey}:${feedKey}`
const muteKey = digestHeatMapKeyPart(muteFingerprint)
return `relayHeatV${CACHE_V}:${pk}:${relayKey}:${followKey}:${feedKey}:${muteKey}`
}
export function parseRelayThreadHeatMapCache(raw: string | null): TRelayThreadHeatMapCacheEnvelope | null {

35
src/pages/primary/SpellsPage/ProfileInteractionsMap.test.ts

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
import { describe, expect, it } from 'vitest'
import { kinds } from 'nostr-tools'
import { mergeInteractionEvents } from './ProfileInteractionsMap'
function interaction(pubkey: string, pTags: string[]) {
return {
kind: kinds.ShortTextNote,
pubkey,
tags: pTags.map((p) => ['p', p]),
content: '',
id: `${pubkey.slice(0, 8)}${'c'.repeat(56)}`,
sig: 's'.repeat(128),
created_at: 1_700_000_000
}
}
describe('mergeInteractionEvents', () => {
it('excludes muted partners and events authored by muted pubkeys', () => {
const profile = 'a'.repeat(64)
const partner = 'b'.repeat(64)
const muted = 'f'.repeat(64)
const cards = mergeInteractionEvents(
profile,
[
interaction(profile, [partner]),
interaction(profile, [muted]),
interaction(muted, [profile]),
interaction(partner, [profile])
],
new Set([muted])
)
expect(cards.map((c) => c.pubkey)).toEqual([partner])
expect(cards[0]?.score).toBe(2)
})
})

74
src/pages/primary/SpellsPage/ProfileInteractionsMap.tsx

@ -4,7 +4,9 @@ import { Button } from '@/components/ui/button' @@ -4,7 +4,9 @@ import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Skeleton } from '@/components/ui/skeleton'
import { ExtendedKind } from '@/constants'
import { useMuteList } from '@/contexts/mute-list-context'
import { getRelayUrlsWithFavoritesFastReadAndInbox, userReadInboxUrls, userWriteOutboxUrls } from '@/lib/favorites-feed-relays'
import { muteSetHas } from '@/lib/mute-set'
import { toProfile } from '@/lib/link'
import { formatPubkey } from '@/lib/pubkey'
import { cn } from '@/lib/utils'
@ -57,12 +59,18 @@ function interactionFilters(pubkey: string, limit: number): TSubRequestFilter[] @@ -57,12 +59,18 @@ function interactionFilters(pubkey: string, limit: number): TSubRequestFilter[]
]
}
function mergeInteractionEvents(targetPubkey: string, events: Event[]): InteractionCard[] {
export function mergeInteractionEvents(
targetPubkey: string,
events: Event[],
mutePubkeySet: ReadonlySet<string>
): InteractionCard[] {
const target = targetPubkey.toLowerCase()
const byPubkey = new Map<string, InteractionCard>()
const add = (partnerRaw: string | undefined, event: Event, direction: 'out' | 'in') => {
if (muteSetHas(mutePubkeySet, event.pubkey)) return
const partner = partnerRaw?.trim().toLowerCase()
if (!partner || partner === target || !/^[0-9a-f]{64}$/.test(partner)) return
if (muteSetHas(mutePubkeySet, partner)) return
let row = byPubkey.get(partner)
if (!row) {
row = {
@ -112,6 +120,7 @@ export default function ProfileInteractionsMap({ pubkey, refreshKey }: Props) { @@ -112,6 +120,7 @@ export default function ProfileInteractionsMap({ pubkey, refreshKey }: Props) {
const { t } = useTranslation()
const { push } = useSecondaryPage()
const { relayList, cacheRelayListEvent } = useNostr()
const { mutePubkeySet } = useMuteList()
const { favoriteRelays, blockedRelays } = useFavoriteRelays()
const [cards, setCards] = useState<InteractionCard[]>([])
const [loading, setLoading] = useState(true)
@ -168,12 +177,12 @@ export default function ProfileInteractionsMap({ pubkey, refreshKey }: Props) { @@ -168,12 +177,12 @@ export default function ProfileInteractionsMap({ pubkey, refreshKey }: Props) {
try {
const local = await load(false)
if (cancelled) return
setCards(mergeInteractionEvents(pubkey, local))
setCards(mergeInteractionEvents(pubkey, local, mutePubkeySet))
setLoading(false)
const all = await load(true)
if (cancelled) return
setCards(mergeInteractionEvents(pubkey, all))
setCards(mergeInteractionEvents(pubkey, all, mutePubkeySet))
} catch (e) {
if (cancelled) return
setError(e instanceof Error ? e.message : String(e))
@ -187,7 +196,7 @@ export default function ProfileInteractionsMap({ pubkey, refreshKey }: Props) { @@ -187,7 +196,7 @@ export default function ProfileInteractionsMap({ pubkey, refreshKey }: Props) {
return () => {
cancelled = true
}
}, [pubkey, refreshKey, load])
}, [pubkey, refreshKey, load, mutePubkeySet])
return (
<div className="flex min-h-0 flex-1 flex-col gap-4">
@ -203,7 +212,7 @@ export default function ProfileInteractionsMap({ pubkey, refreshKey }: Props) { @@ -203,7 +212,7 @@ export default function ProfileInteractionsMap({ pubkey, refreshKey }: Props) {
onClick={() => {
setRefreshing(true)
void load(true)
.then((rows) => setCards(mergeInteractionEvents(pubkey, rows)))
.then((rows) => setCards(mergeInteractionEvents(pubkey, rows, mutePubkeySet)))
.catch((e) => setError(e instanceof Error ? e.message : String(e)))
.finally(() => setRefreshing(false))
}}
@ -221,7 +230,7 @@ export default function ProfileInteractionsMap({ pubkey, refreshKey }: Props) { @@ -221,7 +230,7 @@ export default function ProfileInteractionsMap({ pubkey, refreshKey }: Props) {
{loading && cards.length === 0 ? (
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3">
{Array.from({ length: 9 }).map((_, i) => (
<Skeleton key={i} className="h-24 rounded-xl" />
<Skeleton key={i} className="h-44 rounded-xl" />
))}
</div>
) : error && cards.length === 0 ? (
@ -234,7 +243,7 @@ export default function ProfileInteractionsMap({ pubkey, refreshKey }: Props) { @@ -234,7 +243,7 @@ export default function ProfileInteractionsMap({ pubkey, refreshKey }: Props) {
</div>
) : (
<div className="min-h-0 flex-1 overflow-y-auto pb-4">
<div className="grid grid-cols-1 gap-2 min-[720px]:grid-cols-2 xl:grid-cols-3">
<div className="grid grid-cols-1 gap-3 min-[720px]:grid-cols-2 xl:grid-cols-3">
{cards.map((card, index) => (
<button
key={card.pubkey}
@ -244,38 +253,39 @@ export default function ProfileInteractionsMap({ pubkey, refreshKey }: Props) { @@ -244,38 +253,39 @@ export default function ProfileInteractionsMap({ pubkey, refreshKey }: Props) {
>
<Card
className={cn(
'flex h-full min-w-0 items-center gap-2 p-2 transition-colors hover:bg-accent/70 min-[720px]:gap-3 min-[720px]:p-3',
'flex h-full min-w-0 flex-col overflow-hidden p-3 transition-colors hover:bg-accent/70',
index < 3 && 'border-primary/40 bg-primary/5'
)}
>
<div className="relative shrink-0">
<UserAvatar userId={card.pubkey} size="semiBig" className="min-[720px]:h-16 min-[720px]:w-16" />
<span className="absolute -bottom-1 -right-1 z-10 rounded-full bg-background px-1.5 py-0.5 text-[10px] font-semibold shadow ring-1 ring-border">
<div className="flex min-w-0 items-start justify-between gap-2">
<div className="min-w-0 flex-1">
<Username userId={card.pubkey} className="block truncate text-sm font-semibold" />
<div className="truncate text-xs text-muted-foreground">{formatPubkey(card.pubkey)}</div>
</div>
<span className="shrink-0 rounded-full bg-muted px-2 py-0.5 text-[10px] font-semibold text-foreground ring-1 ring-border">
#{index + 1}
</span>
</div>
<div className="min-w-0 flex-1">
<Username userId={card.pubkey} className="block truncate text-sm font-semibold" />
<div className="truncate text-xs text-muted-foreground">{formatPubkey(card.pubkey)}</div>
<div className="mt-1.5 flex min-w-0 flex-wrap gap-1 text-[11px] text-muted-foreground min-[720px]:mt-2 min-[720px]:gap-1.5 min-[720px]:text-xs">
<span className="rounded-full bg-muted px-2 py-0.5 font-medium text-foreground">
<UserRound className="mr-1 inline size-3" aria-hidden />
<span className="min-[720px]:hidden">{compactCount(card.score)}</span>
<span className="hidden min-[720px]:inline">
{t('n interactions', { count: card.score, formattedCount: compactCount(card.score) })}
</span>
<div className="mt-2 flex min-w-0 flex-wrap gap-1.5 text-[11px] text-muted-foreground">
<span className="max-w-full truncate rounded-full bg-muted px-2 py-0.5 font-medium text-foreground">
<UserRound className="mr-1 inline size-3 shrink-0" aria-hidden />
{t('n interactions', { count: card.score, formattedCount: compactCount(card.score) })}
</span>
{card.authoredByProfile > 0 ? (
<span className="max-w-full truncate rounded-full bg-muted px-2 py-0.5">
{t('outgoing interactions', { count: card.authoredByProfile })}
</span>
{card.authoredByProfile > 0 ? (
<span className="hidden rounded-full bg-muted px-2 py-0.5 min-[720px]:inline">
{t('outgoing interactions', { count: card.authoredByProfile })}
</span>
) : null}
{card.mentionsProfile > 0 ? (
<span className="hidden rounded-full bg-muted px-2 py-0.5 min-[720px]:inline">
{t('incoming interactions', { count: card.mentionsProfile })}
</span>
) : null}
</div>
) : null}
{card.mentionsProfile > 0 ? (
<span className="max-w-full truncate rounded-full bg-muted px-2 py-0.5">
{t('incoming interactions', { count: card.mentionsProfile })}
</span>
) : null}
</div>
<div className="mt-3 flex justify-center">
<UserAvatar userId={card.pubkey} size="big" className="size-16 shrink-0 sm:size-20" />
</div>
</Card>
</button>

21
src/pages/primary/SpellsPage/RelayThreadHeatMap.tsx

@ -4,6 +4,7 @@ import { SimpleUserAvatar } from '@/components/UserAvatar' @@ -4,6 +4,7 @@ import { SimpleUserAvatar } from '@/components/UserAvatar'
import { ExtendedKind } from '@/constants'
import { eventPassesNoteListKindPicker } from '@/lib/feed-kind-filter'
import { filterEventsExcludingTombstones } from '@/lib/event'
import { filterEventsExcludingMutedAuthors, mutePubkeySetFingerprint, muteSetHas } from '@/lib/mute-set'
import { getRelayUrlsWithFavoritesFastReadAndInbox, userReadInboxUrls, userWriteOutboxUrls } from '@/lib/favorites-feed-relays'
import { toNote } from '@/lib/link'
import logger from '@/lib/logger'
@ -22,6 +23,7 @@ import { @@ -22,6 +23,7 @@ import {
type TRelayThreadHeatEdge
} from '@/lib/relay-thread-heat'
import { usePrimaryPage } from '@/contexts/primary-page-context'
import { useMuteList } from '@/contexts/mute-list-context'
import { useSmartNoteNavigation } from '@/PageManager'
import { encodeProfileInteractionsSpellId } from './fauxSpellConfig'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
@ -101,6 +103,7 @@ export default function RelayThreadHeatMap({ followPubkeys, refreshKey }: Props) @@ -101,6 +103,7 @@ export default function RelayThreadHeatMap({ followPubkeys, refreshKey }: Props)
const { navigate: navigatePrimary } = usePrimaryPage()
const { navigateToNote } = useSmartNoteNavigation()
const { pubkey, relayList, cacheRelayListEvent } = useNostr()
const { mutePubkeySet } = useMuteList()
const { favoriteRelays, blockedRelays } = useFavoriteRelays()
const { showKinds, showKind1OPs, showKind1Replies, showKind1111 } = useKindFilterOrDefaults()
@ -142,9 +145,14 @@ export default function RelayThreadHeatMap({ followPubkeys, refreshKey }: Props) @@ -142,9 +145,14 @@ export default function RelayThreadHeatMap({ followPubkeys, refreshKey }: Props)
const [error, setError] = useState<string | null>(null)
const [rescanTick, setRescanTick] = useState(0)
const muteFingerprint = useMemo(() => mutePubkeySetFingerprint(mutePubkeySet), [mutePubkeySet])
const cacheSettingKey = useMemo(
() => (pubkey ? relayThreadHeatMapSettingKey(pubkey, relayUrls, followPubkeys, feedFilterKey) : ''),
[pubkey, relayUrls, followPubkeys, feedFilterKey]
() =>
pubkey
? relayThreadHeatMapSettingKey(pubkey, relayUrls, followPubkeys, feedFilterKey, muteFingerprint)
: '',
[pubkey, relayUrls, followPubkeys, feedFilterKey, muteFingerprint]
)
const mergeHeatMapData = useCallback(async (includeRelay = true): Promise<{
@ -204,7 +212,10 @@ export default function RelayThreadHeatMap({ followPubkeys, refreshKey }: Props) @@ -204,7 +212,10 @@ export default function RelayThreadHeatMap({ followPubkeys, refreshKey }: Props)
dedup.set(ev.id.toLowerCase(), ev)
}
}
const merged = filterEventsExcludingTombstones([...dedup.values()], tombstones)
const merged = filterEventsExcludingMutedAuthors(
filterEventsExcludingTombstones([...dedup.values()], tombstones),
mutePubkeySet
)
const feedNotes = merged.filter((e) =>
eventPassesNoteListKindPicker(e, showKinds, showKind1OPs, showKind1Replies, showKind1111)
)
@ -227,6 +238,7 @@ export default function RelayThreadHeatMap({ followPubkeys, refreshKey }: Props) @@ -227,6 +238,7 @@ export default function RelayThreadHeatMap({ followPubkeys, refreshKey }: Props)
for (const ev of archived) {
if (!verifyEvent(ev)) continue
if (ev.kind !== kinds.ShortTextNote && ev.kind !== ExtendedKind.DISCUSSION) continue
if (muteSetHas(mutePubkeySet, ev.pubkey)) continue
rootById.set(ev.id.toLowerCase(), ev)
}
const stillMissing = missingRootIds.filter((id) => !rootById.has(id))
@ -244,6 +256,7 @@ export default function RelayThreadHeatMap({ followPubkeys, refreshKey }: Props) @@ -244,6 +256,7 @@ export default function RelayThreadHeatMap({ followPubkeys, refreshKey }: Props)
for (const ev of fetched) {
if (!verifyEvent(ev)) continue
if (ev.kind !== kinds.ShortTextNote && ev.kind !== ExtendedKind.DISCUSSION) continue
if (muteSetHas(mutePubkeySet, ev.pubkey)) continue
rootById.set(ev.id.toLowerCase(), ev)
}
}
@ -270,7 +283,7 @@ export default function RelayThreadHeatMap({ followPubkeys, refreshKey }: Props) @@ -270,7 +283,7 @@ export default function RelayThreadHeatMap({ followPubkeys, refreshKey }: Props)
edges: edges.length
})
return { bubbles, edges }
}, [relayUrls, followSet, showKinds, showKind1OPs, showKind1Replies, showKind1111])
}, [relayUrls, followSet, showKinds, showKind1OPs, showKind1Replies, showKind1111, mutePubkeySet])
useEffect(() => {
let cancelled = false

21
src/pages/primary/SpellsPage/TopicKeywordHeatMap.test.ts

@ -38,4 +38,25 @@ describe('buildTopicKeywordBubbles', () => { @@ -38,4 +38,25 @@ describe('buildTopicKeywordBubbles', () => {
expect(nostr?.pubkeys).toContain(pkC)
expect(nostr?.pubkeys).toContain(pkB)
})
it('excludes muted authors from counts and bubble avatars', () => {
const pkA = 'a'.repeat(64)
const pkMuted = 'f'.repeat(64)
const pkB = 'b'.repeat(64)
const bubbles = buildTopicKeywordBubbles(
[
note(pkA, [['t', 'nostr']]),
note(pkMuted, [['t', 'nostr']], 'muted #nostr'),
note(pkB, [['t', 'nostr']])
],
DEFAULT_FEED_SHOW_KINDS,
true,
true,
true,
new Set([pkMuted])
)
const nostr = bubbles.find((b) => b.key === 'nostr')
expect(nostr?.score).toBe(2)
expect(nostr?.pubkeys).not.toContain(pkMuted)
})
})

25
src/pages/primary/SpellsPage/TopicKeywordHeatMap.tsx

@ -1,7 +1,9 @@ @@ -1,7 +1,9 @@
import { Button } from '@/components/ui/button'
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
import { ExtendedKind } from '@/constants'
import { useMuteList } from '@/contexts/mute-list-context'
import { eventPassesNoteListKindPicker } from '@/lib/feed-kind-filter'
import { filterEventsExcludingMutedAuthors, muteSetHas } from '@/lib/mute-set'
import { filterEventsExcludingTombstones } from '@/lib/event'
import { extractHashtagsFromContent, formatTopicMapBubbleLabel, isValidNormalizedTopicKey, normalizeTopic } from '@/lib/discussion-topics'
import { getRelayUrlsWithFavoritesFastReadAndInbox, userReadInboxUrls, userWriteOutboxUrls } from '@/lib/favorites-feed-relays'
@ -50,8 +52,13 @@ type TopicKeyAccum = { @@ -50,8 +52,13 @@ type TopicKeyAccum = {
pubkeyHits: Map<string, number>
}
function topPubkeysForTopic(hits: Map<string, number>, limit: number): string[] {
function topPubkeysForTopic(
hits: Map<string, number>,
limit: number,
mutePubkeySet?: ReadonlySet<string>
): string[] {
return [...hits.entries()]
.filter(([pk]) => !mutePubkeySet || !muteSetHas(mutePubkeySet, pk))
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
.slice(0, limit)
.map(([pk]) => pk)
@ -148,7 +155,8 @@ export function buildTopicKeywordBubbles( @@ -148,7 +155,8 @@ export function buildTopicKeywordBubbles(
showKinds: readonly number[],
showKind1OPs: boolean,
showKind1Replies: boolean,
showKind1111: boolean
showKind1111: boolean,
mutePubkeySet?: ReadonlySet<string>
): TTopicKeywordBubble[] {
const accum = new Map<string, TopicKeyAccum>()
@ -168,6 +176,7 @@ export function buildTopicKeywordBubbles( @@ -168,6 +176,7 @@ export function buildTopicKeywordBubbles(
}
for (const ev of events) {
if (mutePubkeySet && muteSetHas(mutePubkeySet, ev.pubkey)) continue
if (!eventPassesNoteListKindPicker(ev, showKinds, showKind1OPs, showKind1Replies, showKind1111)) continue
const topics = new Set<string>()
for (const row of ev.tags) {
@ -192,7 +201,7 @@ export function buildTopicKeywordBubbles( @@ -192,7 +201,7 @@ export function buildTopicKeywordBubbles(
score,
topicNoteCount: row.topicNoteCount,
keywordNoteCount: row.keywordNoteCount,
pubkeys: topPubkeysForTopic(row.pubkeyHits, MAX_BUBBLE_AVATARS)
pubkeys: topPubkeysForTopic(row.pubkeyHits, MAX_BUBBLE_AVATARS, mutePubkeySet)
})
}
out.sort((x, y) => y.score - x.score || x.key.localeCompare(y.key))
@ -205,6 +214,7 @@ type Props = { @@ -205,6 +214,7 @@ type Props = {
export default function TopicKeywordHeatMap({ refreshKey }: Props) {
const { t } = useTranslation()
const { mutePubkeySet } = useMuteList()
const { navigateToHashtag } = useSmartHashtagNavigation()
const { relayList, cacheRelayListEvent } = useNostr()
const { favoriteRelays, blockedRelays } = useFavoriteRelays()
@ -276,9 +286,12 @@ export default function TopicKeywordHeatMap({ refreshKey }: Props) { @@ -276,9 +286,12 @@ export default function TopicKeywordHeatMap({ refreshKey }: Props) {
dedup.set(ev.id.toLowerCase(), ev)
}
}
const clean = filterEventsExcludingTombstones([...dedup.values()], tombstones)
return buildTopicKeywordBubbles(clean, showKinds, showKind1OPs, showKind1Replies, showKind1111)
}, [relayUrls, showKinds, showKind1OPs, showKind1Replies, showKind1111])
const clean = filterEventsExcludingMutedAuthors(
filterEventsExcludingTombstones([...dedup.values()], tombstones),
mutePubkeySet
)
return buildTopicKeywordBubbles(clean, showKinds, showKind1OPs, showKind1Replies, showKind1111, mutePubkeySet)
}, [relayUrls, showKinds, showKind1OPs, showKind1Replies, showKind1111, mutePubkeySet])
useEffect(() => {
let cancelled = false

Loading…
Cancel
Save