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

<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>