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

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