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.
169 lines
5.9 KiB
169 lines
5.9 KiB
<script lang="ts"> |
|
import Header from '../../../lib/components/layout/Header.svelte'; |
|
import PageHeader from '../../../lib/components/layout/PageHeader.svelte'; |
|
import DiscussionView from '../../../lib/modules/discussions/DiscussionView.svelte'; |
|
import EventView from '../../../lib/modules/events/EventView.svelte'; |
|
import { nostrClient } from '../../../lib/services/nostr/nostr-client.js'; |
|
import { relayManager } from '../../../lib/services/nostr/relay-manager.js'; |
|
import { onMount } from 'svelte'; |
|
import { page } from '$app/stores'; |
|
import { nip19 } from 'nostr-tools'; |
|
import { KIND } from '../../../lib/types/kind-lookup.js'; |
|
|
|
let decodedEventId = $state<string | null>(null); |
|
let eventKind = $state<number | null>(null); |
|
let loading = $state(false); |
|
let error = $state<string | null>(null); |
|
let lastProcessedParam = $state<string | null>(null); // Track last processed param to prevent loops |
|
|
|
/** |
|
* Decode route parameter to event hex ID |
|
* Supports: hex event id, note, nevent, naddr |
|
*/ |
|
async function decodeEventId(param: string): Promise<string | null> { |
|
if (!param) return null; |
|
|
|
// Check if it's already a hex event ID (64 hex characters) |
|
if (/^[0-9a-f]{64}$/i.test(param)) { |
|
return param.toLowerCase(); |
|
} |
|
|
|
// Check if it's a bech32 encoded format (note, nevent, naddr) |
|
if (/^(note|nevent|naddr)1[a-z0-9]+$/i.test(param)) { |
|
try { |
|
const decoded = nip19.decode(param); |
|
if (decoded.type === 'note') { |
|
return String(decoded.data); |
|
} else if (decoded.type === 'nevent') { |
|
// nevent contains event id and optional relays |
|
if (decoded.data && typeof decoded.data === 'object' && 'id' in decoded.data) { |
|
return String(decoded.data.id); |
|
} |
|
} else if (decoded.type === 'naddr') { |
|
// naddr is for parameterized replaceable events (kind + pubkey + d tag) |
|
// Fetch the event using kind, pubkey, and d tag |
|
if (decoded.data && typeof decoded.data === 'object' && 'kind' in decoded.data && 'pubkey' in decoded.data) { |
|
const naddrData = decoded.data as { kind: number; pubkey: string; identifier?: string; relays?: string[] }; |
|
const kind = naddrData.kind; |
|
const pubkey = naddrData.pubkey; |
|
const dTag = naddrData.identifier || ''; |
|
|
|
// Use relays from naddr if provided, otherwise use default relays |
|
const relays = naddrData.relays && naddrData.relays.length > 0 |
|
? naddrData.relays |
|
: relayManager.getProfileReadRelays(); |
|
|
|
// Fetch the event by kind, pubkey, and d tag |
|
const filter: any = { |
|
kinds: [kind], |
|
authors: [pubkey], |
|
limit: 1 |
|
}; |
|
|
|
// Only add #d filter if d tag is present |
|
if (dTag) { |
|
filter['#d'] = [dTag]; |
|
} |
|
|
|
const filters = [filter]; |
|
|
|
const events = await nostrClient.fetchEvents( |
|
filters, |
|
relays, |
|
{ useCache: true, cacheResults: true } |
|
); |
|
|
|
if (events.length > 0) { |
|
return events[0].id; |
|
} else { |
|
// Event not found for naddr |
|
return null; |
|
} |
|
} |
|
} |
|
} catch (error) { |
|
// Invalid bech32 format |
|
return null; |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
async function loadEvent() { |
|
const currentParam = $page.params.id; |
|
if (!currentParam) return; |
|
|
|
// Prevent loading if already loading the same param |
|
if (loading && lastProcessedParam === currentParam) { |
|
return; |
|
} |
|
|
|
loading = true; |
|
error = null; |
|
decodedEventId = null; |
|
eventKind = null; |
|
lastProcessedParam = currentParam; // Track that we're processing this param |
|
|
|
try { |
|
const eventId = await decodeEventId(currentParam); |
|
if (eventId) { |
|
decodedEventId = eventId; |
|
|
|
// Fetch the event to determine its kind for routing |
|
const threadRelays = relayManager.getThreadReadRelays(); |
|
const feedRelays = relayManager.getFeedReadRelays(); |
|
const allRelays = [...new Set([...threadRelays, ...feedRelays])]; |
|
const event = await nostrClient.getEventById(eventId, allRelays); |
|
if (event) { |
|
eventKind = event.kind; |
|
} |
|
} else { |
|
error = 'Event not found or invalid format'; |
|
} |
|
} catch (err) { |
|
// Failed to load event |
|
error = 'Failed to load event'; |
|
} finally { |
|
loading = false; |
|
} |
|
} |
|
|
|
onMount(async () => { |
|
await nostrClient.initialize(); |
|
await loadEvent(); |
|
}); |
|
|
|
$effect(() => { |
|
const currentParam = $page.params.id; |
|
// Only load if param changed and we're not already loading |
|
if (currentParam && currentParam !== lastProcessedParam && !loading) { |
|
loadEvent(); |
|
} |
|
}); |
|
</script> |
|
|
|
<Header /> |
|
|
|
<main class="container mx-auto px-4 py-8"> |
|
{#if loading} |
|
<PageHeader title="Loading event..." /> |
|
<p class="text-fog-text dark:text-fog-dark-text">Loading event...</p> |
|
{:else if error} |
|
<PageHeader title="Error" /> |
|
<p class="text-fog-text dark:text-fog-dark-text">{error}</p> |
|
{:else if decodedEventId} |
|
<PageHeader title="Event" /> |
|
{#if eventKind === KIND.DISCUSSION_THREAD} |
|
<!-- Route kind 11 (discussion threads) to DiscussionView --> |
|
<DiscussionView threadId={decodedEventId} /> |
|
{:else} |
|
<!-- Route all other events (including kind 30040, metadata-only, etc.) to EventView --> |
|
<EventView eventId={decodedEventId} /> |
|
{/if} |
|
{:else if $page.params.id} |
|
<p class="text-fog-text dark:text-fog-dark-text">Invalid event ID format. Supported: hex event ID, note, nevent, or naddr</p> |
|
{:else} |
|
<p class="text-fog-text dark:text-fog-dark-text">Event ID required</p> |
|
{/if} |
|
</main>
|
|
|