Browse Source

pass known event to /event page

master
Silberengel 4 weeks ago
parent
commit
2f7010a40d
  1. 2
      src/lib/components/EventMenu.svelte
  2. 2
      src/lib/components/content/EmbeddedEventBlurb.svelte
  3. 2
      src/lib/components/content/HighlightOverlay.svelte
  4. 2
      src/lib/components/content/QuotedContext.svelte
  5. 2
      src/lib/components/content/ReferencedEventPreview.svelte
  6. 2
      src/lib/components/content/ReplyContext.svelte
  7. 2
      src/lib/components/find/SearchAddressableEvents.svelte
  8. 25
      src/lib/components/layout/ProfileBadge.svelte
  9. 2
      src/lib/components/layout/SearchBox.svelte
  10. 2
      src/lib/components/layout/UnifiedSearch.svelte
  11. 2
      src/lib/components/profile/BookmarksPanel.svelte
  12. 2
      src/lib/components/profile/ProfileEventsPanel.svelte
  13. 2
      src/lib/components/write/CreateEventForm.svelte
  14. 2
      src/lib/components/write/EditEventForm.svelte
  15. 3
      src/lib/components/write/FindEventForm.svelte
  16. 6
      src/lib/modules/comments/Comment.svelte
  17. 2
      src/lib/modules/discussions/DiscussionCard.svelte
  18. 2
      src/lib/modules/discussions/DiscussionList.svelte
  19. 48
      src/lib/modules/events/EventView.svelte
  20. 14
      src/lib/modules/feed/FeedPost.svelte
  21. 6
      src/lib/modules/feed/HighlightCard.svelte
  22. 6
      src/lib/modules/feed/Reply.svelte
  23. 134
      src/lib/modules/profiles/ProfilePage.svelte
  24. 11
      src/lib/services/event-links.ts
  25. 10
      src/lib/services/nostr/event-index-loader.ts
  26. 4
      src/routes/cache/+page.svelte
  27. 3
      src/routes/discussions/+page.svelte
  28. 114
      src/routes/event/[id]/+page.svelte
  29. 6
      src/routes/find/+page.svelte
  30. 4
      src/routes/highlights/+page.svelte
  31. 13
      src/routes/profile/[pubkey]/+page.svelte
  32. 2
      src/routes/repos/+page.svelte
  33. 2
      src/routes/repos/[naddr]/+page.svelte
  34. 2
      src/routes/topics/+page.svelte

2
src/lib/components/EventMenu.svelte

@ -271,6 +271,8 @@
function viewEvent() { function viewEvent() {
closeMenu(); closeMenu();
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(event));
goto(getEventLink(event)); goto(getEventLink(event));
} }

2
src/lib/components/content/EmbeddedEventBlurb.svelte

@ -168,6 +168,8 @@
<button <button
class="view-button" class="view-button"
onclick={() => { onclick={() => {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(event!));
goto(getEventLink(event!)); goto(getEventLink(event!));
}} }}
aria-label="View referenced post" aria-label="View referenced post"

2
src/lib/components/content/HighlightOverlay.svelte

@ -18,6 +18,8 @@
let tooltipPosition = $state({ top: 0, left: 0 }); let tooltipPosition = $state({ top: 0, left: 0 });
function openHighlight(highlight: Highlight) { function openHighlight(highlight: Highlight) {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(highlight.event));
goto(`/event/${highlight.event.id}`); goto(`/event/${highlight.event.id}`);
} }

2
src/lib/components/content/QuotedContext.svelte

@ -148,6 +148,8 @@
<button <button
class="view-button" class="view-button"
onclick={() => { onclick={() => {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(quotedEvent!));
goto(getEventLink(quotedEvent!)); goto(getEventLink(quotedEvent!));
}} }}
aria-label="View quoted post" aria-label="View quoted post"

2
src/lib/components/content/ReferencedEventPreview.svelte

@ -208,6 +208,8 @@
function handleViewEvent() { function handleViewEvent() {
if (referencedEvent) { if (referencedEvent) {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(referencedEvent));
goto(`/event/${referencedEvent.id}`); goto(`/event/${referencedEvent.id}`);
} }
} }

2
src/lib/components/content/ReplyContext.svelte

@ -195,6 +195,8 @@
<button <button
class="view-button" class="view-button"
onclick={() => { onclick={() => {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(parentEvent!));
goto(getEventLink(parentEvent!)); goto(getEventLink(parentEvent!));
}} }}
aria-label="View original post" aria-label="View original post"

2
src/lib/components/find/SearchAddressableEvents.svelte

@ -438,6 +438,8 @@
function handleResultClick(event: NostrEvent) { function handleResultClick(event: NostrEvent) {
// Navigate to /event route with event ID // Navigate to /event route with event ID
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(event));
goto(`/event/${event.id}`); goto(`/event/${event.id}`);
} }

25
src/lib/components/layout/ProfileBadge.svelte

@ -1,7 +1,10 @@
<script lang="ts"> <script lang="ts">
import { getActivityStatus, getActivityMessage } from '../../services/auth/activity-tracker.js'; import { getActivityStatus, getActivityMessage } from '../../services/auth/activity-tracker.js';
import { fetchProfile } from '../../services/user-data.js'; import { fetchProfile } from '../../services/user-data.js';
import { getProfile } from '../../services/cache/profile-cache.js';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import { goto } from '$app/navigation';
import type { NostrEvent } from '../../types/nostr.js';
interface Props { interface Props {
pubkey: string; pubkey: string;
@ -142,9 +145,29 @@
let shouldShowNip05 = $derived.by(() => { let shouldShowNip05 = $derived.by(() => {
return !!(profile?.nip05 && profile.nip05.length > 0); return !!(profile?.nip05 && profile.nip05.length > 0);
}); });
// Handle click to pass kind 0 event to profile page
async function handleProfileClick(e: MouseEvent) {
e.preventDefault();
// Try to get the kind 0 event from cache
try {
const cached = await getProfile(pubkey);
if (cached && cached.event) {
// Store the kind 0 event in sessionStorage so the profile page can use it
sessionStorage.setItem('aitherboard_preloadedProfileEvent', JSON.stringify(cached.event));
}
} catch (error) {
// Cache read failed - continue without preloaded event
console.warn('Failed to get profile event from cache:', error);
}
// Navigate to profile page
goto(`/profile/${pubkey}`);
}
</script> </script>
<a href="/profile/{pubkey}" class="profile-badge inline-flex min-w-0 max-w-full" class:picture-only={pictureOnly} class:inline-badge={inline} class:gap-2={!inline}> <a href="/profile/{pubkey}" onclick={handleProfileClick} class="profile-badge inline-flex min-w-0 max-w-full" class:picture-only={pictureOnly} class:inline-badge={inline} class:gap-2={!inline}>
{#if !inline || pictureOnly} {#if !inline || pictureOnly}
{#if profile?.picture && !imageError} {#if profile?.picture && !imageError}
{@const compressedPictureUrl = (() => { {@const compressedPictureUrl = (() => {

2
src/lib/components/layout/SearchBox.svelte

@ -191,6 +191,8 @@
function handleResultClick(event: NostrEvent) { function handleResultClick(event: NostrEvent) {
showResults = false; showResults = false;
searchQuery = ''; searchQuery = '';
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(event));
goto(`/event/${event.id}`); goto(`/event/${event.id}`);
} }

2
src/lib/components/layout/UnifiedSearch.svelte

@ -1100,6 +1100,8 @@
function handleResultClick(event: NostrEvent) { function handleResultClick(event: NostrEvent) {
showResults = false; showResults = false;
searchQuery = ''; searchQuery = '';
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(event));
goto(`/event/${event.id}`); goto(`/event/${event.id}`);
} }

2
src/lib/components/profile/BookmarksPanel.svelte

@ -20,6 +20,8 @@
let loading = $state(true); let loading = $state(true);
function navigateToEvent(event: NostrEvent) { function navigateToEvent(event: NostrEvent) {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(event));
goto(`/event/${event.id}`); goto(`/event/${event.id}`);
} }

2
src/lib/components/profile/ProfileEventsPanel.svelte

@ -135,6 +135,8 @@
if (results.success.length > 0) { if (results.success.length > 0) {
setTimeout(() => { setTimeout(() => {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(signedEvent));
goto(`/event/${signedEvent.id}`); goto(`/event/${signedEvent.id}`);
}, 5000); }, 5000);
} }

2
src/lib/components/write/CreateEventForm.svelte

@ -389,6 +389,8 @@
} }
await deleteDraft(DRAFT_ID); await deleteDraft(DRAFT_ID);
setTimeout(() => { setTimeout(() => {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(signedEvent));
goto(`/event/${signedEvent.id}`); goto(`/event/${signedEvent.id}`);
}, 5000); }, 5000);
} }

2
src/lib/components/write/EditEventForm.svelte

@ -96,6 +96,8 @@
// If successful, wait 5 seconds and navigate // If successful, wait 5 seconds and navigate
if (results.success.length > 0) { if (results.success.length > 0) {
setTimeout(() => { setTimeout(() => {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(signedEvent));
goto(`/event/${signedEvent.id}`); goto(`/event/${signedEvent.id}`);
}, 5000); }, 5000);
} }

3
src/lib/components/write/FindEventForm.svelte

@ -6,6 +6,7 @@
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import type { NostrEvent } from '../../types/nostr.js'; import type { NostrEvent } from '../../types/nostr.js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { goto } from '$app/navigation';
// @ts-ignore - highlight.js default export works at runtime // @ts-ignore - highlight.js default export works at runtime
import hljs from 'highlight.js'; import hljs from 'highlight.js';
import 'highlight.js/styles/vs2015.css'; import 'highlight.js/styles/vs2015.css';
@ -159,7 +160,7 @@
<div class="found-event"> <div class="found-event">
<div class="event-header"> <div class="event-header">
<h3 class="event-title">Found Event</h3> <h3 class="event-title">Found Event</h3>
<a href="/event/{foundEvent.id}" class="view-link" target="_blank">View in /event page →</a> <a href="/event/{foundEvent.id}" onclick={(e) => { e.preventDefault(); if (foundEvent) { sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(foundEvent)); goto(`/event/${foundEvent.id}`); } }} class="view-link">View in /event page →</a>
</div> </div>
<div class="event-json"> <div class="event-json">

6
src/lib/modules/comments/Comment.svelte

@ -155,7 +155,11 @@
icon="eye" icon="eye"
label="View" label="View"
size={16} size={16}
onclick={() => goto(getEventLink(comment))} onclick={() => {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(comment));
goto(getEventLink(comment));
}}
/> />
{#if sessionManager.isLoggedIn() && onReply} {#if sessionManager.isLoggedIn() && onReply}
<IconButton <IconButton

2
src/lib/modules/discussions/DiscussionCard.svelte

@ -284,7 +284,7 @@
<article class="thread-card bg-fog-post dark:bg-fog-dark-post border border-fog-border dark:border-fog-dark-border p-4 mb-4 shadow-sm dark:shadow-lg"> <article class="thread-card bg-fog-post dark:bg-fog-dark-post border border-fog-border dark:border-fog-dark-border p-4 mb-4 shadow-sm dark:shadow-lg">
{#if !fullView} {#if !fullView}
<a href="/event/{thread.id}" class="card-link"> <a href="/event/{thread.id}" onclick={(e) => { e.preventDefault(); sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(thread)); goto(`/event/${thread.id}`); }} class="card-link">
<div class="card-content" class:expanded={expanded} bind:this={contentElement}> <div class="card-content" class:expanded={expanded} bind:this={contentElement}>
<CardHeader <CardHeader
pubkey={thread.pubkey} pubkey={thread.pubkey}

2
src/lib/modules/discussions/DiscussionList.svelte

@ -581,6 +581,8 @@
return; return;
} }
} }
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(event));
goto(`/event/${event.id}`); goto(`/event/${event.id}`);
} }

48
src/lib/modules/events/EventView.svelte

@ -11,9 +11,10 @@
interface Props { interface Props {
eventId: string; eventId: string;
preloadedEvent?: NostrEvent | null; // Optional preloaded event to avoid re-fetching
} }
let { eventId }: Props = $props(); let { eventId, preloadedEvent = null }: Props = $props();
// Virtual scrolling for kind 30040 (event indexes with 36k+ events) // Virtual scrolling for kind 30040 (event indexes with 36k+ events)
let Virtualizer: any = $state(null); let Virtualizer: any = $state(null);
@ -120,8 +121,17 @@
$effect(() => { $effect(() => {
// Only load if eventId changed and we're not already loading // Only load if eventId changed and we're not already loading
// Also reload if preloadedEvent changes (e.g., from null to an actual event)
if (eventId && eventId !== lastLoadedEventId && !loading) { if (eventId && eventId !== lastLoadedEventId && !loading) {
loadEvent(); loadEvent();
} else if (preloadedEvent && preloadedEvent.id === eventId && !rootEvent && !loading) {
// If we have a preloaded event and haven't loaded yet, use it immediately
rootEvent = preloadedEvent;
loading = false;
if (preloadedEvent.kind === 30040) {
loadEventIndexHierarchy(preloadedEvent);
loadVirtualizer();
}
} }
}); });
@ -139,12 +149,18 @@
lastLoadedEventId = eventId; // Track that we're loading this eventId lastLoadedEventId = eventId; // Track that we're loading this eventId
try { try {
const threadRelays = relayManager.getThreadReadRelays(); // Use preloaded event if available and matches eventId
const feedRelays = relayManager.getFeedReadRelays(); let event: NostrEvent | null = null;
const allRelays = [...new Set([...threadRelays, ...feedRelays])]; if (preloadedEvent && preloadedEvent.id === eventId) {
event = preloadedEvent;
} else {
// Load the event by ID
const threadRelays = relayManager.getThreadReadRelays();
const feedRelays = relayManager.getFeedReadRelays();
const allRelays = [...new Set([...threadRelays, ...feedRelays])];
event = await nostrClient.getEventById(eventId, allRelays);
}
// Load the event by ID
const event = await nostrClient.getEventById(eventId, allRelays);
if (event) { if (event) {
rootEvent = event; rootEvent = event;
@ -172,20 +188,20 @@
indexError = null; indexError = null;
missingEvents = []; missingEvents = [];
try { try {
console.log('Loading event index hierarchy for kind 30040...'); // console.log('Loading event index hierarchy for kind 30040...');
const result = await loadEventIndex(opEvent); const result = await loadEventIndex(opEvent);
eventIndexItems = result.items; eventIndexItems = result.items;
missingEvents = result.missingEvents; missingEvents = result.missingEvents;
console.log(`Loaded ${result.items.length} events from index hierarchy`); // console.log(`Loaded ${result.items.length} events from index hierarchy`);
// Debug: log items with children // Debug: log items with children
const itemsWithChildren = result.items.filter(item => item.children && item.children.length > 0); // const itemsWithChildren = result.items.filter(item => item.children && item.children.length > 0);
console.log(`[EventView] Items with children: ${itemsWithChildren.length}`, itemsWithChildren.map(item => ({ // console.log(`[EventView] Items with children: ${itemsWithChildren.length}`, itemsWithChildren.map(item => ({
id: item.event.id, // id: item.event.id,
kind: item.event.kind, // kind: item.event.kind,
level: item.level, // level: item.level,
childrenCount: item.children?.length || 0, // childrenCount: item.children?.length || 0,
title: item.event.tags.find(t => t[0] === 'title')?.[1] // title: item.event.tags.find(t => t[0] === 'title')?.[1]
}))); // })));
if (result.missingEvents.length > 0) { if (result.missingEvents.length > 0) {
console.warn(`[EventView] ${result.missingEvents.length} events are missing from the index hierarchy`); console.warn(`[EventView] ${result.missingEvents.length} events are missing from the index hierarchy`);
} }

14
src/lib/modules/feed/FeedPost.svelte

@ -956,6 +956,8 @@
} else if (e.key === 'Enter' && !showReplyForm) { } else if (e.key === 'Enter' && !showReplyForm) {
// Enter to view full event // Enter to view full event
e.preventDefault(); e.preventDefault();
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(post));
goto(getEventLink(post)); goto(getEventLink(post));
} }
}} }}
@ -985,7 +987,11 @@
icon="eye" icon="eye"
label="View" label="View"
size={16} size={16}
onclick={() => goto(getEventLink(post))} onclick={() => {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(post));
goto(getEventLink(post));
}}
/> />
{#if isLoggedIn} {#if isLoggedIn}
<IconButton <IconButton
@ -1069,7 +1075,11 @@
icon="eye" icon="eye"
label="View" label="View"
size={16} size={16}
onclick={() => goto(getEventLink(post))} onclick={() => {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(post));
goto(getEventLink(post));
}}
/> />
{#if isLoggedIn} {#if isLoggedIn}
<IconButton <IconButton

6
src/lib/modules/feed/HighlightCard.svelte

@ -361,7 +361,11 @@
icon="eye" icon="eye"
label="View" label="View"
size={16} size={16}
onclick={() => goto(getEventLink(highlight))} onclick={() => {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(highlight));
goto(getEventLink(highlight));
}}
/> />
{#if isLoggedIn} {#if isLoggedIn}
<IconButton <IconButton

6
src/lib/modules/feed/Reply.svelte

@ -98,7 +98,11 @@
icon="eye" icon="eye"
label="View" label="View"
size={16} size={16}
onclick={() => goto(getEventLink(reply))} onclick={() => {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(reply));
goto(getEventLink(reply));
}}
/> />
{#if sessionManager.isLoggedIn() && onReply} {#if sessionManager.isLoggedIn() && onReply}
<IconButton <IconButton

134
src/lib/modules/profiles/ProfilePage.svelte

@ -27,19 +27,22 @@
let notifications = $state<NostrEvent[]>([]); let notifications = $state<NostrEvent[]>([]);
let interactionsWithMe = $state<NostrEvent[]>([]); let interactionsWithMe = $state<NostrEvent[]>([]);
let wallComments = $state<NostrEvent[]>([]); // Kind 1111 comments on the wall let wallComments = $state<NostrEvent[]>([]); // Kind 1111 comments on the wall
let mediaEvents = $state<NostrEvent[]>([]); // Kinds 20, 21, 22
let hasMedia = $state(false); // Whether kinds 20, 21, 22 are available
let loading = $state(true); let loading = $state(true);
let loadingWall = $state(false); let loadingWall = $state(false);
let activeTab = $state<'pins' | 'notifications' | 'interactions' | 'wall'>('pins'); let loadingMedia = $state(false);
let activeTab = $state<'pins' | 'media' | 'notifications' | 'interactions' | 'wall'>('pins');
let nip05Validations = $state<Record<string, boolean | null>>({}); // null = checking, true = valid, false = invalid let nip05Validations = $state<Record<string, boolean | null>>({}); // null = checking, true = valid, false = invalid
// Compute pubkey from route params // Compute pubkey from route params
let profilePubkey = $derived.by(() => decodePubkey($page.params.pubkey)); let profilePubkey = $derived.by(() => decodePubkey($page.params.pubkey));
// Initialize activeTab from URL parameter // Initialize activeTab from URL parameter
function getTabFromUrl(): 'pins' | 'notifications' | 'interactions' | 'wall' { function getTabFromUrl(): 'pins' | 'media' | 'notifications' | 'interactions' | 'wall' {
const tabParam = $page.url.searchParams.get('tab'); const tabParam = $page.url.searchParams.get('tab');
const validTabs: Array<'pins' | 'notifications' | 'interactions' | 'wall'> = ['pins', 'notifications', 'interactions', 'wall']; const validTabs: Array<'pins' | 'media' | 'notifications' | 'interactions' | 'wall'> = ['pins', 'media', 'notifications', 'interactions', 'wall'];
if (tabParam && validTabs.includes(tabParam as any)) { if (tabParam && validTabs.includes(tabParam as any)) {
return tabParam as 'pins' | 'notifications' | 'interactions' | 'wall'; return tabParam as 'pins' | 'media' | 'notifications' | 'interactions' | 'wall';
} }
return 'pins'; // Default return 'pins'; // Default
} }
@ -52,12 +55,14 @@
// Load data for the tab if needed // Load data for the tab if needed
if (activeTab === 'wall' && profileEvent && wallComments.length === 0 && !loadingWall) { if (activeTab === 'wall' && profileEvent && wallComments.length === 0 && !loadingWall) {
loadWallComments(profileEvent.id); loadWallComments(profileEvent.id);
} else if (activeTab === 'media' && profilePubkey && mediaEvents.length === 0 && !loadingMedia) {
loadMedia(profilePubkey);
} }
} }
}); });
// Function to change tab and update URL // Function to change tab and update URL
async function setActiveTab(tab: 'pins' | 'notifications' | 'interactions' | 'wall') { async function setActiveTab(tab: 'pins' | 'media' | 'notifications' | 'interactions' | 'wall') {
activeTab = tab; activeTab = tab;
const url = new URL($page.url); const url = new URL($page.url);
url.searchParams.set('tab', tab); url.searchParams.set('tab', tab);
@ -73,6 +78,11 @@
if (profileEvent && wallComments.length === 0 && !loadingWall) { if (profileEvent && wallComments.length === 0 && !loadingWall) {
await loadWallComments(profileEvent.id); await loadWallComments(profileEvent.id);
} }
} else if (tab === 'media') {
// Load media if not already loaded
if (profilePubkey && mediaEvents.length === 0 && !loadingMedia) {
await loadMedia(profilePubkey);
}
} }
} }
@ -163,6 +173,29 @@
async function loadProfileEvent(pubkey: string) { async function loadProfileEvent(pubkey: string) {
if (!isMounted) return; if (!isMounted) return;
try { try {
// Check sessionStorage for a preloaded kind 0 event (from ProfileBadge click)
if (typeof window !== 'undefined') {
const preloadedEventStr = sessionStorage.getItem('aitherboard_preloadedProfileEvent');
if (preloadedEventStr) {
try {
const preloadedEvent = JSON.parse(preloadedEventStr) as NostrEvent;
// Verify the event is a kind 0 event and matches the pubkey
if (preloadedEvent.kind === KIND.METADATA && preloadedEvent.pubkey === pubkey) {
// Use the preloaded event
profileEvent = preloadedEvent;
// Clear it after reading
sessionStorage.removeItem('aitherboard_preloadedProfileEvent');
// Don't load wall comments here - load them when Wall tab is clicked
return;
}
} catch (parseError) {
// Invalid JSON in sessionStorage, continue with normal loading
console.warn('Failed to parse preloaded profile event from sessionStorage:', parseError);
sessionStorage.removeItem('aitherboard_preloadedProfileEvent');
}
}
}
// Try cache first // Try cache first
const cached = await getProfile(pubkey); const cached = await getProfile(pubkey);
if (cached) { if (cached) {
@ -337,6 +370,70 @@
} }
} }
async function checkMediaAvailability(pubkey: string) {
if (!isMounted) return;
try {
const profileRelays = relayManager.getProfileReadRelays();
// Check if any events of kinds 20, 21, 22 exist for this pubkey
const mediaCheck = await nostrClient.fetchEvents(
[{ kinds: [KIND.PICTURE_NOTE, KIND.VIDEO_NOTE, KIND.SHORT_VIDEO_NOTE], authors: [pubkey], limit: 1 }],
profileRelays,
{ useCache: 'cache-first', cacheResults: true, timeout: config.shortTimeout }
);
if (!isMounted) return;
hasMedia = mediaCheck.length > 0;
} catch (error) {
// Failed to check - assume no media
if (isMounted) hasMedia = false;
}
}
async function loadMedia(pubkey: string) {
if (!isMounted) return;
loadingMedia = true;
try {
const profileRelays = relayManager.getFeedReadRelays();
const fetchPromise = nostrClient.fetchEvents(
[{ kinds: [KIND.PICTURE_NOTE, KIND.VIDEO_NOTE, KIND.SHORT_VIDEO_NOTE], authors: [pubkey], limit: config.feedLimit }],
profileRelays,
{
useCache: 'cache-first',
cacheResults: true,
timeout: config.shortTimeout,
onUpdate: (newMedia) => {
if (!isMounted) return;
// Merge with existing media
const mediaMap = new Map(mediaEvents.map(m => [m.id, m]));
for (const media of newMedia) {
mediaMap.set(media.id, media);
}
mediaEvents = Array.from(mediaMap.values()).sort((a, b) => b.created_at - a.created_at);
}
}
);
activeFetchPromises.add(fetchPromise);
const fetchedMedia = await fetchPromise;
activeFetchPromises.delete(fetchPromise);
if (!isMounted) return;
// Sort by created_at descending
mediaEvents = fetchedMedia.sort((a, b) => b.created_at - a.created_at);
hasMedia = mediaEvents.length > 0;
} catch (error) {
// Failed to load media
if (isMounted) {
mediaEvents = [];
hasMedia = false;
}
} finally {
if (isMounted) {
loadingMedia = false;
}
}
}
async function loadNotifications(pubkey: string) { async function loadNotifications(pubkey: string) {
if (!isMounted) return; if (!isMounted) return;
try { try {
@ -713,6 +810,11 @@
// Failed to load pins - non-critical // Failed to load pins - non-critical
})); }));
// Check if media (kinds 20, 21, 22) are available
loadPromises.push(checkMediaAvailability(pubkey).catch(() => {
// Failed to check media - non-critical
}));
// Load notifications or interactions based on profile type // Load notifications or interactions based on profile type
if (isOwnProfile) { if (isOwnProfile) {
loadPromises.push(loadNotifications(pubkey).catch(() => { loadPromises.push(loadNotifications(pubkey).catch(() => {
@ -783,6 +885,8 @@
interactionsWithMe = []; interactionsWithMe = [];
wallComments = []; wallComments = [];
pins = []; pins = [];
mediaEvents = [];
hasMedia = false;
await loadProfile(); await loadProfile();
} }
export { refresh }; export { refresh };
@ -877,6 +981,14 @@
> >
Pins ({pins.length}) Pins ({pins.length})
</button> </button>
{#if hasMedia}
<button
onclick={() => setActiveTab('media')}
class="px-2 sm:px-4 py-2 font-semibold whitespace-nowrap flex-shrink-0 {activeTab === 'media' ? 'border-b-2 border-fog-accent dark:border-fog-dark-accent' : ''}"
>
Media ({mediaEvents.length})
</button>
{/if}
<button <button
onclick={() => setActiveTab('wall')} onclick={() => setActiveTab('wall')}
class="px-2 sm:px-4 py-2 font-semibold whitespace-nowrap flex-shrink-0 {activeTab === 'wall' ? 'border-b-2 border-fog-accent dark:border-fog-dark-accent' : ''}" class="px-2 sm:px-4 py-2 font-semibold whitespace-nowrap flex-shrink-0 {activeTab === 'wall' ? 'border-b-2 border-fog-accent dark:border-fog-dark-accent' : ''}"
@ -910,6 +1022,18 @@
{/each} {/each}
</div> </div>
{/if} {/if}
{:else if activeTab === 'media'}
{#if loadingMedia}
<p class="text-fog-text-light dark:text-fog-dark-text-light">Loading media...</p>
{:else if mediaEvents.length === 0}
<p class="text-fog-text-light dark:text-fog-dark-text-light">No media posts yet.</p>
{:else}
<div class="media-list">
{#each mediaEvents as media (media.id)}
<FeedPost post={media} />
{/each}
</div>
{/if}
{:else if activeTab === 'notifications'} {:else if activeTab === 'notifications'}
{#if notifications.length === 0} {#if notifications.length === 0}
<p class="text-fog-text-light dark:text-fog-dark-text-light">No notifications yet.</p> <p class="text-fog-text-light dark:text-fog-dark-text-light">No notifications yet.</p>

11
src/lib/services/event-links.ts

@ -4,6 +4,7 @@
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import type { NostrEvent } from '../types/nostr.js'; import type { NostrEvent } from '../types/nostr.js';
import { goto } from '$app/navigation';
/** /**
* Generate a link to view an event * Generate a link to view an event
@ -44,3 +45,13 @@ export function getEventLink(event: NostrEvent): string {
return `/event/${event.id}`; return `/event/${event.id}`;
} }
} }
/**
* Navigate to an event page with the event preloaded
* Stores the event in sessionStorage so the event page can use it without re-fetching
*/
export function navigateToEvent(event: NostrEvent): void {
// Store event in sessionStorage so the event page can use it without re-fetching
sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(event));
goto(getEventLink(event));
}

10
src/lib/services/nostr/event-index-loader.ts

@ -279,7 +279,7 @@ async function loadEventIndexRecursive(
} }
// Process all a-tag results // Process all a-tag results
console.log(`[EventIndex] Processing ${aTags.length} a-tags, found ${aTagResults.size} events`); // console.log(`[EventIndex] Processing ${aTags.length} a-tags, found ${aTagResults.size} events`);
for (const aTagInfo of aTags) { for (const aTagInfo of aTags) {
const parts = aTagInfo.address.split(':'); const parts = aTagInfo.address.split(':');
if (parts.length === 3) { if (parts.length === 3) {
@ -291,13 +291,13 @@ async function loadEventIndexRecursive(
const event = aTagResults.get(aTagInfo.address); const event = aTagResults.get(aTagInfo.address);
if (event) { if (event) {
console.log(`[EventIndex] Processing event ${event.id}, kind ${event.kind}, level ${level}`); // console.log(`[EventIndex] Processing event ${event.id}, kind ${event.kind}, level ${level}`);
// Check if this event is also a kind 30040 (nested index) // Check if this event is also a kind 30040 (nested index)
if (event.kind === 30040) { if (event.kind === 30040) {
console.log(`[EventIndex] Found kind 30040 event ${event.id} at level ${level}, loading children...`); // console.log(`[EventIndex] Found kind 30040 event ${event.id} at level ${level}, loading children...`);
// Recursively load nested index // Recursively load nested index
const nestedResult = await loadEventIndexRecursive(event, level + 1, maxDepth); const nestedResult = await loadEventIndexRecursive(event, level + 1, maxDepth);
console.log(`[EventIndex] Loaded nested index ${event.id} with ${nestedResult.items.length} children at level ${level + 1}`, nestedResult.items.map(i => ({ id: i.event.id, kind: i.event.kind, level: i.level }))); // console.log(`[EventIndex] Loaded nested index ${event.id} with ${nestedResult.items.length} children at level ${level + 1}`, nestedResult.items.map(i => ({ id: i.event.id, kind: i.event.kind, level: i.level })));
// Create a parent item with children // Create a parent item with children
const parentItem: EventIndexItem = { const parentItem: EventIndexItem = {
event, event,
@ -525,7 +525,7 @@ async function loadEventIndexRecursive(
missingEvents.sort((a, b) => a.order - b.order); missingEvents.sort((a, b) => a.order - b.order);
const itemsWithChildren = items.filter(item => item.children && item.children.length > 0); const itemsWithChildren = items.filter(item => item.children && item.children.length > 0);
console.log(`[EventIndex] Returning ${items.length} items (${itemsWithChildren.length} with children) at level ${level}`); // console.log(`[EventIndex] Returning ${items.length} items (${itemsWithChildren.length} with children) at level ${level}`);
return { items, missingEvents }; return { items, missingEvents };
} }

4
src/routes/cache/+page.svelte vendored

@ -779,7 +779,7 @@
{JSON.stringify(event, null, 2)} {JSON.stringify(event, null, 2)}
</div> </div>
<div class="event-actions-bottom"> <div class="event-actions-bottom">
<a href="/event/{event.id}" class="action-button" target="_blank">View</a> <a href="/event/{event.id}" onclick={(e) => { e.preventDefault(); sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(event)); goto(`/event/${event.id}`); }} class="action-button">View</a>
<button class="action-button" onclick={() => copyEventJson(event)} title="Copy event JSON"> <button class="action-button" onclick={() => copyEventJson(event)} title="Copy event JSON">
Copy JSON Copy JSON
</button> </button>
@ -809,7 +809,7 @@
<div class="event-preview"> <div class="event-preview">
<p class="event-content-preview">{event.content.substring(0, 200)}{event.content.length > 200 ? '...' : ''}</p> <p class="event-content-preview">{event.content.substring(0, 200)}{event.content.length > 200 ? '...' : ''}</p>
<div class="event-actions-bottom"> <div class="event-actions-bottom">
<a href="/event/{event.id}" class="action-button" target="_blank">View</a> <a href="/event/{event.id}" onclick={(e) => { e.preventDefault(); sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(event)); goto(`/event/${event.id}`); }} class="action-button">View</a>
<button class="action-button" onclick={() => copyEventJson(event)} title="Copy event JSON"> <button class="action-button" onclick={() => copyEventJson(event)} title="Copy event JSON">
Copy JSON Copy JSON
</button> </button>

3
src/routes/discussions/+page.svelte

@ -10,6 +10,7 @@
import type { NostrEvent } from '../../lib/types/nostr.js'; import type { NostrEvent } from '../../lib/types/nostr.js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { goto } from '$app/navigation';
import Pagination from '../../lib/components/ui/Pagination.svelte'; import Pagination from '../../lib/components/ui/Pagination.svelte';
import { getPaginatedItems, getCurrentPage, ITEMS_PER_PAGE } from '../../lib/utils/pagination.js'; import { getPaginatedItems, getCurrentPage, ITEMS_PER_PAGE } from '../../lib/utils/pagination.js';
import Icon from '../../lib/components/ui/Icon.svelte'; import Icon from '../../lib/components/ui/Icon.svelte';
@ -126,7 +127,7 @@
<h3>Events ({searchResults.events.length})</h3> <h3>Events ({searchResults.events.length})</h3>
<div class="event-results"> <div class="event-results">
{#each paginatedSearchEvents as event} {#each paginatedSearchEvents as event}
<a href="/event/{event.id}" class="event-result-card"> <a href="/event/{event.id}" onclick={(e) => { e.preventDefault(); sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(event)); goto(`/event/${event.id}`); }} class="event-result-card">
<FeedPost post={event} fullView={false} /> <FeedPost post={event} fullView={false} />
</a> </a>
{/each} {/each}

114
src/routes/event/[id]/+page.svelte

@ -9,23 +9,26 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import { KIND } from '../../../lib/types/kind-lookup.js'; import { KIND } from '../../../lib/types/kind-lookup.js';
import type { NostrEvent } from '../../../lib/types/nostr.js';
let decodedEventId = $state<string | null>(null); let decodedEventId = $state<string | null>(null);
let eventKind = $state<number | null>(null); let eventKind = $state<number | null>(null);
let preloadedEvent = $state<NostrEvent | null>(null); // Store event if already loaded (e.g., from naddr)
let loading = $state(false); let loading = $state(false);
let error = $state<string | null>(null); let error = $state<string | null>(null);
let lastProcessedParam = $state<string | null>(null); // Track last processed param to prevent loops let lastProcessedParam = $state<string | null>(null); // Track last processed param to prevent loops
/** /**
* Decode route parameter to event hex ID * Decode route parameter to event hex ID and optionally return the event
* Supports: hex event id, note, nevent, naddr * Supports: hex event id, note, nevent, naddr
* Returns: { eventId: string, event?: NostrEvent } or null
*/ */
async function decodeEventId(param: string): Promise<string | null> { async function decodeEventId(param: string): Promise<{ eventId: string; event?: NostrEvent } | null> {
if (!param) return null; if (!param) return null;
// Check if it's already a hex event ID (64 hex characters) // Check if it's already a hex event ID (64 hex characters)
if (/^[0-9a-f]{64}$/i.test(param)) { if (/^[0-9a-f]{64}$/i.test(param)) {
return param.toLowerCase(); return { eventId: param.toLowerCase() };
} }
// Check if it's a bech32 encoded format (note, nevent, naddr) // Check if it's a bech32 encoded format (note, nevent, naddr)
@ -33,11 +36,11 @@
try { try {
const decoded = nip19.decode(param); const decoded = nip19.decode(param);
if (decoded.type === 'note') { if (decoded.type === 'note') {
return String(decoded.data); return { eventId: String(decoded.data) };
} else if (decoded.type === 'nevent') { } else if (decoded.type === 'nevent') {
// nevent contains event id and optional relays // nevent contains event id and optional relays
if (decoded.data && typeof decoded.data === 'object' && 'id' in decoded.data) { if (decoded.data && typeof decoded.data === 'object' && 'id' in decoded.data) {
return String(decoded.data.id); return { eventId: String(decoded.data.id) };
} }
} else if (decoded.type === 'naddr') { } else if (decoded.type === 'naddr') {
// naddr is for parameterized replaceable events (kind + pubkey + d tag) // naddr is for parameterized replaceable events (kind + pubkey + d tag)
@ -70,11 +73,12 @@
const events = await nostrClient.fetchEvents( const events = await nostrClient.fetchEvents(
filters, filters,
relays, relays,
{ useCache: true, cacheResults: true } { useCache: 'cache-first', cacheResults: true, timeout: 10000 }
); );
if (events.length > 0) { if (events.length > 0) {
return events[0].id; // Return both eventId and the event itself to avoid re-fetching
return { eventId: events[0].id, event: events[0] };
} else { } else {
// Event not found for naddr // Event not found for naddr
return null; return null;
@ -103,20 +107,49 @@
error = null; error = null;
decodedEventId = null; decodedEventId = null;
eventKind = null; eventKind = null;
preloadedEvent = null;
lastProcessedParam = currentParam; // Track that we're processing this param lastProcessedParam = currentParam; // Track that we're processing this param
try { try {
const eventId = await decodeEventId(currentParam); // Check sessionStorage for a preloaded event (from EventMenu, eye icon button, or any click)
if (eventId) { let sessionEvent: NostrEvent | null = null;
decodedEventId = eventId; if (typeof window !== 'undefined') {
const preloadedEventStr = sessionStorage.getItem('aitherboard_preloadedEvent');
// Fetch the event to determine its kind for routing if (preloadedEventStr) {
const threadRelays = relayManager.getThreadReadRelays(); try {
const feedRelays = relayManager.getFeedReadRelays(); sessionEvent = JSON.parse(preloadedEventStr) as NostrEvent;
const allRelays = [...new Set([...threadRelays, ...feedRelays])]; // Clear it after reading (we'll verify it matches first)
const event = await nostrClient.getEventById(eventId, allRelays); sessionStorage.removeItem('aitherboard_preloadedEvent');
if (event) { } catch (parseError) {
eventKind = event.kind; // Invalid JSON in sessionStorage, continue with normal loading
console.warn('Failed to parse preloaded event from sessionStorage:', parseError);
sessionStorage.removeItem('aitherboard_preloadedEvent');
}
}
}
const result = await decodeEventId(currentParam);
if (result) {
decodedEventId = result.eventId;
// If we already have the event from naddr decoding, use it (highest priority)
if (result.event) {
preloadedEvent = result.event;
eventKind = result.event.kind;
} else if (sessionEvent && sessionEvent.id === result.eventId) {
// Use session event if it matches the decoded event ID
preloadedEvent = sessionEvent;
eventKind = sessionEvent.kind;
} else {
// 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(result.eventId, allRelays);
if (event) {
preloadedEvent = event;
eventKind = event.kind;
}
} }
} else { } else {
error = 'Event not found or invalid format'; error = 'Event not found or invalid format';
@ -146,24 +179,33 @@
<Header /> <Header />
<main class="container mx-auto px-4 py-8"> <main class="container mx-auto px-4 py-8">
{#if loading} <div class="event-content-wrapper">
<PageHeader title="Loading event..." onRefresh={loadEvent} refreshLoading={loading} /> {#if loading}
<p class="text-fog-text dark:text-fog-dark-text">Loading event...</p> <PageHeader title="Loading event..." onRefresh={loadEvent} refreshLoading={loading} />
{:else if error} <p class="text-fog-text dark:text-fog-dark-text">Loading event...</p>
<PageHeader title="Error" onRefresh={loadEvent} refreshLoading={loading} /> {:else if error}
<p class="text-fog-text dark:text-fog-dark-text">{error}</p> <PageHeader title="Error" onRefresh={loadEvent} refreshLoading={loading} />
{:else if decodedEventId} <p class="text-fog-text dark:text-fog-dark-text">{error}</p>
<PageHeader title="Event" onRefresh={loadEvent} refreshLoading={loading} /> {:else if decodedEventId}
{#if eventKind === KIND.DISCUSSION_THREAD} <PageHeader title="Event" onRefresh={loadEvent} refreshLoading={loading} />
<!-- Route kind 11 (discussion threads) to DiscussionView --> {#if eventKind === KIND.DISCUSSION_THREAD}
<DiscussionView threadId={decodedEventId} /> <!-- 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} preloadedEvent={preloadedEvent} />
{/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} {:else}
<!-- Route all other events (including kind 30040, metadata-only, etc.) to EventView --> <p class="text-fog-text dark:text-fog-dark-text">Event ID required</p>
<EventView eventId={decodedEventId} />
{/if} {/if}
{:else if $page.params.id} </div>
<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> </main>
<style>
.event-content-wrapper {
max-width: var(--content-width);
margin: 0 auto;
}
</style>

6
src/routes/find/+page.svelte

@ -12,7 +12,7 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { untrack } from 'svelte'; import { untrack } from 'svelte';
import { afterNavigate } from '$app/navigation'; import { afterNavigate, goto } from '$app/navigation';
import Pagination from '../../lib/components/ui/Pagination.svelte'; import Pagination from '../../lib/components/ui/Pagination.svelte';
import { getPaginatedItems, getCurrentPage, ITEMS_PER_PAGE } from '../../lib/utils/pagination.js'; import { getPaginatedItems, getCurrentPage, ITEMS_PER_PAGE } from '../../lib/utils/pagination.js';
@ -156,7 +156,7 @@
<div class="event-results"> <div class="event-results">
{#each paginatedCacheEvents as event} {#each paginatedCacheEvents as event}
<div class="event-result-card"> <div class="event-result-card">
<a href="/event/{event.id}" class="event-result-link"> <a href="/event/{event.id}" onclick={(e) => { e.preventDefault(); sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(event)); goto(`/event/${event.id}`); }} class="event-result-link">
<FeedPost post={event} fullView={false} /> <FeedPost post={event} fullView={false} />
</a> </a>
<div class="event-relay-badge"> <div class="event-relay-badge">
@ -194,7 +194,7 @@
<div class="event-results"> <div class="event-results">
{#each paginatedSearchEvents as event} {#each paginatedSearchEvents as event}
<div class="event-result-card"> <div class="event-result-card">
<a href="/event/{event.id}" class="event-result-link"> <a href="/event/{event.id}" onclick={(e) => { e.preventDefault(); sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(event)); goto(`/event/${event.id}`); }} class="event-result-link">
<FeedPost post={event} fullView={false} /> <FeedPost post={event} fullView={false} />
</a> </a>
{#if eventRelayMap.has(event.id)} {#if eventRelayMap.has(event.id)}

4
src/routes/highlights/+page.svelte

@ -310,7 +310,7 @@
{#each searchResults.events as event} {#each searchResults.events as event}
{#if event.kind === KIND.HIGHLIGHTED_ARTICLE} {#if event.kind === KIND.HIGHLIGHTED_ARTICLE}
<div class="event-result-card"> <div class="event-result-card">
<HighlightCard highlight={event} onOpenEvent={(e) => goto(`/event/${e.id}`)} /> <HighlightCard highlight={event} onOpenEvent={(e) => { sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(e)); goto(`/event/${e.id}`); }} />
</div> </div>
{/if} {/if}
{/each} {/each}
@ -337,7 +337,7 @@
<div class="highlights-posts"> <div class="highlights-posts">
{#each paginatedItems as item (item.event.id)} {#each paginatedItems as item (item.event.id)}
<div class="highlight-item-wrapper"> <div class="highlight-item-wrapper">
<HighlightCard highlight={item.event} onOpenEvent={(event) => goto(`/event/${event.id}`)} /> <HighlightCard highlight={item.event} onOpenEvent={(event) => { sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(event)); goto(`/event/${event.id}`); }} />
</div> </div>
{/each} {/each}
</div> </div>

13
src/routes/profile/[pubkey]/+page.svelte

@ -23,6 +23,15 @@
<Header /> <Header />
<main class="container mx-auto px-2 sm:px-4 py-4 sm:py-8"> <main class="container mx-auto px-2 sm:px-4 py-4 sm:py-8">
<PageHeader title="Profile" onRefresh={handleRefresh} /> <div class="profile-content">
<ProfilePage bind:this={profilePageComponent} /> <PageHeader title="Profile" onRefresh={handleRefresh} />
<ProfilePage bind:this={profilePageComponent} />
</div>
</main> </main>
<style>
.profile-content {
max-width: var(--content-width);
margin: 0 auto;
}
</style>

2
src/routes/repos/+page.svelte

@ -563,7 +563,7 @@
<FeedPost post={event} fullView={false} /> <FeedPost post={event} fullView={false} />
</a> </a>
{:else} {:else}
<a href="/event/{event.id}" class="event-result-card"> <a href="/event/{event.id}" onclick={(e) => { e.preventDefault(); sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(event)); goto(`/event/${event.id}`); }} class="event-result-card">
<FeedPost post={event} fullView={false} /> <FeedPost post={event} fullView={false} />
</a> </a>
{/if} {/if}

2
src/routes/repos/[naddr]/+page.svelte

@ -1468,7 +1468,7 @@
<div class="doc-header"> <div class="doc-header">
<div class="doc-meta"> <div class="doc-meta">
<span class="doc-kind">Kind {docEvent.kind}</span> <span class="doc-kind">Kind {docEvent.kind}</span>
<a href="/event/{docEvent.id}" class="doc-event-link">View Event</a> <a href="/event/{docEvent.id}" onclick={(e) => { e.preventDefault(); sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(docEvent)); goto(`/event/${docEvent.id}`); }} class="doc-event-link">View Event</a>
<EventMenu event={docEvent} showContentActions={true} /> <EventMenu event={docEvent} showContentActions={true} />
</div> </div>
</div> </div>

2
src/routes/topics/+page.svelte

@ -282,7 +282,7 @@
<h3>Events ({searchResults.events.length})</h3> <h3>Events ({searchResults.events.length})</h3>
<div class="event-results"> <div class="event-results">
{#each paginatedSearchEvents as event} {#each paginatedSearchEvents as event}
<a href="/event/{event.id}" class="event-result-card"> <a href="/event/{event.id}" onclick={(e) => { e.preventDefault(); sessionStorage.setItem('aitherboard_preloadedEvent', JSON.stringify(event)); goto(`/event/${event.id}`); }} class="event-result-card">
<FeedPost post={event} fullView={false} /> <FeedPost post={event} fullView={false} />
</a> </a>
{/each} {/each}

Loading…
Cancel
Save