diff --git a/src/components/CommunityDetailPage.tsx b/src/components/CommunityDetailPage.tsx
index e89d9d51..5d6894c5 100644
--- a/src/components/CommunityDetailPage.tsx
+++ b/src/components/CommunityDetailPage.tsx
@@ -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 && (
diff --git a/src/hooks/useCommunityBookmarks.ts b/src/hooks/useCommunityBookmarks.ts
index c0d18a7e..07f3001e 100644
--- a/src/hooks/useCommunityBookmarks.ts
+++ b/src/hooks/useCommunityBookmarks.ts
@@ -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);
diff --git a/src/hooks/useMyCommunities.ts b/src/hooks/useMyCommunities.ts
index 22430196..7ff047c2 100644
--- a/src/hooks/useMyCommunities.ts
+++ b/src/hooks/useMyCommunities.ts
@@ -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
const coordsByAuthor = new Map>();
for (const coord of bookmarkedCoords) {
- // Format: "34550::" -- 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);