|
|
|
|
@ -21,6 +21,7 @@
@@ -21,6 +21,7 @@
|
|
|
|
|
let zapReceipts = $state<NostrEvent[]>([]); // kind 9735 (zap receipts) |
|
|
|
|
let loading = $state(true); |
|
|
|
|
let replyingTo = $state<NostrEvent | null>(null); |
|
|
|
|
let loadingPromise: Promise<void> | null = null; // Track ongoing load to prevent concurrent calls |
|
|
|
|
|
|
|
|
|
const isKind1 = $derived(event?.kind === 1); |
|
|
|
|
const rootKind = $derived(event?.kind || null); |
|
|
|
|
@ -32,7 +33,27 @@
@@ -32,7 +33,27 @@
|
|
|
|
|
// Reload comments when threadId or event changes |
|
|
|
|
$effect(() => { |
|
|
|
|
if (threadId) { |
|
|
|
|
loadComments(); |
|
|
|
|
// Access event to make it a dependency of this effect |
|
|
|
|
// This ensures the effect re-runs when event changes from undefined to the actual event |
|
|
|
|
const currentEvent = event; |
|
|
|
|
const currentIsKind1 = isKind1; |
|
|
|
|
|
|
|
|
|
// Prevent concurrent loads |
|
|
|
|
if (loadingPromise) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Load comments - filters will adapt based on whether event is available |
|
|
|
|
// Ensure nostrClient is initialized first |
|
|
|
|
loadingPromise = nostrClient.initialize().then(() => { |
|
|
|
|
return loadComments(); |
|
|
|
|
}).catch((error) => { |
|
|
|
|
console.error('Error initializing nostrClient in CommentThread:', error); |
|
|
|
|
// Still try to load comments even if initialization fails |
|
|
|
|
return loadComments(); |
|
|
|
|
}).finally(() => { |
|
|
|
|
loadingPromise = null; |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
@ -82,8 +103,12 @@
@@ -82,8 +103,12 @@
|
|
|
|
|
|
|
|
|
|
// For kind 1, 1244, 9735: check e tag |
|
|
|
|
if (replyEvent.kind === 1 || replyEvent.kind === 1244 || replyEvent.kind === 9735) { |
|
|
|
|
const eTag = replyEvent.tags.find((t) => t[0] === 'e' && t[1] !== replyEvent.id); |
|
|
|
|
if (eTag && eTag[1]) return eTag[1]; |
|
|
|
|
// For kind 1, check all e tags (NIP-10) |
|
|
|
|
const eTags = replyEvent.tags.filter((t) => t[0] === 'e' && t[1] && t[1] !== replyEvent.id); |
|
|
|
|
// Prefer e tag with 'reply' marker, otherwise use first e tag |
|
|
|
|
const replyTag = eTags.find((t) => t[3] === 'reply'); |
|
|
|
|
if (replyTag && replyTag[1]) return replyTag[1]; |
|
|
|
|
if (eTags.length > 0 && eTags[0][1]) return eTags[0][1]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return null; |
|
|
|
|
@ -127,11 +152,16 @@
@@ -127,11 +152,16 @@
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async function loadComments() { |
|
|
|
|
if (!threadId) { |
|
|
|
|
loading = false; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
loading = true; |
|
|
|
|
try { |
|
|
|
|
const relays = relayManager.getCommentReadRelays(); |
|
|
|
|
const feedRelays = relayManager.getFeedReadRelays(); |
|
|
|
|
const allRelays = [...new Set([...relays, ...feedRelays])]; |
|
|
|
|
// Use all relay sources: profileRelays + defaultRelays + user's inboxes + user's localrelays + cache |
|
|
|
|
// getProfileReadRelays() includes: defaultRelays + profileRelays + user inbox (which includes local relays from kind 10432) |
|
|
|
|
const allRelays = relayManager.getProfileReadRelays(); |
|
|
|
|
|
|
|
|
|
const replyFilters: any[] = []; |
|
|
|
|
|
|
|
|
|
@ -153,23 +183,34 @@
@@ -153,23 +183,34 @@
|
|
|
|
|
// Fetch zap receipts (kind 9735) |
|
|
|
|
replyFilters.push({ kinds: [9735], '#e': [threadId] }); |
|
|
|
|
|
|
|
|
|
console.log('CommentThread: Loading comments for threadId:', threadId, 'event kind:', event?.kind); |
|
|
|
|
console.log('CommentThread: Filters:', replyFilters); |
|
|
|
|
|
|
|
|
|
const allReplies = await nostrClient.fetchEvents( |
|
|
|
|
replyFilters, |
|
|
|
|
allRelays, |
|
|
|
|
{ useCache: true, cacheResults: true } |
|
|
|
|
{ useCache: true, cacheResults: true, timeout: 10000 } |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
console.log('CommentThread: Fetched', allReplies.length, 'replies'); |
|
|
|
|
|
|
|
|
|
// Filter to only replies that reference the root |
|
|
|
|
const rootReplies = allReplies.filter(reply => referencesRoot(reply)); |
|
|
|
|
|
|
|
|
|
console.log('CommentThread: Root replies:', rootReplies.length); |
|
|
|
|
|
|
|
|
|
// Separate by type |
|
|
|
|
comments = rootReplies.filter(e => e.kind === 1111); |
|
|
|
|
kind1Replies = rootReplies.filter(e => e.kind === 1); |
|
|
|
|
yakBacks = rootReplies.filter(e => e.kind === 1244); |
|
|
|
|
zapReceipts = rootReplies.filter(e => e.kind === 9735); |
|
|
|
|
|
|
|
|
|
console.log('CommentThread: Separated - comments:', comments.length, 'kind1Replies:', kind1Replies.length, 'yakBacks:', yakBacks.length, 'zapReceipts:', zapReceipts.length); |
|
|
|
|
|
|
|
|
|
// Recursively fetch all nested replies |
|
|
|
|
await fetchNestedReplies(); |
|
|
|
|
// Recursively fetch all nested replies (non-blocking - let it run in background) |
|
|
|
|
fetchNestedReplies().catch((error) => { |
|
|
|
|
console.error('Error fetching nested replies:', error); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
|
console.error('Error loading comments:', error); |
|
|
|
|
@ -179,9 +220,8 @@
@@ -179,9 +220,8 @@
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async function fetchNestedReplies() { |
|
|
|
|
const relays = relayManager.getCommentReadRelays(); |
|
|
|
|
const feedRelays = relayManager.getFeedReadRelays(); |
|
|
|
|
const allRelays = [...new Set([...relays, ...feedRelays])]; |
|
|
|
|
// Use all relay sources: profileRelays + defaultRelays + user's inboxes + user's localrelays + cache |
|
|
|
|
const allRelays = relayManager.getProfileReadRelays(); |
|
|
|
|
let hasNewReplies = true; |
|
|
|
|
let iterations = 0; |
|
|
|
|
const maxIterations = 10; |
|
|
|
|
@ -272,16 +312,32 @@
@@ -272,16 +312,32 @@
|
|
|
|
|
// Second pass: determine parent-child relationships |
|
|
|
|
for (const item of items) { |
|
|
|
|
const parentId = getParentEventId(item.event); |
|
|
|
|
console.log('CommentThread: sortThreadItems - item:', item.type, item.event.id.slice(0, 8), 'parentId:', parentId ? parentId.slice(0, 8) : 'null', 'threadId:', threadId.slice(0, 8)); |
|
|
|
|
|
|
|
|
|
if (parentId && (parentId === threadId || allEventIds.has(parentId))) { |
|
|
|
|
// This is a reply |
|
|
|
|
if (!replyMap.has(parentId)) { |
|
|
|
|
replyMap.set(parentId, []); |
|
|
|
|
if (parentId) { |
|
|
|
|
if (parentId === threadId) { |
|
|
|
|
// This is a direct reply to the root OP |
|
|
|
|
if (!replyMap.has(threadId)) { |
|
|
|
|
replyMap.set(threadId, []); |
|
|
|
|
} |
|
|
|
|
replyMap.get(threadId)!.push(item.event.id); |
|
|
|
|
console.log('CommentThread: Added to replyMap for root threadId:', threadId.slice(0, 8)); |
|
|
|
|
} else if (allEventIds.has(parentId)) { |
|
|
|
|
// This is a reply to another reply |
|
|
|
|
if (!replyMap.has(parentId)) { |
|
|
|
|
replyMap.set(parentId, []); |
|
|
|
|
} |
|
|
|
|
replyMap.get(parentId)!.push(item.event.id); |
|
|
|
|
console.log('CommentThread: Added to replyMap for parent:', parentId.slice(0, 8)); |
|
|
|
|
} else { |
|
|
|
|
// Parent not found - treat as root item (orphaned reply) |
|
|
|
|
rootItems.push(item); |
|
|
|
|
console.log('CommentThread: Added to rootItems (orphaned)'); |
|
|
|
|
} |
|
|
|
|
replyMap.get(parentId)!.push(item.event.id); |
|
|
|
|
} else { |
|
|
|
|
// No parent or parent not found - treat as root item |
|
|
|
|
// No parent - treat as root item (direct reply without parent tag) |
|
|
|
|
rootItems.push(item); |
|
|
|
|
console.log('CommentThread: Added to rootItems (no parent)'); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -307,7 +363,18 @@
@@ -307,7 +363,18 @@
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Add all root items sorted by time |
|
|
|
|
// First, add all direct replies to the root OP (threadId) |
|
|
|
|
const rootReplies = replyMap.get(threadId) || []; |
|
|
|
|
const rootReplyItems = rootReplies |
|
|
|
|
.map(id => eventMap.get(id)) |
|
|
|
|
.filter((item): item is { event: NostrEvent; type: 'comment' | 'reply' | 'yak' | 'zap' } => item !== undefined) |
|
|
|
|
.sort((a, b) => a.event.created_at - b.event.created_at); |
|
|
|
|
|
|
|
|
|
for (const reply of rootReplyItems) { |
|
|
|
|
addThread(reply); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Then, add all root items (orphaned replies) sorted by time |
|
|
|
|
rootItems.sort((a, b) => a.event.created_at - b.event.created_at); |
|
|
|
|
for (const root of rootItems) { |
|
|
|
|
addThread(root); |
|
|
|
|
@ -323,7 +390,10 @@
@@ -323,7 +390,10 @@
|
|
|
|
|
...yakBacks.map(y => ({ event: y, type: 'yak' as const })), |
|
|
|
|
...zapReceipts.map(z => ({ event: z, type: 'zap' as const })) |
|
|
|
|
]; |
|
|
|
|
return sortThreadItems(items); |
|
|
|
|
console.log('CommentThread: getThreadItems - items before sort:', items.length, items.map(i => ({ type: i.type, id: i.event.id.slice(0, 8) }))); |
|
|
|
|
const sorted = sortThreadItems(items); |
|
|
|
|
console.log('CommentThread: getThreadItems - items after sort:', sorted.length, sorted.map(i => ({ type: i.type, id: i.event.id.slice(0, 8) }))); |
|
|
|
|
return sorted; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function handleReply(replyEvent: NostrEvent) { |
|
|
|
|
|