From 0d3017543cd0f768b4bb06e3e1959cda68804134 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Thu, 5 Feb 2026 09:54:55 +0100 Subject: [PATCH] reveal votes, even if the user is logged-out hide bookmark indicator, unless active bookmark available --- src/lib/components/content/EmojiPicker.svelte | 2 +- src/lib/modules/comments/CommentThread.svelte | 212 +++++---- src/lib/modules/feed/FeedPost.svelte | 17 +- src/lib/modules/feed/HighlightCard.svelte | 17 +- .../reactions/FeedReactionButtons.svelte | 222 ++++----- src/lib/modules/threads/ThreadList.svelte | 35 +- src/lib/services/nostr/nip30-emoji.ts | 10 +- src/lib/services/nostr/nostr-client.ts | 445 +++++++++++++++--- src/lib/services/user-data.ts | 9 +- 9 files changed, 652 insertions(+), 317 deletions(-) diff --git a/src/lib/components/content/EmojiPicker.svelte b/src/lib/components/content/EmojiPicker.svelte index d29689e..0ea5c8c 100644 --- a/src/lib/components/content/EmojiPicker.svelte +++ b/src/lib/components/content/EmojiPicker.svelte @@ -61,7 +61,7 @@ try { await loadAllEmojiPacks(); const allEmojis = getAllCustomEmojis(); - console.debug(`[EmojiPicker] Loaded ${allEmojis.length} custom emojis`); + // Silently load emojis - no need to log customEmojis = allEmojis; } catch (error) { console.error('Error loading custom emojis:', error); diff --git a/src/lib/modules/comments/CommentThread.svelte b/src/lib/modules/comments/CommentThread.svelte index 4d27ffb..6b73b38 100644 --- a/src/lib/modules/comments/CommentThread.svelte +++ b/src/lib/modules/comments/CommentThread.svelte @@ -168,66 +168,93 @@ return; } - // Batch updates to prevent flickering - requestAnimationFrame(() => { - isProcessingUpdate = true; + // Process immediately - don't batch with requestAnimationFrame for faster UI updates + isProcessingUpdate = true; + + try { + let hasNewReplies = false; + const commentsMap = new Map(comments.map(c => [c.id, c])); + const kind1RepliesMap = new Map(kind1Replies.map(r => [r.id, r])); + const yakBacksMap = new Map(yakBacks.map(y => [y.id, y])); + const zapReceiptsMap = new Map(zapReceipts.map(z => [z.id, z])); - try { - let hasNewReplies = false; - const commentsMap = new Map(comments.map(c => [c.id, c])); - const kind1RepliesMap = new Map(kind1Replies.map(r => [r.id, r])); - const yakBacksMap = new Map(yakBacks.map(y => [y.id, y])); - const zapReceiptsMap = new Map(zapReceipts.map(z => [z.id, z])); + for (const reply of updated) { + // Skip if we already have this reply + if (commentsMap.has(reply.id) || kind1RepliesMap.has(reply.id) || + yakBacksMap.has(reply.id) || zapReceiptsMap.has(reply.id)) { + continue; + } - for (const reply of updated) { - // Skip if we already have this reply - if (commentsMap.has(reply.id) || kind1RepliesMap.has(reply.id) || - yakBacksMap.has(reply.id) || zapReceiptsMap.has(reply.id)) { - continue; - } - - // Check if this reply references the root OR is a reply to any existing comment/reply - const parentId = getParentEventId(reply); - const isReplyToRoot = referencesRoot(reply); - const isReplyToExisting = parentId && ( - parentId === threadId || - commentsMap.has(parentId) || - kind1RepliesMap.has(parentId) || - yakBacksMap.has(parentId) || - zapReceiptsMap.has(parentId) - ); - - if (!isReplyToRoot && !isReplyToExisting) { - continue; - } - - // Add the reply to the appropriate map - if (reply.kind === KIND.COMMENT) { - commentsMap.set(reply.id, reply); - hasNewReplies = true; - } else if (reply.kind === KIND.SHORT_TEXT_NOTE) { - kind1RepliesMap.set(reply.id, reply); - hasNewReplies = true; - } else if (reply.kind === KIND.VOICE_REPLY) { - yakBacksMap.set(reply.id, reply); - hasNewReplies = true; - } else if (reply.kind === KIND.ZAP_RECEIPT) { - zapReceiptsMap.set(reply.id, reply); - hasNewReplies = true; - } + // Check if this reply references the root OR is a reply to any existing comment/reply + const parentId = getParentEventId(reply); + const isReplyToRoot = referencesRoot(reply); + const isReplyToExisting = parentId && ( + parentId === threadId || + commentsMap.has(parentId) || + kind1RepliesMap.has(parentId) || + yakBacksMap.has(parentId) || + zapReceiptsMap.has(parentId) + ); + + if (!isReplyToRoot && !isReplyToExisting) { + continue; } - // Only update state if we have new replies - if (hasNewReplies) { - comments = Array.from(commentsMap.values()); - kind1Replies = Array.from(kind1RepliesMap.values()); - yakBacks = Array.from(yakBacksMap.values()); - zapReceipts = Array.from(zapReceiptsMap.values()); + // Add the reply to the appropriate map + if (reply.kind === KIND.COMMENT) { + commentsMap.set(reply.id, reply); + hasNewReplies = true; + } else if (reply.kind === KIND.SHORT_TEXT_NOTE) { + kind1RepliesMap.set(reply.id, reply); + hasNewReplies = true; + } else if (reply.kind === KIND.VOICE_REPLY) { + yakBacksMap.set(reply.id, reply); + hasNewReplies = true; + } else if (reply.kind === KIND.ZAP_RECEIPT) { + zapReceiptsMap.set(reply.id, reply); + hasNewReplies = true; } - } finally { - isProcessingUpdate = false; } - }); + + // Update state immediately if we have new replies + if (hasNewReplies) { + const allComments = Array.from(commentsMap.values()); + const allKind1Replies = Array.from(kind1RepliesMap.values()); + const allYakBacks = Array.from(yakBacksMap.values()); + const allZapReceipts = Array.from(zapReceiptsMap.values()); + + // Limit array sizes to prevent memory bloat (keep most recent 500 of each type) + const MAX_COMMENTS = 500; + const MAX_REPLIES = 500; + + // Sort by created_at descending and take most recent + comments = allComments + .sort((a, b) => b.created_at - a.created_at) + .slice(0, MAX_COMMENTS); + kind1Replies = allKind1Replies + .sort((a, b) => b.created_at - a.created_at) + .slice(0, MAX_REPLIES); + yakBacks = allYakBacks + .sort((a, b) => b.created_at - a.created_at) + .slice(0, MAX_REPLIES); + zapReceipts = allZapReceipts + .sort((a, b) => b.created_at - a.created_at) + .slice(0, MAX_REPLIES); + + // Clear loading flag as soon as we get the first results + // This allows comments to render immediately instead of waiting for all fetches + if (loading) { + loading = false; + } + } else if (updated.length > 0 && loading) { + // If we got events but they were all filtered out, still clear loading + // This prevents the UI from being stuck in loading state + // The events might be nested replies that will be processed later + loading = false; + } + } finally { + isProcessingUpdate = false; + } } async function loadComments() { @@ -238,13 +265,13 @@ const allRelays = relayManager.getProfileReadRelays(); const replyFilters: any[] = [ - { kinds: [KIND.COMMENT], '#e': [threadId], limit: 500 }, - { kinds: [KIND.COMMENT], '#E': [threadId], limit: 500 }, - { kinds: [KIND.COMMENT], '#a': [threadId], limit: 500 }, - { kinds: [KIND.COMMENT], '#A': [threadId], limit: 500 }, - { kinds: [KIND.SHORT_TEXT_NOTE], '#e': [threadId], limit: 500 }, - { kinds: [KIND.VOICE_REPLY], '#e': [threadId], limit: 500 }, - { kinds: [KIND.ZAP_RECEIPT], '#e': [threadId], limit: 500 } + { kinds: [KIND.COMMENT], '#e': [threadId], limit: 100 }, + { kinds: [KIND.COMMENT], '#E': [threadId], limit: 100 }, + { kinds: [KIND.COMMENT], '#a': [threadId], limit: 100 }, + { kinds: [KIND.COMMENT], '#A': [threadId], limit: 100 }, + { kinds: [KIND.SHORT_TEXT_NOTE], '#e': [threadId], limit: 100 }, + { kinds: [KIND.VOICE_REPLY], '#e': [threadId], limit: 100 }, + { kinds: [KIND.ZAP_RECEIPT], '#e': [threadId], limit: 100 } ]; // fetchEvents with useCache:true returns cached data immediately if available, @@ -261,7 +288,9 @@ loading = true; // Only show loading if no cache } - // Now fetch with full options - returns cached immediately, fetches fresh in background + // Now fetch with full options - returns relay results immediately, then enhances with cache + // onUpdate callback will be called as events arrive from relays, allowing immediate rendering + // Use high priority to ensure comments load before background fetches (reactions, profiles, etc.) const allReplies = await nostrClient.fetchEvents( replyFilters, allRelays, @@ -269,20 +298,43 @@ useCache: true, cacheResults: true, timeout: 10000, - onUpdate: handleReplyUpdate + onUpdate: handleReplyUpdate, + priority: 'high' } ); - // Filter to only replies that reference the root + // Process initial results (from relays or cache) + // Note: onUpdate may have already updated the state and cleared loading + // But if onUpdate didn't process them (e.g., filtered out), we need to process them here const rootReplies = allReplies.filter(reply => referencesRoot(reply)); - // Separate by type - comments = rootReplies.filter(e => e.kind === KIND.COMMENT); - kind1Replies = rootReplies.filter(e => e.kind === KIND.SHORT_TEXT_NOTE); - yakBacks = rootReplies.filter(e => e.kind === KIND.VOICE_REPLY); - zapReceipts = rootReplies.filter(e => e.kind === KIND.ZAP_RECEIPT); + // Only update if we have new replies not already processed by onUpdate + const existingIds = new Set([ + ...comments.map(c => c.id), + ...kind1Replies.map(r => r.id), + ...yakBacks.map(y => y.id), + ...zapReceipts.map(z => z.id) + ]); + + const newRootReplies = rootReplies.filter(r => !existingIds.has(r.id)); - loading = false; // Hide loading now that we have data (cached or fresh) + if (newRootReplies.length > 0) { + // Merge with existing (onUpdate may have already added some) + const allComments = [...comments, ...newRootReplies.filter(e => e.kind === KIND.COMMENT)]; + const allKind1Replies = [...kind1Replies, ...newRootReplies.filter(e => e.kind === KIND.SHORT_TEXT_NOTE)]; + const allYakBacks = [...yakBacks, ...newRootReplies.filter(e => e.kind === KIND.VOICE_REPLY)]; + const allZapReceipts = [...zapReceipts, ...newRootReplies.filter(e => e.kind === KIND.ZAP_RECEIPT)]; + + // Deduplicate + comments = Array.from(new Map(allComments.map(c => [c.id, c])).values()); + kind1Replies = Array.from(new Map(allKind1Replies.map(r => [r.id, r])).values()); + yakBacks = Array.from(new Map(allYakBacks.map(y => [y.id, y])).values()); + zapReceipts = Array.from(new Map(allZapReceipts.map(z => [z.id, z])).values()); + } + + // ALWAYS clear loading flag after fetch completes, even if no events matched + // This prevents the UI from being stuck in loading state + loading = false; // Recursively fetch all nested replies (non-blocking - let it run in background) fetchNestedReplies().then(() => { @@ -337,7 +389,8 @@ { useCache: true, cacheResults: true, - onUpdate: handleReplyUpdate + onUpdate: handleReplyUpdate, + priority: 'high' } ).catch(error => { console.error('Error subscribing to nested replies:', error); @@ -544,27 +597,28 @@ // Always fetch kind 1111 comments - check both e and E tags, and a and A tags replyFilters.push( - { kinds: [KIND.COMMENT], '#e': [threadId], limit: 500 }, // Lowercase e tag - { kinds: [KIND.COMMENT], '#E': [threadId], limit: 500 }, // Uppercase E tag (NIP-22) - { kinds: [KIND.COMMENT], '#a': [threadId], limit: 500 }, // Lowercase a tag (some clients use wrong tags) - { kinds: [KIND.COMMENT], '#A': [threadId], limit: 500 } // Uppercase A tag (NIP-22 for addressable events) + { kinds: [KIND.COMMENT], '#e': [threadId], limit: 100 }, // Lowercase e tag + { kinds: [KIND.COMMENT], '#E': [threadId], limit: 100 }, // Uppercase E tag (NIP-22) + { kinds: [KIND.COMMENT], '#a': [threadId], limit: 100 }, // Lowercase a tag (some clients use wrong tags) + { kinds: [KIND.COMMENT], '#A': [threadId], limit: 100 } // Uppercase A tag (NIP-22 for addressable events) ); // For kind 1 events, fetch kind 1 replies // Also fetch kind 1 replies for any event (some apps use kind 1 for everything) - replyFilters.push({ kinds: [KIND.SHORT_TEXT_NOTE], '#e': [threadId], limit: 500 }); + replyFilters.push({ kinds: [KIND.SHORT_TEXT_NOTE], '#e': [threadId], limit: 100 }); // Fetch yak backs (kind 1244) - voice replies - replyFilters.push({ kinds: [KIND.VOICE_REPLY], '#e': [threadId], limit: 500 }); + replyFilters.push({ kinds: [KIND.VOICE_REPLY], '#e': [threadId], limit: 100 }); // Fetch zap receipts (kind 9735) - replyFilters.push({ kinds: [KIND.ZAP_RECEIPT], '#e': [threadId], limit: 500 }); + replyFilters.push({ kinds: [KIND.ZAP_RECEIPT], '#e': [threadId], limit: 100 }); // Don't use cache when reloading after publishing - we want fresh data + // Use high priority to ensure comments load before background fetches const allReplies = await nostrClient.fetchEvents( replyFilters, allRelays, - { useCache: false, cacheResults: true, timeout: 10000 } + { useCache: false, cacheResults: true, timeout: 10000, priority: 'high' } ); // Filter to only replies that reference the root diff --git a/src/lib/modules/feed/FeedPost.svelte b/src/lib/modules/feed/FeedPost.svelte index c1db073..b74dfa5 100644 --- a/src/lib/modules/feed/FeedPost.svelte +++ b/src/lib/modules/feed/FeedPost.svelte @@ -14,6 +14,7 @@ import { getKindInfo, KIND } from '../../types/kind-lookup.js'; import { stripMarkdown } from '../../services/text-utils.js'; import { isBookmarked } from '../../services/user-actions.js'; + import { sessionManager } from '../../services/auth/session-manager.js'; interface Props { post: NostrEvent; @@ -37,12 +38,18 @@ let zapCount = $state(0); // Check if this event is bookmarked (async, so we use state) + // Only check if user is logged in let bookmarked = $state(false); + const isLoggedIn = $derived(sessionManager.isLoggedIn()); $effect(() => { - isBookmarked(post.id).then(b => { - bookmarked = b; - }); + if (isLoggedIn) { + isBookmarked(post.id).then(b => { + bookmarked = b; + }); + } else { + bookmarked = false; + } }); // Calculate votes as derived values to avoid infinite loops @@ -465,7 +472,9 @@ {/if} {/if}
- 🔖 + {#if isLoggedIn && bookmarked} + 🔖 + {/if}
diff --git a/src/lib/modules/feed/HighlightCard.svelte b/src/lib/modules/feed/HighlightCard.svelte index aa7e1f6..a3c55ff 100644 --- a/src/lib/modules/feed/HighlightCard.svelte +++ b/src/lib/modules/feed/HighlightCard.svelte @@ -9,6 +9,7 @@ import { getKindInfo, KIND } from '../../types/kind-lookup.js'; import { getHighlightsForEvent } from '../../services/nostr/highlight-service.js'; import { isBookmarked } from '../../services/user-actions.js'; + import { sessionManager } from '../../services/auth/session-manager.js'; interface Props { highlight: NostrEvent; // The highlight event (kind 9802) @@ -21,12 +22,18 @@ let loadingSource = $state(false); // Check if this event is bookmarked (async, so we use state) + // Only check if user is logged in let bookmarked = $state(false); + const isLoggedIn = $derived(sessionManager.isLoggedIn()); $effect(() => { - isBookmarked(highlight.id).then(b => { - bookmarked = b; - }); + if (isLoggedIn) { + isBookmarked(highlight.id).then(b => { + bookmarked = b; + }); + } else { + bookmarked = false; + } }); // Extract source event ID from e-tag or a-tag @@ -313,7 +320,9 @@ via {getClientName()} {/if}
- 🔖 + {#if isLoggedIn && bookmarked} + 🔖 + {/if}
diff --git a/src/lib/modules/reactions/FeedReactionButtons.svelte b/src/lib/modules/reactions/FeedReactionButtons.svelte index 42e8b4b..0742431 100644 --- a/src/lib/modules/reactions/FeedReactionButtons.svelte +++ b/src/lib/modules/reactions/FeedReactionButtons.svelte @@ -33,6 +33,8 @@ let loadingReactions = $state(false); let lastEventId = $state(null); let isMounted = $state(false); + let processingUpdate = $state(false); + let updateDebounceTimer: ReturnType | null = null; onMount(() => { // Set lastEventId immediately to prevent $effect from running during mount @@ -82,26 +84,46 @@ }); // Handle real-time updates - process reactions when new ones arrive + // Debounced to prevent excessive processing async function handleReactionUpdate(updated: NostrEvent[]) { - console.debug(`[FeedReactionButtons] Received reaction update for event ${event.id.substring(0, 16)}...:`, { - count: updated.length, - events: updated.map(r => ({ - id: r.id.substring(0, 16) + '...', - pubkey: r.pubkey.substring(0, 16) + '...', - content: r.content, - fullEvent: r - })) - }); + // Prevent concurrent processing + if (processingUpdate) { + return; + } // Add new reactions to the map + let hasNewReactions = false; for (const r of updated) { - allReactionsMap.set(r.id, r); + if (!allReactionsMap.has(r.id)) { + allReactionsMap.set(r.id, r); + hasNewReactions = true; + } + } + + // Only process if we have new reactions + if (!hasNewReactions) { + return; + } + + // Clear existing debounce timer + if (updateDebounceTimer) { + clearTimeout(updateDebounceTimer); } - // Process all accumulated reactions - const allReactions = Array.from(allReactionsMap.values()); - const filtered = await filterDeletedReactions(allReactions); - processReactions(filtered); + // Debounce processing to batch multiple rapid updates + updateDebounceTimer = setTimeout(async () => { + if (processingUpdate) return; + processingUpdate = true; + + try { + // Process all accumulated reactions + const allReactions = Array.from(allReactionsMap.values()); + const filtered = await filterDeletedReactions(allReactions); + processReactions(filtered); + } finally { + processingUpdate = false; + } + }, 300); // 300ms debounce } async function loadReactions() { @@ -115,44 +137,22 @@ // Use getProfileReadRelays() to include defaultRelays + profileRelays + user inbox + localRelays // This ensures we get all reactions from the complete relay set, matching ThreadList behavior const reactionRelays = relayManager.getProfileReadRelays(); - console.debug(`[FeedReactionButtons] Loading reactions for event ${event.id.substring(0, 16)}... (kind ${event.kind})`); - console.debug(`[FeedReactionButtons] Using relays:`, reactionRelays); // Clear and rebuild reactions map for this event allReactionsMap.clear(); + // Use low priority for reactions - they're background data, comments should load first const reactionsWithLowerE = await nostrClient.fetchEvents( [{ kinds: [KIND.REACTION], '#e': [event.id], limit: 100 }], reactionRelays, - { useCache: true, cacheResults: true, onUpdate: handleReactionUpdate, timeout: 5000 } + { useCache: true, cacheResults: true, onUpdate: handleReactionUpdate, timeout: 5000, priority: 'low' } ); const reactionsWithUpperE = await nostrClient.fetchEvents( [{ kinds: [KIND.REACTION], '#E': [event.id], limit: 100 }], reactionRelays, - { useCache: true, cacheResults: true, onUpdate: handleReactionUpdate, timeout: 5000 } + { useCache: true, cacheResults: true, onUpdate: handleReactionUpdate, timeout: 5000, priority: 'low' } ); - console.debug(`[FeedReactionButtons] Reactions fetched:`, { - eventId: event.id.substring(0, 16) + '...', - kind: event.kind, - withLowerE: reactionsWithLowerE.length, - withUpperE: reactionsWithUpperE.length, - lowerE_events: reactionsWithLowerE.map(r => ({ - id: r.id.substring(0, 16) + '...', - pubkey: r.pubkey.substring(0, 16) + '...', - content: r.content, - tags: r.tags.filter(t => t[0] === 'e' || t[0] === 'E'), - fullEvent: r - })), - upperE_events: reactionsWithUpperE.map(r => ({ - id: r.id.substring(0, 16) + '...', - pubkey: r.pubkey.substring(0, 16) + '...', - content: r.content, - tags: r.tags.filter(t => t[0] === 'e' || t[0] === 'E'), - fullEvent: r - })) - }); - // Combine and deduplicate by reaction ID for (const r of reactionsWithLowerE) { allReactionsMap.set(r.id, r); @@ -162,31 +162,8 @@ } const reactionEvents = Array.from(allReactionsMap.values()); - console.debug(`[FeedReactionButtons] All reactions (deduplicated):`, { - total: reactionEvents.length, - events: reactionEvents.map(r => ({ - id: r.id.substring(0, 16) + '...', - pubkey: r.pubkey.substring(0, 16) + '...', - content: r.content, - tags: r.tags.filter(t => t[0] === 'e' || t[0] === 'E'), - created_at: new Date(r.created_at * 1000).toISOString(), - fullEvent: r - })) - }); - // Filter out deleted reactions (kind 5) const filteredReactions = await filterDeletedReactions(reactionEvents); - console.debug(`[FeedReactionButtons] After filtering deleted reactions:`, { - before: reactionEvents.length, - after: filteredReactions.length, - filtered: reactionEvents.length - filteredReactions.length, - events: filteredReactions.map(r => ({ - id: r.id.substring(0, 16) + '...', - pubkey: r.pubkey.substring(0, 16) + '...', - content: r.content, - fullEvent: r - })) - }); processReactions(filteredReactions); } catch (error) { @@ -211,22 +188,13 @@ // Fetch deletion events that reference these specific reaction IDs // This is much more efficient than fetching all deletion events from all users + // Use low priority for deletion events - background data const deletionEvents = await nostrClient.fetchEvents( [{ kinds: [KIND.EVENT_DELETION], '#e': limitedReactionIds, limit: 100 }], reactionRelays, - { useCache: true, timeout: 5000 } + { useCache: true, timeout: 5000, priority: 'low' } ); - console.debug(`[FeedReactionButtons] Deletion events fetched:`, { - count: deletionEvents.length, - events: deletionEvents.map(d => ({ - id: d.id.substring(0, 16) + '...', - pubkey: d.pubkey.substring(0, 16) + '...', - deletedEventIds: d.tags.filter(t => t[0] === 'e').map(t => t[1]?.substring(0, 16) + '...'), - fullEvent: d - })) - }); - // Build a set of deleted reaction event IDs (more efficient - just a Set) const deletedReactionIds = new Set(); for (const deletionEvent of deletionEvents) { @@ -238,10 +206,6 @@ } } - console.debug(`[FeedReactionButtons] Deleted reaction IDs by pubkey:`, - Array.from(deletedReactionIds).slice(0, 10).map(id => id.substring(0, 16) + '...') - ); - // Filter out deleted reactions - much simpler now const filtered = reactions.filter(reaction => { const isDeleted = deletedReactionIds.has(reaction.id); @@ -252,7 +216,10 @@ } async function processReactions(reactionEvents: NostrEvent[]) { - console.debug(`[FeedReactionButtons] Processing ${reactionEvents.length} reactions for event ${event.id.substring(0, 16)}... (kind ${event.kind})`); + // Prevent duplicate processing - check if we're already processing the same set + if (processingUpdate) { + return; + } const reactionMap = new Map; eventIds: Map }>(); const currentUser = sessionManager.getCurrentPubkey(); let skippedInvalid = 0; @@ -270,12 +237,7 @@ content = '-'; } else if (content !== '+' && content !== '-') { skippedInvalid++; - console.log(`[FeedReactionButtons] Skipping invalid reaction for kind 11:`, { - originalContent, - reactionId: reactionEvent.id.substring(0, 16) + '...', - pubkey: reactionEvent.pubkey.substring(0, 16) + '...', - fullEvent: reactionEvent - }); + // Silently skip invalid reactions - no need to log every one continue; // Skip invalid reactions for threads } } @@ -292,26 +254,17 @@ } } - console.debug(`[FeedReactionButtons] Processed reactions summary:`, { - totalReactions: reactionEvents.length, - skippedInvalid, - reactionCounts: Array.from(reactionMap.entries()).map(([content, data]) => ({ - content, - count: data.pubkeys.size, - pubkeys: Array.from(data.pubkeys).map(p => p.substring(0, 16) + '...'), - eventIds: Array.from(data.eventIds.entries()).map(([pubkey, eventId]) => ({ - pubkey: pubkey.substring(0, 16) + '...', - eventId: eventId.substring(0, 16) + '...' + // Only log in debug mode to reduce console noise + if (import.meta.env.DEV && false) { // Disable verbose logging + console.debug(`[FeedReactionButtons] Processed reactions summary:`, { + totalReactions: reactionEvents.length, + skippedInvalid, + reactionCounts: Array.from(reactionMap.entries()).map(([content, data]) => ({ + content, + count: data.pubkeys.size })) - })), - userReaction, - allReactionEvents: reactionEvents.map(r => ({ - id: r.id.substring(0, 16) + '...', - pubkey: r.pubkey.substring(0, 16) + '...', - content: r.content, - fullEvent: r - })) - }); + }); + } reactions = reactionMap; @@ -639,48 +592,51 @@ const isLoggedIn = $derived(sessionManager.isLoggedIn()); -{#if isLoggedIn}
{#if event.kind === KIND.DISCUSSION_THREAD || forceUpvoteDownvote} {:else} -
- - - { - toggleReaction(emoji); - showMenu = false; - }} - onClose={() => { showMenu = false; }} - /> -
+ {#if isLoggedIn} +
+ + + { + toggleReaction(emoji); + showMenu = false; + }} + onClose={() => { showMenu = false; }} + /> +
+ {/if} {#if event.kind !== KIND.DISCUSSION_THREAD} {#each getAllReactions() as { content, count }} @@ -711,7 +667,6 @@ {/if} {/if}
-{/if}