diff --git a/src/components/NoteOptions/useMenuActions.tsx b/src/components/NoteOptions/useMenuActions.tsx index 15799d6..b2abd83 100644 --- a/src/components/NoteOptions/useMenuActions.tsx +++ b/src/components/NoteOptions/useMenuActions.tsx @@ -7,6 +7,7 @@ import { useCurrentRelays } from '@/providers/CurrentRelaysProvider' import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' import { useMuteList } from '@/providers/MuteListProvider' import { useNostr } from '@/providers/NostrProvider' +import { BIG_RELAY_URLS, FAST_READ_RELAY_URLS, FAST_WRITE_RELAY_URLS } from '@/constants' import client from '@/services/client.service' import { Bell, BellOff, Code, Copy, Link, SatelliteDish, Trash2, TriangleAlert, Pin } from 'lucide-react' import { Event } from 'nostr-tools' @@ -71,7 +72,35 @@ export function useMenuActions({ return } try { - const pinListEvent = await client.fetchPinListEvent(pubkey) + // Build comprehensive relay list for pin status check + const allRelays = [ + ...(currentBrowsingRelayUrls || []), + ...(favoriteRelays || []), + ...BIG_RELAY_URLS, + ...FAST_READ_RELAY_URLS, + ...FAST_WRITE_RELAY_URLS + ] + + const normalizedRelays = allRelays + .map(url => normalizeUrl(url)) + .filter((url): url is string => !!url) + + const comprehensiveRelays = Array.from(new Set(normalizedRelays)) + + // Try to fetch pin list event from comprehensive relay list first + let pinListEvent = null + try { + const pinListEvents = await client.fetchEvents(comprehensiveRelays, { + authors: [pubkey], + kinds: [10001], // Pin list kind + limit: 1 + }) + pinListEvent = pinListEvents[0] || null + } catch (error) { + console.warn('[PinStatus] Error fetching pin list from comprehensive relays, falling back to default method:', error) + pinListEvent = await client.fetchPinListEvent(pubkey) + } + if (pinListEvent) { const isEventPinned = pinListEvent.tags.some(tag => tag[0] === 'e' && tag[1] === event.id) setIsPinned(isEventPinned) @@ -81,14 +110,42 @@ export function useMenuActions({ } } checkIfPinned() - }, [pubkey, event.id]) + }, [pubkey, event.id, currentBrowsingRelayUrls, favoriteRelays]) const handlePinNote = async () => { if (!pubkey) return try { - // Fetch existing pin list - let pinListEvent = await client.fetchPinListEvent(pubkey) + // Build comprehensive relay list for pin list fetching + const allRelays = [ + ...(currentBrowsingRelayUrls || []), + ...(favoriteRelays || []), + ...BIG_RELAY_URLS, + ...FAST_READ_RELAY_URLS, + ...FAST_WRITE_RELAY_URLS + ] + + const normalizedRelays = allRelays + .map(url => normalizeUrl(url)) + .filter((url): url is string => !!url) + + const comprehensiveRelays = Array.from(new Set(normalizedRelays)) + + // Try to fetch pin list event from comprehensive relay list first + let pinListEvent = null + try { + const pinListEvents = await client.fetchEvents(comprehensiveRelays, { + authors: [pubkey], + kinds: [10001], // Pin list kind + limit: 1 + }) + pinListEvent = pinListEvents[0] || null + } catch (error) { + console.warn('[PinNote] Error fetching pin list from comprehensive relays, falling back to default method:', error) + pinListEvent = await client.fetchPinListEvent(pubkey) + } + + console.log('[PinNote] Current pin list event:', pinListEvent) // Get existing event IDs, excluding the one we're toggling const existingEventIds = (pinListEvent?.tags || []) @@ -96,6 +153,10 @@ export function useMenuActions({ .map(tag => tag[1]) .filter(id => id !== event.id) + console.log('[PinNote] Existing event IDs (excluding current):', existingEventIds) + console.log('[PinNote] Current event ID:', event.id) + console.log('[PinNote] Is currently pinned:', isPinned) + let newTags: string[][] let successMessage: string @@ -103,18 +164,24 @@ export function useMenuActions({ // Unpin: just keep the existing tags without this event newTags = existingEventIds.map(id => ['e', id]) successMessage = t('Note unpinned') + console.log('[PinNote] Unpinning - new tags:', newTags) } else { // Pin: add this event to the existing list newTags = [...existingEventIds.map(id => ['e', id]), ['e', event.id]] successMessage = t('Note pinned') + console.log('[PinNote] Pinning - new tags:', newTags) } // Create and publish the new pin list event + console.log('[PinNote] Publishing new pin list event with', newTags.length, 'tags') + console.log('[PinNote] Publishing to comprehensive relays:', comprehensiveRelays) await publish({ kind: 10001, tags: newTags, content: '', created_at: Math.floor(Date.now() / 1000) + }, { + specifiedRelayUrls: comprehensiveRelays }) // Update local state - the publish will update the cache automatically @@ -337,7 +404,8 @@ export function useMenuActions({ } } - if (pubkey && event.pubkey === pubkey) { + // Pin functionality available for any note (not just own notes) + if (pubkey) { actions.push({ icon: Pin, label: isPinned ? t('Unpin note') : t('Pin note'), @@ -346,6 +414,10 @@ export function useMenuActions({ }, separator: true }) + } + + // Delete functionality only available for own notes + if (pubkey && event.pubkey === pubkey) { actions.push({ icon: Trash2, label: t('Try deleting this note'), diff --git a/src/components/Profile/ProfileFeed.tsx b/src/components/Profile/ProfileFeed.tsx index b72fc00..63b0de8 100644 --- a/src/components/Profile/ProfileFeed.tsx +++ b/src/components/Profile/ProfileFeed.tsx @@ -1,10 +1,12 @@ import KindFilter from '@/components/KindFilter' import NoteList, { TNoteListRef } from '@/components/NoteList' import Tabs from '@/components/Tabs' -import { BIG_RELAY_URLS } from '@/constants' +import { BIG_RELAY_URLS, FAST_READ_RELAY_URLS, FAST_WRITE_RELAY_URLS } from '@/constants' import { isTouchDevice } from '@/lib/utils' import { useKindFilter } from '@/providers/KindFilterProvider' import { useNostr } from '@/providers/NostrProvider' +import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' +import { normalizeUrl } from '@/lib/url' import client from '@/services/client.service' import storage from '@/services/local-storage.service' import { TFeedSubRequest, TNoteListMode } from '@/types' @@ -21,6 +23,7 @@ export default function ProfileFeed({ topSpace?: number }) { const { pubkey: myPubkey } = useNostr() + const { favoriteRelays } = useFavoriteRelays() const { showKinds } = useKindFilter() const [temporaryShowKinds, setTemporaryShowKinds] = useState(showKinds) const [listMode, setListMode] = useState(() => storage.getNoteListMode()) @@ -47,7 +50,24 @@ export default function ProfileFeed({ const init = async () => { // Privacy: Only use user's own relays + defaults, never connect to other users' relays const myRelayList = myPubkey ? await client.fetchRelayList(myPubkey) : { write: [], read: [] } - const userRelays = [...myRelayList.read, ...BIG_RELAY_URLS] + + // Build comprehensive relay list: user's inboxes + user's favorite relays + big relays + fast read relays + fast write relays + const allRelays = [ + ...(myRelayList.read || []), // User's inboxes (kind 10002) + ...(myRelayList.write || []), // User's outboxes (kind 10002) + ...(favoriteRelays || []), // User's favorite relays (kind 10012) + ...BIG_RELAY_URLS, // Big relays + ...FAST_READ_RELAY_URLS, // Fast read relays + ...FAST_WRITE_RELAY_URLS // Fast write relays + ] + + // Normalize and deduplicate relay URLs + const normalizedRelays = allRelays + .map(url => normalizeUrl(url)) + .filter((url): url is string => !!url) + + const userRelays = Array.from(new Set(normalizedRelays)) + console.log('[ProfileFeed] Using', userRelays.length, 'relays for profile feed:', userRelays) if (listMode === 'you') { if (!myPubkey) { @@ -57,14 +77,14 @@ export default function ProfileFeed({ setSubRequests([ { - urls: userRelays.slice(0, 5), + urls: userRelays, filter: { authors: [myPubkey], '#p': [pubkey] } }, { - urls: userRelays.slice(0, 5), + urls: userRelays, filter: { authors: [pubkey], '#p': [myPubkey] @@ -76,7 +96,7 @@ export default function ProfileFeed({ setSubRequests([ { - urls: userRelays.slice(0, 8), + urls: userRelays, filter: { authors: [pubkey] } @@ -84,14 +104,47 @@ export default function ProfileFeed({ ]) } init() - }, [pubkey, listMode, myPubkey]) + }, [pubkey, listMode, myPubkey, favoriteRelays]) // Fetch pinned notes useEffect(() => { const fetchPinnedNotes = async () => { setLoadingPinned(true) try { - const pinListEvent = await client.fetchPinListEvent(pubkey) + // Build comprehensive relay list for fetching pin list event + const myRelayList = myPubkey ? await client.fetchRelayList(myPubkey) : { write: [], read: [] } + const allRelaysForPinList = [ + ...(myRelayList.read || []), // User's inboxes (kind 10002) + ...(myRelayList.write || []), // User's outboxes (kind 10002) + ...(favoriteRelays || []), // User's favorite relays (kind 10012) + ...BIG_RELAY_URLS, // Big relays + ...FAST_READ_RELAY_URLS, // Fast read relays + ...FAST_WRITE_RELAY_URLS // Fast write relays + ] + + const normalizedRelaysForPinList = allRelaysForPinList + .map(url => normalizeUrl(url)) + .filter((url): url is string => !!url) + + const comprehensiveRelaysForPinList = Array.from(new Set(normalizedRelaysForPinList)) + console.log('[ProfileFeed] Using', comprehensiveRelaysForPinList.length, 'relays for pin list event:', comprehensiveRelaysForPinList) + console.log('[ProfileFeed] Relay breakdown - inboxes:', myRelayList.read?.length || 0, 'outboxes:', myRelayList.write?.length || 0, 'favorites:', favoriteRelays?.length || 0, 'big:', BIG_RELAY_URLS.length, 'fast_read:', FAST_READ_RELAY_URLS.length, 'fast_write:', FAST_WRITE_RELAY_URLS.length) + + // Try to fetch pin list event from comprehensive relay list first + let pinListEvent = null + try { + const pinListEvents = await client.fetchEvents(comprehensiveRelaysForPinList, { + authors: [pubkey], + kinds: [10001], // Pin list kind + limit: 1 + }) + pinListEvent = pinListEvents[0] || null + } catch (error) { + console.warn('[ProfileFeed] Error fetching pin list from comprehensive relays, falling back to default method:', error) + pinListEvent = await client.fetchPinListEvent(pubkey) + } + + console.log('[ProfileFeed] Pin list event:', pinListEvent) if (pinListEvent && pinListEvent.tags.length > 0) { // Extract event IDs from pin list const eventIds = pinListEvent.tags @@ -99,12 +152,45 @@ export default function ProfileFeed({ .map(tag => tag[1]) .reverse() // Reverse to show newest first + console.log('[ProfileFeed] Found', eventIds.length, 'pinned event IDs:', eventIds) + + // Use the same comprehensive relay list we built for the pin list event + console.log('[ProfileFeed] Using', comprehensiveRelaysForPinList.length, 'relays for pinned notes:', comprehensiveRelaysForPinList) + // Fetch the actual events const events = await client.fetchEvents( - [...BIG_RELAY_URLS], + comprehensiveRelaysForPinList, { ids: eventIds } ) + console.log('[ProfileFeed] Fetched', events.length, 'pinned events out of', eventIds.length, 'requested') + console.log('[ProfileFeed] Fetched events:', events.map(e => ({ id: e.id, content: e.content.substring(0, 50) + '...' }))) + + // Debug: Check which event IDs were not found + const foundEventIds = events.map(e => e.id) + const missingEventIds = eventIds.filter(id => !foundEventIds.includes(id)) + if (missingEventIds.length > 0) { + console.log('[ProfileFeed] Missing event IDs that could not be fetched:', missingEventIds) + + // Try to fetch missing events individually to see if any specific relay has them + for (const missingId of missingEventIds) { + try { + console.log('[ProfileFeed] Attempting to fetch missing event:', missingId) + const missingEvents = await client.fetchEvents(comprehensiveRelaysForPinList, { + ids: [missingId], + limit: 1 + }) + if (missingEvents.length > 0) { + console.log('[ProfileFeed] Successfully fetched missing event:', missingId, missingEvents[0].content.substring(0, 50) + '...') + } else { + console.log('[ProfileFeed] Missing event not found on any relay:', missingId) + } + } catch (error) { + console.error('[ProfileFeed] Error fetching missing event:', missingId, error) + } + } + } + // Sort by created_at desc (newest first) const sortedEvents = events.sort((a, b) => b.created_at - a.created_at) setPinnedEvents(sortedEvents) @@ -120,7 +206,7 @@ export default function ProfileFeed({ } fetchPinnedNotes() - }, [pubkey]) + }, [pubkey, myPubkey, favoriteRelays]) const handleListModeChange = (mode: TNoteListMode) => { setListMode(mode)