|
|
|
|
@ -297,51 +297,29 @@
@@ -297,51 +297,29 @@
|
|
|
|
|
|
|
|
|
|
if (!isMounted) return; |
|
|
|
|
|
|
|
|
|
// Fetch reactions with lowercase e |
|
|
|
|
const reactionsFetchPromise1 = nostrClient.fetchEvents( |
|
|
|
|
[{ kinds: [KIND.REACTION], '#e': threadIds, limit: config.feedLimit }], |
|
|
|
|
// Optimized: Fetch reactions with both #e and #E in single call (most relays support both) |
|
|
|
|
// If a relay rejects #E, it will just return empty results for that filter |
|
|
|
|
const reactionsFetchPromise = nostrClient.fetchEvents( |
|
|
|
|
[ |
|
|
|
|
{ kinds: [KIND.REACTION], '#e': threadIds, limit: config.feedLimit }, |
|
|
|
|
{ kinds: [KIND.REACTION], '#E': threadIds, limit: config.feedLimit } |
|
|
|
|
], |
|
|
|
|
reactionRelays, |
|
|
|
|
{ |
|
|
|
|
useCache: 'relay-first', |
|
|
|
|
useCache: 'cache-first', // Changed from relay-first for better performance |
|
|
|
|
cacheResults: true, |
|
|
|
|
timeout: config.standardTimeout, |
|
|
|
|
onUpdate: handleReactionUpdate |
|
|
|
|
} |
|
|
|
|
); |
|
|
|
|
activeFetchPromises.add(reactionsFetchPromise1); |
|
|
|
|
const reactionsWithLowerE = await reactionsFetchPromise1; |
|
|
|
|
activeFetchPromises.delete(reactionsFetchPromise1); |
|
|
|
|
activeFetchPromises.add(reactionsFetchPromise); |
|
|
|
|
const allReactions = await reactionsFetchPromise; |
|
|
|
|
activeFetchPromises.delete(reactionsFetchPromise); |
|
|
|
|
|
|
|
|
|
if (!isMounted) return; |
|
|
|
|
|
|
|
|
|
// Try uppercase filter |
|
|
|
|
let reactionsWithUpperE: NostrEvent[] = []; |
|
|
|
|
try { |
|
|
|
|
const reactionsFetchPromise2 = nostrClient.fetchEvents( |
|
|
|
|
[{ kinds: [KIND.REACTION], '#E': threadIds, limit: config.feedLimit }], |
|
|
|
|
reactionRelays, |
|
|
|
|
{ |
|
|
|
|
useCache: 'relay-first', |
|
|
|
|
cacheResults: true, |
|
|
|
|
timeout: config.standardTimeout, |
|
|
|
|
onUpdate: handleReactionUpdate |
|
|
|
|
} |
|
|
|
|
); |
|
|
|
|
activeFetchPromises.add(reactionsFetchPromise2); |
|
|
|
|
reactionsWithUpperE = await reactionsFetchPromise2; |
|
|
|
|
activeFetchPromises.delete(reactionsFetchPromise2); |
|
|
|
|
if (!isMounted) return; |
|
|
|
|
} catch (error) { |
|
|
|
|
if (isMounted) { |
|
|
|
|
console.log('[DiscussionList] Upper case #E filter rejected by relay (this is normal):', error); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Combine reactions |
|
|
|
|
for (const r of reactionsWithLowerE) { |
|
|
|
|
allReactionsMap.set(r.id, r); |
|
|
|
|
} |
|
|
|
|
for (const r of reactionsWithUpperE) { |
|
|
|
|
// Add all reactions to map (deduplication handled by Map) |
|
|
|
|
for (const r of allReactions) { |
|
|
|
|
allReactionsMap.set(r.id, r); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -352,65 +330,71 @@
@@ -352,65 +330,71 @@
|
|
|
|
|
updateVoteCountsMap(); |
|
|
|
|
voteCountsReady = true; |
|
|
|
|
|
|
|
|
|
// Fetch zap receipts (for sorting) |
|
|
|
|
// Optimized: Fetch zaps and comments in parallel (they're independent) |
|
|
|
|
if (!isMounted) return; |
|
|
|
|
const zapFetchPromise = nostrClient.fetchEvents( |
|
|
|
|
[{ kinds: [KIND.ZAP_RECEIPT], '#e': threadIds, limit: config.feedLimit }], |
|
|
|
|
zapRelays, |
|
|
|
|
{ useCache: 'relay-first', cacheResults: true, timeout: config.standardTimeout } |
|
|
|
|
{ useCache: 'cache-first', cacheResults: true, timeout: config.standardTimeout } |
|
|
|
|
); |
|
|
|
|
activeFetchPromises.add(zapFetchPromise); |
|
|
|
|
const allZapReceipts = await zapFetchPromise; |
|
|
|
|
activeFetchPromises.delete(zapFetchPromise); |
|
|
|
|
|
|
|
|
|
if (!isMounted) return; |
|
|
|
|
|
|
|
|
|
// Group zap receipts by thread ID (for sorting) |
|
|
|
|
const newZapReceiptsMap = new Map<string, NostrEvent[]>(); |
|
|
|
|
for (const zapReceipt of allZapReceipts) { |
|
|
|
|
const threadId = zapReceipt.tags.find((t: string[]) => t[0] === 'e')?.[1]; |
|
|
|
|
if (threadId && threadsMap.has(threadId)) { |
|
|
|
|
if (!newZapReceiptsMap.has(threadId)) { |
|
|
|
|
newZapReceiptsMap.set(threadId, []); |
|
|
|
|
} |
|
|
|
|
newZapReceiptsMap.get(threadId)!.push(zapReceipt); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
zapReceiptsMap = newZapReceiptsMap; |
|
|
|
|
|
|
|
|
|
// Batch-load comment counts for all threads |
|
|
|
|
if (!isMounted) return; |
|
|
|
|
const commentsFetchPromise = nostrClient.fetchEvents( |
|
|
|
|
[{ kinds: [KIND.COMMENT], '#E': threadIds, '#K': ['11'], limit: config.feedLimit }], |
|
|
|
|
commentRelays, |
|
|
|
|
{ useCache: 'relay-first', cacheResults: true, timeout: config.standardTimeout, priority: 'low' } |
|
|
|
|
{ useCache: 'cache-first', cacheResults: true, timeout: config.standardTimeout, priority: 'low' } |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// Track both promises for cleanup |
|
|
|
|
activeFetchPromises.add(zapFetchPromise); |
|
|
|
|
activeFetchPromises.add(commentsFetchPromise); |
|
|
|
|
const allComments = await commentsFetchPromise; |
|
|
|
|
activeFetchPromises.delete(commentsFetchPromise); |
|
|
|
|
|
|
|
|
|
if (!isMounted) return; |
|
|
|
|
try { |
|
|
|
|
const [allZapReceipts, allComments] = await Promise.all([ |
|
|
|
|
zapFetchPromise, |
|
|
|
|
commentsFetchPromise |
|
|
|
|
]); |
|
|
|
|
|
|
|
|
|
// Count comments per thread |
|
|
|
|
const newCommentsMap = new Map<string, number>(); |
|
|
|
|
for (const comment of allComments) { |
|
|
|
|
const threadId = comment.tags.find((t: string[]) => { |
|
|
|
|
const tagName = t[0]; |
|
|
|
|
return (tagName === 'e' || tagName === 'E') && t[1]; |
|
|
|
|
})?.[1]; |
|
|
|
|
if (!isMounted) return; |
|
|
|
|
|
|
|
|
|
if (threadId && threadsMap.has(threadId)) { |
|
|
|
|
newCommentsMap.set(threadId, (newCommentsMap.get(threadId) || 0) + 1); |
|
|
|
|
// Group zap receipts by thread ID (for sorting) |
|
|
|
|
const newZapReceiptsMap = new Map<string, NostrEvent[]>(); |
|
|
|
|
for (const zapReceipt of allZapReceipts) { |
|
|
|
|
const threadId = zapReceipt.tags.find((t: string[]) => t[0] === 'e')?.[1]; |
|
|
|
|
if (threadId && threadsMap.has(threadId)) { |
|
|
|
|
if (!newZapReceiptsMap.has(threadId)) { |
|
|
|
|
newZapReceiptsMap.set(threadId, []); |
|
|
|
|
} |
|
|
|
|
newZapReceiptsMap.get(threadId)!.push(zapReceipt); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Set count to 0 for threads with no comments |
|
|
|
|
for (const threadId of threadIds) { |
|
|
|
|
if (!newCommentsMap.has(threadId)) { |
|
|
|
|
newCommentsMap.set(threadId, 0); |
|
|
|
|
zapReceiptsMap = newZapReceiptsMap; |
|
|
|
|
|
|
|
|
|
if (!isMounted) return; |
|
|
|
|
|
|
|
|
|
// Count comments per thread |
|
|
|
|
const newCommentsMap = new Map<string, number>(); |
|
|
|
|
for (const comment of allComments) { |
|
|
|
|
const threadId = comment.tags.find((t: string[]) => { |
|
|
|
|
const tagName = t[0]; |
|
|
|
|
return (tagName === 'e' || tagName === 'E') && t[1]; |
|
|
|
|
})?.[1]; |
|
|
|
|
|
|
|
|
|
if (threadId && threadsMap.has(threadId)) { |
|
|
|
|
newCommentsMap.set(threadId, (newCommentsMap.get(threadId) || 0) + 1); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Set count to 0 for threads with no comments |
|
|
|
|
for (const threadId of threadIds) { |
|
|
|
|
if (!newCommentsMap.has(threadId)) { |
|
|
|
|
newCommentsMap.set(threadId, 0); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
commentsMap = newCommentsMap; |
|
|
|
|
} finally { |
|
|
|
|
// Clean up both promises |
|
|
|
|
activeFetchPromises.delete(zapFetchPromise); |
|
|
|
|
activeFetchPromises.delete(commentsFetchPromise); |
|
|
|
|
} |
|
|
|
|
commentsMap = newCommentsMap; |
|
|
|
|
} |
|
|
|
|
} catch (error) { |
|
|
|
|
console.error('Error loading thread data:', error); |
|
|
|
|
|