Improve community bookmark reliability
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user