You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
143 lines
3.9 KiB
143 lines
3.9 KiB
<script lang="ts"> |
|
import DiscussionCard from './DiscussionCard.svelte'; |
|
import CommentThread from '../comments/CommentThread.svelte'; |
|
import { nostrClient } from '../../services/nostr/nostr-client.js'; |
|
import { relayManager } from '../../services/nostr/relay-manager.js'; |
|
import { onMount } from 'svelte'; |
|
import type { NostrEvent } from '../../types/nostr.js'; |
|
|
|
interface Props { |
|
threadId: string; |
|
} |
|
|
|
let { threadId }: Props = $props(); |
|
|
|
let rootEvent = $state<NostrEvent | null>(null); |
|
let loading = $state(true); |
|
|
|
onMount(async () => { |
|
await nostrClient.initialize(); |
|
loadRootEvent(); |
|
}); |
|
|
|
$effect(() => { |
|
if (threadId) { |
|
loadRootEvent(); |
|
} |
|
}); |
|
|
|
/** |
|
* Find the root OP event by traversing up the reply chain |
|
*/ |
|
async function findRootEvent(event: NostrEvent, visited: Set<string> = new Set()): Promise<NostrEvent> { |
|
// Prevent infinite loops |
|
if (visited.has(event.id)) { |
|
return event; |
|
} |
|
visited.add(event.id); |
|
|
|
// Check for 'root' tag first (NIP-10) - this directly points to the root |
|
const rootTag = event.tags.find((t) => t[0] === 'root'); |
|
if (rootTag && rootTag[1]) { |
|
// If root tag points to self, we're already at root |
|
if (rootTag[1] === event.id) { |
|
return event; |
|
} |
|
|
|
const relays = relayManager.getFeedReadRelays(); |
|
const rootEvent = await nostrClient.getEventById(rootTag[1], relays); |
|
if (rootEvent) { |
|
return rootEvent; |
|
} |
|
} |
|
|
|
// Check if this event has a parent 'e' tag (NIP-10) |
|
const eTags = event.tags.filter((t) => t[0] === 'e' && t[1] && t[1] !== event.id); |
|
|
|
// Prefer 'e' tag with 'reply' marker (NIP-10) |
|
let parentId: string | undefined; |
|
const replyTag = eTags.find((t) => t[3] === 'reply'); |
|
if (replyTag) { |
|
parentId = replyTag[1]; |
|
} else if (eTags.length > 0) { |
|
// Use first 'e' tag if no explicit reply marker |
|
parentId = eTags[0][1]; |
|
} |
|
|
|
if (!parentId) { |
|
// No parent - this is the root |
|
return event; |
|
} |
|
|
|
const relays = relayManager.getFeedReadRelays(); |
|
const parent = await nostrClient.getEventById(parentId, relays); |
|
|
|
if (!parent) { |
|
// Parent not found - treat current event as root |
|
return event; |
|
} |
|
|
|
// Recursively find root |
|
return findRootEvent(parent, visited); |
|
} |
|
|
|
async function loadRootEvent() { |
|
loading = true; |
|
try { |
|
const threadRelays = relayManager.getThreadReadRelays(); |
|
const feedRelays = relayManager.getFeedReadRelays(); |
|
const allRelays = [...new Set([...threadRelays, ...feedRelays])]; |
|
|
|
// Load the event by ID |
|
const event = await nostrClient.getEventById(threadId, allRelays); |
|
if (event) { |
|
// Find the root OP by traversing up the chain |
|
rootEvent = await findRootEvent(event); |
|
} |
|
} catch (error) { |
|
console.error('Error loading thread:', error); |
|
} finally { |
|
loading = false; |
|
} |
|
} |
|
</script> |
|
|
|
{#if loading} |
|
<p class="text-fog-text dark:text-fog-dark-text">Loading thread...</p> |
|
{:else if rootEvent} |
|
<article class="thread-view"> |
|
<!-- Display the root OP event (kind 11) using DiscussionCard --> |
|
<div class="op-section"> |
|
<DiscussionCard thread={rootEvent} fullView={true} /> |
|
</div> |
|
|
|
<!-- Display all replies (kind 1111 comments) using CommentThread --> |
|
<div class="comments-section"> |
|
<CommentThread threadId={rootEvent.id} event={rootEvent} /> |
|
</div> |
|
</article> |
|
{:else} |
|
<p class="text-fog-text dark:text-fog-dark-text">Thread not found</p> |
|
{/if} |
|
|
|
<style> |
|
.thread-view { |
|
max-width: var(--content-width); |
|
margin: 0 auto; |
|
padding: 1rem; |
|
} |
|
|
|
.op-section { |
|
margin-bottom: 2rem; |
|
padding-bottom: 1rem; |
|
border-bottom: 2px solid var(--fog-border, #e5e7eb); |
|
} |
|
|
|
:global(.dark) .op-section { |
|
border-bottom-color: var(--fog-dark-border, #374151); |
|
} |
|
|
|
.comments-section { |
|
margin-top: 2rem; |
|
} |
|
</style>
|
|
|