Improve community bookmark reliability

This commit is contained in:
lemon
2026-05-04 21:44:34 -07:00
parent 5e91f1d328
commit 72d7962632
3 changed files with 31 additions and 28 deletions
+4 -2
View File
@@ -176,7 +176,7 @@ export function CommunityDetailPage({ event }: { event: NostrEvent }) {
} = useCommunityBookmarks();
const bookmarked = !!communityATag && isCommunityBookmarked(communityATag);
const handleToggleBookmark = useCallback(() => {
if (!user || !communityATag) return;
if (!user || !communityATag || toggleCommunityBookmark.isPending) return;
toggleCommunityBookmark.mutate({ aTag: communityATag });
}, [user, communityATag, toggleCommunityBookmark]);
@@ -376,10 +376,12 @@ export function CommunityDetailPage({ event }: { event: NostrEvent }) {
)}
{user && communityATag && (
<button
className="p-2 rounded-full hover:bg-secondary/60 transition-colors"
className="p-2 rounded-full hover:bg-secondary/60 transition-colors disabled:opacity-50 disabled:pointer-events-none"
onClick={handleToggleBookmark}
disabled={toggleCommunityBookmark.isPending}
aria-label={bookmarked ? 'Remove community bookmark' : 'Bookmark community'}
aria-pressed={bookmarked}
aria-busy={toggleCommunityBookmark.isPending}
>
<Bookmark className={cn('size-5', bookmarked && 'fill-current')} />
</button>
+12 -1
View File
@@ -5,11 +5,22 @@ import { useCurrentUser } from './useCurrentUser';
import { useNostrPublish } from './useNostrPublish';
import { fetchFreshEvent } from '@/lib/fetchFreshEvent';
import { COMMUNITY_DEFINITION_KIND } from '@/lib/communityUtils';
import { parseATagCoordinate } from '@/lib/nostrEvents';
import { toast } from '@/hooks/useToast';
/** NIP-51 Communities list — kind 10004. */
export const COMMUNITIES_LIST_KIND = 10004;
const HEX_PUBKEY_RE = /^[0-9a-f]{64}$/i;
/** Parse and validate a NIP-51 community list coordinate. */
export function parseCommunityBookmarkATag(aTag: string): { pubkey: string; dTag: string } | undefined {
const coord = parseATagCoordinate(aTag);
if (!coord || coord.kind !== COMMUNITY_DEFINITION_KIND) return undefined;
if (!HEX_PUBKEY_RE.test(coord.pubkey) || !coord.identifier) return undefined;
return { pubkey: coord.pubkey, dTag: coord.identifier };
}
/**
* Hook to manage the user's NIP-51 Communities list (kind 10004).
*
@@ -42,7 +53,7 @@ export function useCommunityBookmarks() {
// Extract bookmarked community a-tags (only `34550:` coordinates)
const bookmarkedATags: string[] = (listQuery.data?.tags ?? [])
.filter(([name, value]) =>
name === 'a' && typeof value === 'string' && value.startsWith(`${COMMUNITY_DEFINITION_KIND}:`),
name === 'a' && typeof value === 'string' && !!parseCommunityBookmarkATag(value),
)
.map(([, value]) => value);
+15 -25
View File
@@ -3,7 +3,7 @@ import { useNostr } from '@nostrify/react';
import { useQuery } from '@tanstack/react-query';
import { useCurrentUser } from './useCurrentUser';
import { COMMUNITIES_LIST_KIND } from './useCommunityBookmarks';
import { COMMUNITIES_LIST_KIND, parseCommunityBookmarkATag } from './useCommunityBookmarks';
import {
COMMUNITY_DEFINITION_KIND,
BADGE_AWARD_KIND,
@@ -108,44 +108,34 @@ export function useMyCommunities() {
.filter(([n, v]) =>
n === 'a'
&& typeof v === 'string'
&& v.startsWith(`${COMMUNITY_DEFINITION_KIND}:`),
&& !!parseCommunityBookmarkATag(v),
)
.map(([, v]) => v);
// Group bookmarked coords by author pubkey: author -> Set<d-tag>
const coordsByAuthor = new Map<string, Set<string>>();
for (const coord of bookmarkedCoords) {
// Format: "34550:<pubkey>:<d-tag>" -- d-tag may itself contain ":"
const firstColon = coord.indexOf(':');
const secondColon = coord.indexOf(':', firstColon + 1);
if (firstColon === -1 || secondColon === -1) continue;
const authorPubkey = coord.slice(firstColon + 1, secondColon);
const dTag = coord.slice(secondColon + 1);
if (!authorPubkey || !dTag) continue;
const existing = coordsByAuthor.get(authorPubkey);
const parsed = parseCommunityBookmarkATag(coord);
if (!parsed) continue;
const existing = coordsByAuthor.get(parsed.pubkey);
if (existing) {
existing.add(dTag);
existing.add(parsed.dTag);
} else {
coordsByAuthor.set(authorPubkey, new Set([dTag]));
coordsByAuthor.set(parsed.pubkey, new Set([parsed.dTag]));
}
}
let bookmarkedCommunityEvents: NostrEvent[] = [];
if (coordsByAuthor.size > 0) {
const bookmarkQueries = await Promise.all(
Array.from(coordsByAuthor.entries()).map(([authorPubkey, dTags]) =>
nostr.query(
[{
kinds: [COMMUNITY_DEFINITION_KIND],
authors: [authorPubkey],
'#d': [...dTags],
limit: dTags.size,
}],
{ signal: combinedSignal },
),
),
bookmarkedCommunityEvents = await nostr.query(
Array.from(coordsByAuthor.entries()).map(([authorPubkey, dTags]) => ({
kinds: [COMMUNITY_DEFINITION_KIND],
authors: [authorPubkey],
'#d': [...dTags],
limit: dTags.size,
})),
{ signal: combinedSignal },
);
bookmarkedCommunityEvents = bookmarkQueries.flat();
}
const bookmarkedATagSet = new Set(bookmarkedCoords);