diff --git a/src/components/FeedSwitcher/index.tsx b/src/components/FeedSwitcher/index.tsx
index 6da3376..055fc3e 100644
--- a/src/components/FeedSwitcher/index.tsx
+++ b/src/components/FeedSwitcher/index.tsx
@@ -5,7 +5,8 @@ import { SecondaryPageLink } from '@/PageManager'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useFeed } from '@/providers/FeedProvider'
import { useNostr } from '@/providers/NostrProvider'
-import { UsersRound } from 'lucide-react'
+import { usePinnedUsers } from '@/providers/PinnedUsersProvider'
+import { Star, UsersRound } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import RelayIcon from '../RelayIcon'
import RelaySetCard from '../RelaySetCard'
@@ -15,6 +16,7 @@ export default function FeedSwitcher({ close }: { close?: () => void }) {
const { pubkey } = useNostr()
const { relaySets, favoriteRelays } = useFavoriteRelays()
const { feedInfo, switchFeed } = useFeed()
+ const { pinnedPubkeySet } = usePinnedUsers()
return (
@@ -35,6 +37,23 @@ export default function FeedSwitcher({ close }: { close?: () => void }) {
+ {
+ if (!pubkey) return
+ switchFeed('pinned', { pubkey })
+ close?.()
+ }}
+ >
+
+
+
+
+
{t('Special Follow')}
+
+
+
isPinned(pubkey), [isPinned, pubkey])
+
+ if (!accountPubkey || (pubkey && pubkey === accountPubkey)) return null
+
+ const onToggle = async (e: React.MouseEvent) => {
+ e.stopPropagation()
+ checkLogin(async () => {
+ setUpdating(true)
+ try {
+ await togglePin(pubkey)
+ } catch (error) {
+ if (pinned) {
+ toast.error(t('Unfollow failed') + ': ' + (error as Error).message)
+ } else {
+ toast.error(t('Follow failed') + ': ' + (error as Error).message)
+ }
+ } finally {
+ setUpdating(false)
+ }
+ })
+ }
+
+ return (
+
+ )
+}
diff --git a/src/components/Profile/index.tsx b/src/components/Profile/index.tsx
index c1ba795..eda5218 100644
--- a/src/components/Profile/index.tsx
+++ b/src/components/Profile/index.tsx
@@ -27,6 +27,7 @@ import FollowedBy from './FollowedBy'
import Followings from './Followings'
import ProfileFeed from './ProfileFeed'
import Relays from './Relays'
+import SpecialFollowButton from './SpecialFollowButton'
export default function Profile({ id }: { id?: string }) {
const { t } = useTranslation()
@@ -133,6 +134,7 @@ export default function Profile({ id }: { id?: string }) {
) : (
<>
{!!lightningAddress && }
+
>
)}
diff --git a/src/pages/primary/NoteListPage/FeedButton.tsx b/src/pages/primary/NoteListPage/FeedButton.tsx
index 9a63fea..a725605 100644
--- a/src/pages/primary/NoteListPage/FeedButton.tsx
+++ b/src/pages/primary/NoteListPage/FeedButton.tsx
@@ -6,7 +6,7 @@ import { cn } from '@/lib/utils'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useFeed } from '@/providers/FeedProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
-import { ChevronDown, Server, UsersRound } from 'lucide-react'
+import { ChevronDown, Server, Star, UsersRound } from 'lucide-react'
import { forwardRef, HTMLAttributes, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
@@ -62,6 +62,9 @@ const FeedSwitcherTrigger = forwardRef {
+ if (feedInfo?.feedType === 'following') return
+ if (feedInfo?.feedType === 'pinned') return
+ return
+ }, [feedInfo])
+
return (
- {feedInfo?.feedType === 'following' ?
:
}
+ {icon}
{title}
diff --git a/src/pages/primary/NoteListPage/PinnedFeed.tsx b/src/pages/primary/NoteListPage/PinnedFeed.tsx
new file mode 100644
index 0000000..7469605
--- /dev/null
+++ b/src/pages/primary/NoteListPage/PinnedFeed.tsx
@@ -0,0 +1,34 @@
+import NormalFeed from '@/components/NormalFeed'
+import { useFeed } from '@/providers/FeedProvider'
+import { useNostr } from '@/providers/NostrProvider'
+import { usePinnedUsers } from '@/providers/PinnedUsersProvider'
+import client from '@/services/client.service'
+import { TFeedSubRequest } from '@/types'
+import { useEffect, useRef, useState } from 'react'
+
+export default function PinnedFeed() {
+ const { pubkey } = useNostr()
+ const { feedInfo } = useFeed()
+ const { pinnedPubkeySet } = usePinnedUsers()
+ const [subRequests, setSubRequests] = useState([])
+ const initializedRef = useRef(false)
+
+ useEffect(() => {
+ if (initializedRef.current) return
+
+ async function init() {
+ if (feedInfo?.feedType !== 'pinned' || !pubkey || pinnedPubkeySet.size === 0) {
+ setSubRequests([])
+ return
+ }
+
+ initializedRef.current = true
+ const pinnedPubkeys = Array.from(pinnedPubkeySet)
+ setSubRequests(await client.generateSubRequestsForPubkeys(pinnedPubkeys, pubkey))
+ }
+
+ init()
+ }, [feedInfo?.feedType, pubkey, pinnedPubkeySet])
+
+ return
+}
diff --git a/src/pages/primary/NoteListPage/index.tsx b/src/pages/primary/NoteListPage/index.tsx
index 88ea41d..cc107cd 100644
--- a/src/pages/primary/NoteListPage/index.tsx
+++ b/src/pages/primary/NoteListPage/index.tsx
@@ -22,6 +22,7 @@ import {
import { useTranslation } from 'react-i18next'
import FeedButton from './FeedButton'
import FollowingFeed from './FollowingFeed'
+import PinnedFeed from './PinnedFeed'
import RelaysFeed from './RelaysFeed'
const NoteListPage = forwardRef((_, ref) => {
@@ -59,8 +60,13 @@ const NoteListPage = forwardRef((_, ref) => {
} else if (feedInfo.feedType === 'following' && !pubkey) {
switchFeed(null)
return null
+ } else if (feedInfo.feedType === 'pinned' && !pubkey) {
+ switchFeed(null)
+ return null
} else if (feedInfo.feedType === 'following') {
content =
+ } else if (feedInfo.feedType === 'pinned') {
+ content =
} else {
content = (
<>
diff --git a/src/providers/FeedProvider.tsx b/src/providers/FeedProvider.tsx
index a932a02..1369b52 100644
--- a/src/providers/FeedProvider.tsx
+++ b/src/providers/FeedProvider.tsx
@@ -65,6 +65,11 @@ export function FeedProvider({ children }: { children: React.ReactNode }) {
return await switchFeed('following', { pubkey })
}
+ // update pinned feed if pubkey changes
+ if (feedInfo?.feedType === 'pinned' && pubkey) {
+ return await switchFeed('pinned', { pubkey })
+ }
+
setIsReady(true)
}
@@ -147,6 +152,20 @@ export function FeedProvider({ children }: { children: React.ReactNode }) {
setIsReady(true)
return
}
+ if (feedType === 'pinned') {
+ if (!options.pubkey) {
+ setIsReady(true)
+ return
+ }
+ const newFeedInfo = { feedType }
+ setFeedInfo(newFeedInfo)
+ feedInfoRef.current = newFeedInfo
+ storage.setFeedInfo(newFeedInfo, pubkey)
+
+ setRelayUrls([])
+ setIsReady(true)
+ return
+ }
setIsReady(true)
}
diff --git a/src/types/index.d.ts b/src/types/index.d.ts
index ad4ac8b..004169c 100644
--- a/src/types/index.d.ts
+++ b/src/types/index.d.ts
@@ -107,7 +107,7 @@ export type TAccount = {
export type TAccountPointer = Pick
-export type TFeedType = 'following' | 'relays' | 'relay'
+export type TFeedType = 'following' | 'pinned' | 'relays' | 'relay'
export type TFeedInfo = { feedType: TFeedType; id?: string } | null
export type TLanguage = 'en' | 'zh' | 'pl'