|
|
|
|
@ -3,11 +3,9 @@
@@ -3,11 +3,9 @@
|
|
|
|
|
import MarkdownRenderer from '../../components/content/MarkdownRenderer.svelte'; |
|
|
|
|
import PaymentAddresses from './PaymentAddresses.svelte'; |
|
|
|
|
import FeedPost from '../feed/FeedPost.svelte'; |
|
|
|
|
import ThreadDrawer from '../feed/ThreadDrawer.svelte'; |
|
|
|
|
import ProfileEventsPanel from '../../components/profile/ProfileEventsPanel.svelte'; |
|
|
|
|
import ProfileMenu from '../../components/profile/ProfileMenu.svelte'; |
|
|
|
|
import BookmarksPanel from '../../components/profile/BookmarksPanel.svelte'; |
|
|
|
|
import { getPinnedEvents } from '../../services/user-actions.js'; |
|
|
|
|
import { fetchProfile, fetchUserStatus, type ProfileData } from '../../services/user-data.js'; |
|
|
|
|
import { nostrClient } from '../../services/nostr/nostr-client.js'; |
|
|
|
|
import { relayManager } from '../../services/nostr/relay-manager.js'; |
|
|
|
|
@ -17,27 +15,15 @@
@@ -17,27 +15,15 @@
|
|
|
|
|
import { page } from '$app/stores'; |
|
|
|
|
import { nip19 } from 'nostr-tools'; |
|
|
|
|
import type { NostrEvent } from '../../types/nostr.js'; |
|
|
|
|
import { KIND, getFeedKinds } from '../../types/kind-lookup.js'; |
|
|
|
|
import { KIND } from '../../types/kind-lookup.js'; |
|
|
|
|
|
|
|
|
|
// Progressive rendering for profile posts |
|
|
|
|
const INITIAL_RENDER_LIMIT = 25; |
|
|
|
|
const RENDER_INCREMENT = 25; |
|
|
|
|
let visiblePostsCount = $state(INITIAL_RENDER_LIMIT); |
|
|
|
|
let visibleResponsesCount = $state(INITIAL_RENDER_LIMIT); |
|
|
|
|
let visibleInteractionsCount = $state(INITIAL_RENDER_LIMIT); |
|
|
|
|
|
|
|
|
|
// Derived: visible items for each tab |
|
|
|
|
const visiblePosts = $derived.by(() => posts.slice(0, visiblePostsCount)); |
|
|
|
|
const visibleResponses = $derived.by(() => responses.slice(0, visibleResponsesCount)); |
|
|
|
|
const visibleInteractions = $derived.by(() => interactionsWithMe.slice(0, visibleInteractionsCount)); |
|
|
|
|
|
|
|
|
|
let profile = $state<ProfileData | null>(null); |
|
|
|
|
let userStatus = $state<string | null>(null); |
|
|
|
|
let posts = $state<NostrEvent[]>([]); |
|
|
|
|
let responses = $state<NostrEvent[]>([]); |
|
|
|
|
let notifications = $state<NostrEvent[]>([]); |
|
|
|
|
let interactionsWithMe = $state<NostrEvent[]>([]); |
|
|
|
|
let loading = $state(true); |
|
|
|
|
let activeTab = $state<'posts' | 'responses' | 'interactions' | 'pins'>('posts'); |
|
|
|
|
let activeTab = $state<'pins' | 'notifications' | 'interactions'>('pins'); |
|
|
|
|
let nip05Validations = $state<Record<string, boolean | null>>({}); // null = checking, true = valid, false = invalid |
|
|
|
|
// Compute pubkey from route params |
|
|
|
|
let profilePubkey = $derived.by(() => decodePubkey($page.params.pubkey)); |
|
|
|
|
@ -49,10 +35,6 @@
@@ -49,10 +35,6 @@
|
|
|
|
|
// Get current logged-in user's pubkey |
|
|
|
|
let currentUserPubkey = $state<string | null>(sessionManager.getCurrentPubkey()); |
|
|
|
|
|
|
|
|
|
// Drawer state for viewing threads |
|
|
|
|
let drawerOpen = $state(false); |
|
|
|
|
let drawerEvent = $state<NostrEvent | null>(null); |
|
|
|
|
|
|
|
|
|
// Profile events panel state |
|
|
|
|
let profileEventsPanelOpen = $state(false); |
|
|
|
|
|
|
|
|
|
@ -82,16 +64,6 @@
@@ -82,16 +64,6 @@
|
|
|
|
|
}; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
function openDrawer(event: NostrEvent) { |
|
|
|
|
drawerEvent = event; |
|
|
|
|
drawerOpen = true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function closeDrawer() { |
|
|
|
|
drawerOpen = false; |
|
|
|
|
drawerEvent = null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function openProfileEventsPanel() { |
|
|
|
|
profileEventsPanelOpen = true; |
|
|
|
|
} |
|
|
|
|
@ -117,14 +89,17 @@
@@ -117,14 +89,17 @@
|
|
|
|
|
$effect(() => { |
|
|
|
|
const unsubscribe = sessionManager.session.subscribe((session) => { |
|
|
|
|
currentUserPubkey = session?.pubkey || null; |
|
|
|
|
// Reload interactions if session changes and we're viewing another user's profile |
|
|
|
|
// Reload notifications/interactions if session changes |
|
|
|
|
if (profile) { |
|
|
|
|
const pubkey = decodePubkey($page.params.pubkey); |
|
|
|
|
if (pubkey && currentUserPubkey && currentUserPubkey !== pubkey) { |
|
|
|
|
// Reload interactions tab data |
|
|
|
|
loadInteractionsWithMe(pubkey, currentUserPubkey); |
|
|
|
|
} else { |
|
|
|
|
interactionsWithMe = []; |
|
|
|
|
if (pubkey && currentUserPubkey) { |
|
|
|
|
if (currentUserPubkey === pubkey) { |
|
|
|
|
// Reload notifications for own profile |
|
|
|
|
loadNotifications(pubkey); |
|
|
|
|
} else { |
|
|
|
|
// Reload interactions for other user's profile |
|
|
|
|
loadInteractionsWithMe(pubkey, currentUserPubkey); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
@ -134,13 +109,33 @@
@@ -134,13 +109,33 @@
|
|
|
|
|
async function loadPins(pubkey: string) { |
|
|
|
|
if (!isMounted) return; |
|
|
|
|
try { |
|
|
|
|
const pinnedIds = await getPinnedEvents(); |
|
|
|
|
if (!isMounted || pinnedIds.size === 0) { |
|
|
|
|
// Fetch the user's pin list (kind 10001) |
|
|
|
|
const profileRelays = relayManager.getProfileReadRelays(); |
|
|
|
|
const pinLists = await nostrClient.fetchEvents( |
|
|
|
|
[{ kinds: [KIND.PIN_LIST], authors: [pubkey], limit: 1 }], |
|
|
|
|
profileRelays, |
|
|
|
|
{ useCache: true, cacheResults: true, timeout: config.mediumTimeout } |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
if (!isMounted || pinLists.length === 0) { |
|
|
|
|
if (isMounted) pins = []; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const profileRelays = relayManager.getProfileReadRelays(); |
|
|
|
|
// Extract event IDs from pin list |
|
|
|
|
const pinnedIds = new Set<string>(); |
|
|
|
|
for (const tag of pinLists[0].tags) { |
|
|
|
|
if (tag[0] === 'e' && tag[1]) { |
|
|
|
|
pinnedIds.add(tag[1]); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (pinnedIds.size === 0) { |
|
|
|
|
if (isMounted) pins = []; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Fetch the actual pinned events |
|
|
|
|
const fetchPromise = nostrClient.fetchEvents( |
|
|
|
|
[{ ids: Array.from(pinnedIds), limit: config.feedLimit }], |
|
|
|
|
profileRelays, |
|
|
|
|
@ -160,6 +155,53 @@
@@ -160,6 +155,53 @@
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async function loadNotifications(pubkey: string) { |
|
|
|
|
if (!isMounted) return; |
|
|
|
|
try { |
|
|
|
|
const notificationRelays = relayManager.getFeedReadRelays(); |
|
|
|
|
|
|
|
|
|
// Fetch user's posts to find replies |
|
|
|
|
const userPosts = await nostrClient.fetchEvents( |
|
|
|
|
[{ kinds: [KIND.SHORT_TEXT_NOTE], authors: [pubkey], limit: 100 }], |
|
|
|
|
notificationRelays, |
|
|
|
|
{ useCache: true, cacheResults: true, timeout: config.mediumTimeout } |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
if (!isMounted) return; |
|
|
|
|
|
|
|
|
|
const userPostIds = new Set(userPosts.map(p => p.id)); |
|
|
|
|
|
|
|
|
|
// Fetch notifications: replies, mentions, reactions, zaps |
|
|
|
|
const notificationEvents = await nostrClient.fetchEvents( |
|
|
|
|
[ |
|
|
|
|
{ kinds: [KIND.SHORT_TEXT_NOTE], '#e': Array.from(userPostIds).slice(0, 50), limit: 100 }, // Replies to user's posts |
|
|
|
|
{ kinds: [KIND.SHORT_TEXT_NOTE], '#p': [pubkey], limit: 100 }, // Mentions |
|
|
|
|
{ kinds: [KIND.REACTION], '#p': [pubkey], limit: 100 }, // Reactions |
|
|
|
|
{ kinds: [KIND.ZAP_RECEIPT], '#p': [pubkey], limit: 100 } // Zaps |
|
|
|
|
], |
|
|
|
|
notificationRelays, |
|
|
|
|
{ useCache: true, cacheResults: true, timeout: config.mediumTimeout } |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
if (!isMounted) return; |
|
|
|
|
|
|
|
|
|
// Deduplicate and sort by created_at descending |
|
|
|
|
const seenIds = new Set<string>(); |
|
|
|
|
notifications = notificationEvents |
|
|
|
|
.filter(e => { |
|
|
|
|
if (seenIds.has(e.id)) return false; |
|
|
|
|
seenIds.add(e.id); |
|
|
|
|
// Exclude user's own events |
|
|
|
|
return e.pubkey !== pubkey; |
|
|
|
|
}) |
|
|
|
|
.sort((a, b) => b.created_at - a.created_at) |
|
|
|
|
.slice(0, 100); // Limit to 100 most recent |
|
|
|
|
} catch (error) { |
|
|
|
|
console.error('Error loading notifications:', error); |
|
|
|
|
notifications = []; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async function loadInteractionsWithMe(profilePubkey: string, currentUserPubkey: string) { |
|
|
|
|
if (!isMounted || !currentUserPubkey || currentUserPubkey === profilePubkey) { |
|
|
|
|
if (isMounted) interactionsWithMe = []; |
|
|
|
|
@ -230,6 +272,7 @@
@@ -230,6 +272,7 @@
|
|
|
|
|
interactionsWithMe = []; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onMount(async () => { |
|
|
|
|
await nostrClient.initialize(); |
|
|
|
|
@ -480,73 +523,31 @@
@@ -480,73 +523,31 @@
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Step 2: Load posts and responses in parallel (non-blocking, update when ready) |
|
|
|
|
const profileRelays = relayManager.getProfileReadRelays(); |
|
|
|
|
const responseRelays = relayManager.getFeedResponseReadRelays(); |
|
|
|
|
|
|
|
|
|
// Check again before loading posts |
|
|
|
|
if (abortController.signal.aborted || currentLoadPubkey !== pubkey) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Load posts first (needed for response filtering) |
|
|
|
|
// Fetch all feed kinds (not just kind 1) |
|
|
|
|
const feedKinds = getFeedKinds(); |
|
|
|
|
const feedEvents = await nostrClient.fetchEvents( |
|
|
|
|
[{ kinds: feedKinds, authors: [pubkey], limit: config.feedLimit }], |
|
|
|
|
profileRelays, |
|
|
|
|
{ useCache: true, cacheResults: true, timeout: config.mediumTimeout } |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// Check again after posts load |
|
|
|
|
if (abortController.signal.aborted || currentLoadPubkey !== pubkey) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
posts = feedEvents.sort((a, b) => b.created_at - a.created_at); |
|
|
|
|
// Step 2: Load pins for the profile being viewed |
|
|
|
|
await loadPins(pubkey); |
|
|
|
|
|
|
|
|
|
// Reset visible counts when new data loads |
|
|
|
|
visiblePostsCount = INITIAL_RENDER_LIMIT; |
|
|
|
|
visibleResponsesCount = INITIAL_RENDER_LIMIT; |
|
|
|
|
visibleInteractionsCount = INITIAL_RENDER_LIMIT; |
|
|
|
|
|
|
|
|
|
// Load responses in parallel with posts (but filter after posts are loaded) |
|
|
|
|
const userPostIds = new Set(posts.map(p => p.id)); |
|
|
|
|
const responseEvents = await nostrClient.fetchEvents( |
|
|
|
|
[{ kinds: [KIND.SHORT_TEXT_NOTE], '#p': [pubkey], limit: config.feedLimit }], // Fetch more to account for filtering |
|
|
|
|
responseRelays, |
|
|
|
|
{ useCache: true, cacheResults: true, timeout: config.mediumTimeout } |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// Check again after responses load |
|
|
|
|
if (abortController.signal.aborted || currentLoadPubkey !== pubkey) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Filter responses (exclude self-replies, only include replies to user's posts) |
|
|
|
|
responses = responseEvents |
|
|
|
|
.filter(e => { |
|
|
|
|
if (e.pubkey === pubkey) return false; // Exclude self-replies |
|
|
|
|
const eTag = e.tags.find(t => t[0] === 'e'); |
|
|
|
|
return eTag && userPostIds.has(eTag[1]); |
|
|
|
|
}) |
|
|
|
|
.sort((a, b) => b.created_at - a.created_at) |
|
|
|
|
.slice(0, 20); // Limit to 20 after filtering |
|
|
|
|
|
|
|
|
|
// Step 3: Load interactions in background (non-blocking) |
|
|
|
|
if (currentUserPubkey && currentUserPubkey !== pubkey) { |
|
|
|
|
loadInteractionsWithMe(pubkey, currentUserPubkey).catch(err => { |
|
|
|
|
console.debug('Error loading interactions:', err); |
|
|
|
|
}); |
|
|
|
|
} else { |
|
|
|
|
interactionsWithMe = []; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Step 4: Load pins if viewing own profile |
|
|
|
|
// Step 3: Load notifications or interactions |
|
|
|
|
if (isOwnProfile) { |
|
|
|
|
loadPins(pubkey); |
|
|
|
|
await loadNotifications(pubkey); |
|
|
|
|
interactionsWithMe = []; |
|
|
|
|
// Set default tab: if no pins, default to notifications |
|
|
|
|
if (pins.length === 0 && notifications.length > 0) { |
|
|
|
|
activeTab = 'notifications'; |
|
|
|
|
} else if (pins.length > 0) { |
|
|
|
|
activeTab = 'pins'; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
pins = []; |
|
|
|
|
notifications = []; |
|
|
|
|
// Load interactions if logged in and viewing another user's profile |
|
|
|
|
if (currentUserPubkey) { |
|
|
|
|
await loadInteractionsWithMe(pubkey, currentUserPubkey); |
|
|
|
|
} else { |
|
|
|
|
interactionsWithMe = []; |
|
|
|
|
} |
|
|
|
|
// Set default tab to pins if available |
|
|
|
|
if (pins.length > 0) { |
|
|
|
|
activeTab = 'pins'; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} catch (error) { |
|
|
|
|
// Only update state if this load wasn't aborted |
|
|
|
|
@ -581,7 +582,7 @@
@@ -581,7 +582,7 @@
|
|
|
|
|
{#if profile.about} |
|
|
|
|
<p class="text-fog-text dark:text-fog-dark-text mb-2">{profile.about}</p> |
|
|
|
|
{/if} |
|
|
|
|
{#if userStatus} |
|
|
|
|
{#if userStatus && userStatus.trim()} |
|
|
|
|
<p class="text-fog-text-light dark:text-fog-dark-text-light italic mb-2" style="font-size: 0.875em;"> |
|
|
|
|
{userStatus} |
|
|
|
|
</p> |
|
|
|
|
@ -656,77 +657,46 @@
@@ -656,77 +657,46 @@
|
|
|
|
|
<div class="profile-posts"> |
|
|
|
|
<div class="tabs mb-4 flex gap-4 border-b border-fog-border dark:border-fog-dark-border"> |
|
|
|
|
<button |
|
|
|
|
onclick={() => activeTab = 'posts'} |
|
|
|
|
class="px-4 py-2 font-semibold {activeTab === 'posts' ? 'border-b-2 border-fog-accent dark:border-fog-dark-accent' : ''}" |
|
|
|
|
onclick={() => activeTab = 'pins'} |
|
|
|
|
class="px-4 py-2 font-semibold {activeTab === 'pins' ? 'border-b-2 border-fog-accent dark:border-fog-dark-accent' : ''}" |
|
|
|
|
> |
|
|
|
|
Posts ({posts.length}) |
|
|
|
|
Pins ({pins.length}) |
|
|
|
|
</button> |
|
|
|
|
<button |
|
|
|
|
onclick={() => activeTab = 'responses'} |
|
|
|
|
class="px-4 py-2 font-semibold {activeTab === 'responses' ? 'border-b-2 border-fog-accent dark:border-fog-dark-accent' : ''}" |
|
|
|
|
> |
|
|
|
|
Responses ({responses.length}) |
|
|
|
|
</button> |
|
|
|
|
{#if currentUserPubkey && currentUserPubkey !== decodePubkey($page.params.pubkey)} |
|
|
|
|
{#if isOwnProfile} |
|
|
|
|
<button |
|
|
|
|
onclick={() => activeTab = 'interactions'} |
|
|
|
|
class="px-4 py-2 font-semibold {activeTab === 'interactions' ? 'border-b-2 border-fog-accent dark:border-fog-dark-accent' : ''}" |
|
|
|
|
onclick={() => activeTab = 'notifications'} |
|
|
|
|
class="px-4 py-2 font-semibold {activeTab === 'notifications' ? 'border-b-2 border-fog-accent dark:border-fog-dark-accent' : ''}" |
|
|
|
|
> |
|
|
|
|
Interactions with me ({interactionsWithMe.length}) |
|
|
|
|
Notifications ({notifications.length}) |
|
|
|
|
</button> |
|
|
|
|
{/if} |
|
|
|
|
{#if isOwnProfile} |
|
|
|
|
{:else if currentUserPubkey && currentUserPubkey !== profilePubkey} |
|
|
|
|
<button |
|
|
|
|
onclick={() => activeTab = 'pins'} |
|
|
|
|
class="px-4 py-2 font-semibold {activeTab === 'pins' ? 'border-b-2 border-fog-accent dark:border-fog-dark-accent' : ''}" |
|
|
|
|
onclick={() => activeTab = 'interactions'} |
|
|
|
|
class="px-4 py-2 font-semibold {activeTab === 'interactions' ? 'border-b-2 border-fog-accent dark:border-fog-dark-accent' : ''}" |
|
|
|
|
> |
|
|
|
|
Pins ({pins.length}) |
|
|
|
|
Interactions with me ({interactionsWithMe.length}) |
|
|
|
|
</button> |
|
|
|
|
{/if} |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
{#if activeTab === 'posts'} |
|
|
|
|
{#if posts.length === 0} |
|
|
|
|
<p class="text-fog-text-light dark:text-fog-dark-text-light">No posts yet.</p> |
|
|
|
|
{#if activeTab === 'pins'} |
|
|
|
|
{#if pins.length === 0} |
|
|
|
|
<p class="text-fog-text-light dark:text-fog-dark-text-light">No pinned posts yet.</p> |
|
|
|
|
{:else} |
|
|
|
|
<div class="posts-list"> |
|
|
|
|
{#each visiblePosts as post (post.id)} |
|
|
|
|
<FeedPost post={post} onOpenEvent={openDrawer} /> |
|
|
|
|
<div class="pins-list"> |
|
|
|
|
{#each pins as pin (pin.id)} |
|
|
|
|
<FeedPost post={pin} /> |
|
|
|
|
{/each} |
|
|
|
|
{#if posts.length > visiblePostsCount} |
|
|
|
|
<div class="load-more-visible text-center py-4"> |
|
|
|
|
<button |
|
|
|
|
class="px-4 py-2 bg-fog-accent dark:bg-fog-dark-accent text-white rounded hover:opacity-80" |
|
|
|
|
onclick={() => { |
|
|
|
|
visiblePostsCount = Math.min(visiblePostsCount + RENDER_INCREMENT, posts.length); |
|
|
|
|
}} |
|
|
|
|
> |
|
|
|
|
Load {Math.min(RENDER_INCREMENT, posts.length - visiblePostsCount)} more posts |
|
|
|
|
</button> |
|
|
|
|
</div> |
|
|
|
|
{/if} |
|
|
|
|
</div> |
|
|
|
|
{/if} |
|
|
|
|
{:else if activeTab === 'responses'} |
|
|
|
|
{#if responses.length === 0} |
|
|
|
|
<p class="text-fog-text-light dark:text-fog-dark-text-light">No responses yet.</p> |
|
|
|
|
{:else if activeTab === 'notifications'} |
|
|
|
|
{#if notifications.length === 0} |
|
|
|
|
<p class="text-fog-text-light dark:text-fog-dark-text-light">No notifications yet.</p> |
|
|
|
|
{:else} |
|
|
|
|
<div class="responses-list"> |
|
|
|
|
{#each visibleResponses as response (response.id)} |
|
|
|
|
<FeedPost post={response} onOpenEvent={openDrawer} /> |
|
|
|
|
<div class="notifications-list"> |
|
|
|
|
{#each notifications as notification (notification.id)} |
|
|
|
|
<FeedPost post={notification} /> |
|
|
|
|
{/each} |
|
|
|
|
{#if responses.length > visibleResponsesCount} |
|
|
|
|
<div class="load-more-visible text-center py-4"> |
|
|
|
|
<button |
|
|
|
|
class="px-4 py-2 bg-fog-accent dark:bg-fog-dark-accent text-white rounded hover:opacity-80" |
|
|
|
|
onclick={() => { |
|
|
|
|
visibleResponsesCount = Math.min(visibleResponsesCount + RENDER_INCREMENT, responses.length); |
|
|
|
|
}} |
|
|
|
|
> |
|
|
|
|
Load {Math.min(RENDER_INCREMENT, responses.length - visibleResponsesCount)} more responses |
|
|
|
|
</button> |
|
|
|
|
</div> |
|
|
|
|
{/if} |
|
|
|
|
</div> |
|
|
|
|
{/if} |
|
|
|
|
{:else if activeTab === 'interactions'} |
|
|
|
|
@ -734,30 +704,8 @@
@@ -734,30 +704,8 @@
|
|
|
|
|
<p class="text-fog-text-light dark:text-fog-dark-text-light">No interactions with you yet.</p> |
|
|
|
|
{:else} |
|
|
|
|
<div class="interactions-list"> |
|
|
|
|
{#each visibleInteractions as interaction (interaction.id)} |
|
|
|
|
<FeedPost post={interaction} onOpenEvent={openDrawer} /> |
|
|
|
|
{/each} |
|
|
|
|
{#if interactionsWithMe.length > visibleInteractionsCount} |
|
|
|
|
<div class="load-more-visible text-center py-4"> |
|
|
|
|
<button |
|
|
|
|
class="px-4 py-2 bg-fog-accent dark:bg-fog-dark-accent text-white rounded hover:opacity-80" |
|
|
|
|
onclick={() => { |
|
|
|
|
visibleInteractionsCount = Math.min(visibleInteractionsCount + RENDER_INCREMENT, interactionsWithMe.length); |
|
|
|
|
}} |
|
|
|
|
> |
|
|
|
|
Load {Math.min(RENDER_INCREMENT, interactionsWithMe.length - visibleInteractionsCount)} more interactions |
|
|
|
|
</button> |
|
|
|
|
</div> |
|
|
|
|
{/if} |
|
|
|
|
</div> |
|
|
|
|
{/if} |
|
|
|
|
{:else if activeTab === 'pins'} |
|
|
|
|
{#if pins.length === 0} |
|
|
|
|
<p class="text-fog-text-light dark:text-fog-dark-text-light">No pinned posts yet.</p> |
|
|
|
|
{:else} |
|
|
|
|
<div class="pins-list"> |
|
|
|
|
{#each pins as pin (pin.id)} |
|
|
|
|
<FeedPost post={pin} onOpenEvent={openDrawer} /> |
|
|
|
|
{#each interactionsWithMe as interaction (interaction.id)} |
|
|
|
|
<FeedPost post={interaction} /> |
|
|
|
|
{/each} |
|
|
|
|
</div> |
|
|
|
|
{/if} |
|
|
|
|
@ -767,8 +715,6 @@
@@ -767,8 +715,6 @@
|
|
|
|
|
<p class="text-fog-text-light dark:text-fog-dark-text-light">Profile not found</p> |
|
|
|
|
{/if} |
|
|
|
|
|
|
|
|
|
<ThreadDrawer opEvent={drawerEvent} isOpen={drawerOpen} onClose={closeDrawer} /> |
|
|
|
|
|
|
|
|
|
{#if isOwnProfile} |
|
|
|
|
<ProfileEventsPanel |
|
|
|
|
isOpen={profileEventsPanelOpen} |
|
|
|
|
|