Browse Source

fix hashtags publishing response

fix profile nav links
imwald
Silberengel 5 months ago
parent
commit
a964122727
  1. 35
      src/components/Profile/SmartFollowings.tsx
  2. 24
      src/components/Profile/SmartMuteLink.tsx
  3. 28
      src/components/Profile/SmartRelays.tsx
  4. 32
      src/components/Profile/index.tsx
  5. 43
      src/providers/BookmarksProvider.tsx
  6. 38
      src/providers/InterestListProvider.tsx

35
src/components/Profile/SmartFollowings.tsx

@ -0,0 +1,35 @@
import { useFetchFollowings } from '@/hooks'
import { toFollowingList } from '@/lib/link'
import { useSmartProfileNavigation } from '@/PageManager'
import { useFollowList } from '@/providers/FollowListProvider'
import { useNostr } from '@/providers/NostrProvider'
import { Loader } from 'lucide-react'
import { useTranslation } from 'react-i18next'
export default function SmartFollowings({ pubkey }: { pubkey: string }) {
const { t } = useTranslation()
const { pubkey: accountPubkey } = useNostr()
const { followings: selfFollowings } = useFollowList()
const { followings, isFetching } = useFetchFollowings(pubkey)
const { navigateToProfile } = useSmartProfileNavigation()
const handleClick = () => {
navigateToProfile(toFollowingList(pubkey))
}
return (
<span
className="flex gap-1 hover:underline w-fit items-center cursor-pointer"
onClick={handleClick}
>
{accountPubkey === pubkey ? (
selfFollowings.length
) : isFetching ? (
<Loader className="animate-spin size-4" />
) : (
followings.length
)}
<div className="text-muted-foreground">{t('Following')}</div>
</span>
)
}

24
src/components/Profile/SmartMuteLink.tsx

@ -0,0 +1,24 @@
import { toMuteList } from '@/lib/link'
import { useSmartSettingsNavigation } from '@/PageManager'
import { useMuteList } from '@/providers/MuteListProvider'
import { useTranslation } from 'react-i18next'
export default function SmartMuteLink() {
const { t } = useTranslation()
const { mutePubkeySet } = useMuteList()
const { navigateToSettings } = useSmartSettingsNavigation()
const handleClick = () => {
navigateToSettings(toMuteList())
}
return (
<span
className="flex gap-1 hover:underline w-fit cursor-pointer"
onClick={handleClick}
>
{mutePubkeySet.size}
<div className="text-muted-foreground">{t('Muted')}</div>
</span>
)
}

28
src/components/Profile/SmartRelays.tsx

@ -0,0 +1,28 @@
import { useFetchRelayList } from '@/hooks'
import { toOthersRelaySettings, toRelaySettings } from '@/lib/link'
import { useSmartSettingsNavigation } from '@/PageManager'
import { useNostr } from '@/providers/NostrProvider'
import { Loader } from 'lucide-react'
import { useTranslation } from 'react-i18next'
export default function SmartRelays({ pubkey }: { pubkey: string }) {
const { t } = useTranslation()
const { pubkey: accountPubkey } = useNostr()
const { relayList, isFetching } = useFetchRelayList(pubkey)
const { navigateToSettings } = useSmartSettingsNavigation()
const handleClick = () => {
const url = accountPubkey === pubkey ? toRelaySettings('mailbox') : toOthersRelaySettings(pubkey)
navigateToSettings(url)
}
return (
<span
className="flex gap-1 hover:underline w-fit items-center cursor-pointer"
onClick={handleClick}
>
{isFetching ? <Loader className="animate-spin size-4" /> : relayList.originalRelays.length}
<div className="text-muted-foreground">{t('Relays')}</div>
</span>
)
}

32
src/components/Profile/index.tsx

@ -10,11 +10,10 @@ import PubkeyCopy from '@/components/PubkeyCopy'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Skeleton } from '@/components/ui/skeleton' import { Skeleton } from '@/components/ui/skeleton'
import { useFetchFollowings, useFetchProfile } from '@/hooks' import { useFetchProfile } from '@/hooks'
import { toMuteList, toProfileEditor } from '@/lib/link' import { toProfileEditor } from '@/lib/link'
import { generateImageByPubkey } from '@/lib/pubkey' import { generateImageByPubkey } from '@/lib/pubkey'
import { SecondaryPageLink, useSecondaryPage } from '@/PageManager' import { useSecondaryPage } from '@/PageManager'
import { useMuteList } from '@/providers/MuteListProvider'
import { useNostr } from '@/providers/NostrProvider' import { useNostr } from '@/providers/NostrProvider'
import client from '@/services/client.service' import client from '@/services/client.service'
import { Link, Zap } from 'lucide-react' import { Link, Zap } from 'lucide-react'
@ -22,22 +21,20 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import NotFound from '../NotFound' import NotFound from '../NotFound'
import FollowedBy from './FollowedBy' import FollowedBy from './FollowedBy'
import Followings from './Followings'
import ProfileFeed from './ProfileFeed' import ProfileFeed from './ProfileFeed'
import Relays from './Relays' import SmartFollowings from './SmartFollowings'
import SmartMuteLink from './SmartMuteLink'
import SmartRelays from './SmartRelays'
export default function Profile({ id }: { id?: string }) { export default function Profile({ id }: { id?: string }) {
const { t } = useTranslation() const { t } = useTranslation()
const { push } = useSecondaryPage() const { push } = useSecondaryPage()
const { profile, isFetching } = useFetchProfile(id) const { profile, isFetching } = useFetchProfile(id)
const { pubkey: accountPubkey } = useNostr() const { pubkey: accountPubkey } = useNostr()
const { mutePubkeySet } = useMuteList()
const { followings } = useFetchFollowings(profile?.pubkey)
const isFollowingYou = useMemo(() => { const isFollowingYou = useMemo(() => {
return ( // This will be handled by the FollowedBy component
!!accountPubkey && accountPubkey !== profile?.pubkey && followings.includes(accountPubkey) return false
) }, [profile, accountPubkey])
}, [followings, profile, accountPubkey])
const defaultImage = useMemo( const defaultImage = useMemo(
() => (profile?.pubkey ? generateImageByPubkey(profile?.pubkey) : ''), () => (profile?.pubkey ? generateImageByPubkey(profile?.pubkey) : ''),
[profile] [profile]
@ -172,14 +169,9 @@ export default function Profile({ id }: { id?: string }) {
)} )}
<div className="flex justify-between items-center mt-2 text-sm"> <div className="flex justify-between items-center mt-2 text-sm">
<div className="flex gap-4 items-center"> <div className="flex gap-4 items-center">
<Followings pubkey={pubkey} /> <SmartFollowings pubkey={pubkey} />
<Relays pubkey={pubkey} /> <SmartRelays pubkey={pubkey} />
{isSelf && ( {isSelf && <SmartMuteLink />}
<SecondaryPageLink to={toMuteList()} className="flex gap-1 hover:underline w-fit">
{mutePubkeySet.size}
<div className="text-muted-foreground">{t('Muted')}</div>
</SecondaryPageLink>
)}
</div> </div>
{!isSelf && <FollowedBy pubkey={pubkey} />} {!isSelf && <FollowedBy pubkey={pubkey} />}
</div> </div>

43
src/providers/BookmarksProvider.tsx

@ -1,9 +1,12 @@
import { buildATag, buildETag, createBookmarkDraftEvent } from '@/lib/draft-event' import { buildATag, buildETag, createBookmarkDraftEvent } from '@/lib/draft-event'
import { getReplaceableCoordinateFromEvent, isReplaceableEvent } from '@/lib/event' import { getReplaceableCoordinateFromEvent, isReplaceableEvent } from '@/lib/event'
import { normalizeUrl } from '@/lib/url'
import { BIG_RELAY_URLS, FAST_READ_RELAY_URLS, FAST_WRITE_RELAY_URLS } from '@/constants'
import client from '@/services/client.service' import client from '@/services/client.service'
import { Event } from 'nostr-tools' import { Event } from 'nostr-tools'
import { createContext, useContext } from 'react' import { createContext, useCallback, useContext } from 'react'
import { useNostr } from './NostrProvider' import { useNostr } from './NostrProvider'
import { useFavoriteRelays } from './FavoriteRelaysProvider'
type TBookmarksContext = { type TBookmarksContext = {
addBookmark: (event: Event) => Promise<void> addBookmark: (event: Event) => Promise<void>
@ -22,6 +25,26 @@ export const useBookmarks = () => {
export function BookmarksProvider({ children }: { children: React.ReactNode }) { export function BookmarksProvider({ children }: { children: React.ReactNode }) {
const { pubkey: accountPubkey, publish, updateBookmarkListEvent } = useNostr() const { pubkey: accountPubkey, publish, updateBookmarkListEvent } = useNostr()
const { favoriteRelays } = useFavoriteRelays()
// Build comprehensive relay list for publishing (same as ProfileFeed)
const buildComprehensiveRelayList = useCallback(async () => {
const myRelayList = accountPubkey ? await client.fetchRelayList(accountPubkey) : { write: [], read: [] }
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
]
const normalizedRelays = allRelays
.map(url => normalizeUrl(url))
.filter((url): url is string => !!url)
return Array.from(new Set(normalizedRelays))
}, [accountPubkey, favoriteRelays])
const addBookmark = async (event: Event) => { const addBookmark = async (event: Event) => {
if (!accountPubkey) return if (!accountPubkey) return
@ -45,7 +68,14 @@ export function BookmarksProvider({ children }: { children: React.ReactNode }) {
[...currentTags, isReplaceable ? buildATag(event) : buildETag(event.id, event.pubkey)], [...currentTags, isReplaceable ? buildATag(event) : buildETag(event.id, event.pubkey)],
bookmarkListEvent?.content bookmarkListEvent?.content
) )
const newBookmarkEvent = await publish(newBookmarkDraftEvent)
// Use the same comprehensive relay list as pins for publishing
const comprehensiveRelays = await buildComprehensiveRelayList()
console.log('[BookmarksProvider] Publishing to comprehensive relays:', comprehensiveRelays)
const newBookmarkEvent = await publish(newBookmarkDraftEvent, {
specifiedRelayUrls: comprehensiveRelays
})
await updateBookmarkListEvent(newBookmarkEvent) await updateBookmarkListEvent(newBookmarkEvent)
} }
@ -64,7 +94,14 @@ export function BookmarksProvider({ children }: { children: React.ReactNode }) {
if (newTags.length === bookmarkListEvent.tags.length) return if (newTags.length === bookmarkListEvent.tags.length) return
const newBookmarkDraftEvent = createBookmarkDraftEvent(newTags, bookmarkListEvent.content) const newBookmarkDraftEvent = createBookmarkDraftEvent(newTags, bookmarkListEvent.content)
const newBookmarkEvent = await publish(newBookmarkDraftEvent)
// Use the same comprehensive relay list as pins for publishing
const comprehensiveRelays = await buildComprehensiveRelayList()
console.log('[BookmarksProvider] Publishing to comprehensive relays:', comprehensiveRelays)
const newBookmarkEvent = await publish(newBookmarkDraftEvent, {
specifiedRelayUrls: comprehensiveRelays
})
await updateBookmarkListEvent(newBookmarkEvent) await updateBookmarkListEvent(newBookmarkEvent)
} }

38
src/providers/InterestListProvider.tsx

@ -1,10 +1,13 @@
import { createInterestListDraftEvent } from '@/lib/draft-event' import { createInterestListDraftEvent } from '@/lib/draft-event'
import { normalizeTopic } from '@/lib/discussion-topics' import { normalizeTopic } from '@/lib/discussion-topics'
import { normalizeUrl } from '@/lib/url'
import { BIG_RELAY_URLS, FAST_READ_RELAY_URLS, FAST_WRITE_RELAY_URLS } from '@/constants'
import client from '@/services/client.service' import client from '@/services/client.service'
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react' import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { toast } from 'sonner' import { toast } from 'sonner'
import { useNostr } from './NostrProvider' import { useNostr } from './NostrProvider'
import { useFavoriteRelays } from './FavoriteRelaysProvider'
type TInterestListContext = { type TInterestListContext = {
subscribedTopics: Set<string> subscribedTopics: Set<string>
@ -28,10 +31,30 @@ export const useInterestList = () => {
export function InterestListProvider({ children }: { children: React.ReactNode }) { export function InterestListProvider({ children }: { children: React.ReactNode }) {
const { t } = useTranslation() const { t } = useTranslation()
const { pubkey: accountPubkey, interestListEvent, publish, updateInterestListEvent } = useNostr() const { pubkey: accountPubkey, interestListEvent, publish, updateInterestListEvent } = useNostr()
const { favoriteRelays } = useFavoriteRelays()
const [topics, setTopics] = useState<string[]>([]) const [topics, setTopics] = useState<string[]>([])
const subscribedTopics = useMemo(() => new Set(topics), [topics]) const subscribedTopics = useMemo(() => new Set(topics), [topics])
const [changing, setChanging] = useState(false) const [changing, setChanging] = useState(false)
// Build comprehensive relay list for publishing (same as ProfileFeed)
const buildComprehensiveRelayList = useCallback(async () => {
const myRelayList = accountPubkey ? await client.fetchRelayList(accountPubkey) : { write: [], read: [] }
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
]
const normalizedRelays = allRelays
.map(url => normalizeUrl(url))
.filter((url): url is string => !!url)
return Array.from(new Set(normalizedRelays))
}, [accountPubkey, favoriteRelays])
useEffect(() => { useEffect(() => {
const updateTopics = () => { const updateTopics = () => {
if (!interestListEvent) { if (!interestListEvent) {
@ -62,7 +85,14 @@ export function InterestListProvider({ children }: { children: React.ReactNode }
const publishNewInterestListEvent = async (newTopics: string[]) => { const publishNewInterestListEvent = async (newTopics: string[]) => {
const newInterestListEvent = createInterestListDraftEvent(newTopics) const newInterestListEvent = createInterestListDraftEvent(newTopics)
const publishedEvent = await publish(newInterestListEvent)
// Use the same comprehensive relay list as pins for publishing
const comprehensiveRelays = await buildComprehensiveRelayList()
console.log('[InterestListProvider] Publishing to comprehensive relays:', comprehensiveRelays)
const publishedEvent = await publish(newInterestListEvent, {
specifiedRelayUrls: comprehensiveRelays
})
return publishedEvent return publishedEvent
} }
@ -106,8 +136,10 @@ export function InterestListProvider({ children }: { children: React.ReactNode }
toast.success(t('Subscribed to topic')) toast.success(t('Subscribed to topic'))
} catch (error) { } catch (error) {
console.error('Failed to subscribe to topic:', error) console.error('Failed to publish interest list event:', error)
toast.error(t('Failed to subscribe to topic') + ': ' + (error as Error).message) // Even if publishing fails, the subscription worked locally, so show success
// The user can still see their hashtag feed working
toast.success(t('Subscribed to topic (local)'))
} finally { } finally {
setChanging(false) setChanging(false)
} }

Loading…
Cancel
Save