From 64a557396965430d4857864a94f457f72b14e284 Mon Sep 17 00:00:00 2001 From: codytseng Date: Fri, 17 Jan 2025 12:07:22 +0800 Subject: [PATCH] feat: others relays --- .../MailboxSetting/MailboxRelay.tsx | 16 +---- src/components/MailboxSetting/index.tsx | 12 +--- src/components/OthersRelayList/index.tsx | 66 +++++++++++++++++++ src/components/RelayIcon/index.tsx | 27 ++++++++ src/i18n/en.ts | 1 + src/i18n/zh.ts | 1 + src/lib/event.ts | 8 +-- src/lib/link.ts | 4 ++ src/lib/relay.ts | 15 ++++- .../OthersRelaySettingsPage/index.tsx | 24 +++++++ src/pages/secondary/ProfilePage/index.tsx | 34 +++++++--- src/routes.tsx | 2 + 12 files changed, 174 insertions(+), 36 deletions(-) create mode 100644 src/components/OthersRelayList/index.tsx create mode 100644 src/components/RelayIcon/index.tsx create mode 100644 src/pages/secondary/OthersRelaySettingsPage/index.tsx diff --git a/src/components/MailboxSetting/MailboxRelay.tsx b/src/components/MailboxSetting/MailboxRelay.tsx index 6b717451..db8e77d5 100644 --- a/src/components/MailboxSetting/MailboxRelay.tsx +++ b/src/components/MailboxSetting/MailboxRelay.tsx @@ -1,4 +1,3 @@ -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { Select, SelectContent, @@ -7,9 +6,9 @@ import { SelectValue } from '@/components/ui/select' import { TMailboxRelay, TMailboxRelayScope } from '@/types' -import { CircleX, Server } from 'lucide-react' -import { useMemo } from 'react' +import { CircleX } from 'lucide-react' import { useTranslation } from 'react-i18next' +import RelayIcon from '../RelayIcon' export default function MailboxRelay({ mailboxRelay, @@ -21,20 +20,11 @@ export default function MailboxRelay({ removeMailboxRelay: (url: string) => void }) { const { t } = useTranslation() - const relayIcon = useMemo(() => { - const url = new URL(mailboxRelay.url) - return `${url.protocol === 'wss:' ? 'https:' : 'http:'}//${url.host}/favicon.ico` - }, [mailboxRelay.url]) return (
- - - - - - +
{mailboxRelay.url}
diff --git a/src/components/MailboxSetting/index.tsx b/src/components/MailboxSetting/index.tsx index 645225ac..fbe79d69 100644 --- a/src/components/MailboxSetting/index.tsx +++ b/src/components/MailboxSetting/index.tsx @@ -1,4 +1,5 @@ import { Button } from '@/components/ui/button' +import { relayListToMailboxRelay } from '@/lib/relay' import { normalizeUrl } from '@/lib/url' import { useNostr } from '@/providers/NostrProvider' import { TMailboxRelay, TMailboxRelayScope } from '@/types' @@ -17,16 +18,7 @@ export default function MailboxSetting() { useEffect(() => { if (!relayList) return - const mailboxRelays: TMailboxRelay[] = relayList.read.map((url) => ({ url, scope: 'read' })) - relayList.write.forEach((url) => { - const item = mailboxRelays.find((r) => r.url === url) - if (item) { - item.scope = 'both' - } else { - mailboxRelays.push({ url, scope: 'write' }) - } - }) - setRelays(mailboxRelays) + setRelays(relayListToMailboxRelay(relayList)) }, [relayList]) if (!pubkey) { diff --git a/src/components/OthersRelayList/index.tsx b/src/components/OthersRelayList/index.tsx new file mode 100644 index 00000000..a1329dd9 --- /dev/null +++ b/src/components/OthersRelayList/index.tsx @@ -0,0 +1,66 @@ +import { useSecondaryPage } from '@/PageManager' +import { Button } from '@/components/ui/button' +import { useFetchRelayList } from '@/hooks/useFetchRelayList' +import { toNoteList } from '@/lib/link' +import { userIdToPubkey } from '@/lib/pubkey' +import { relayListToMailboxRelay } from '@/lib/relay' +import { simplifyUrl } from '@/lib/url' +import { TMailboxRelay } from '@/types' +import { ListPlus, Telescope } from 'lucide-react' +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import RelayIcon from '../RelayIcon' +import SaveRelayDropdownMenu from '../SaveRelayDropdownMenu' +import { Badge } from '../ui/badge' + +export default function OthersRelayList({ userId }: { userId: string }) { + const { t } = useTranslation() + const pubkey = useMemo(() => userIdToPubkey(userId), [userId]) + const { relayList, isFetching } = useFetchRelayList(pubkey) + const mailboxRelays = useMemo(() => relayListToMailboxRelay(relayList), [relayList]) + + if (isFetching) { + return
{t('loading...')}
+ } + + return ( +
+ {mailboxRelays.map((relay, index) => ( + + ))} +
+ ) +} + +function RelayItem({ relay }: { relay: TMailboxRelay }) { + const { t } = useTranslation() + const { push } = useSecondaryPage() + const { url, scope } = relay + + return ( +
+
push(toNoteList({ relay: url }))} + > + +
{simplifyUrl(url)}
+
+
+ {scope === 'read' ? ( + {t('Read')} + ) : scope === 'write' ? ( + {t('Write')} + ) : null} + + + + +
+
+ ) +} diff --git a/src/components/RelayIcon/index.tsx b/src/components/RelayIcon/index.tsx new file mode 100644 index 00000000..86e8cb6c --- /dev/null +++ b/src/components/RelayIcon/index.tsx @@ -0,0 +1,27 @@ +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { Server } from 'lucide-react' +import { useMemo } from 'react' + +export default function RelayIcon({ + url, + className = 'w-6 h-6', + iconSize = 14 +}: { + url: string + className?: string + iconSize?: number +}) { + const icon = useMemo(() => { + const u = new URL(url) + return `${u.protocol === 'wss:' ? 'https:' : 'http:'}//${u.host}/favicon.ico` + }, [url]) + + return ( + + + + + + + ) +} diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 2a2b3107..99532dab 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -48,6 +48,7 @@ export default { 'switch to system theme': 'switch to system theme', Note: 'Note', "username's following": "{{username}}'s following", + "username's used relays": "{{username}}'s used relays", Login: 'Login', 'Follows you': 'Follows you', 'Relay Settings': 'Relay Settings', diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts index c9ca2ff7..d191ebcf 100644 --- a/src/i18n/zh.ts +++ b/src/i18n/zh.ts @@ -48,6 +48,7 @@ export default { 'switch to system theme': '切换到系统主题', Note: '笔记', "username's following": '{{username}} 的关注', + "username's used relays": '{{username}} 使用的服务器', Login: '登录', 'Follows you': '关注了你', 'Relay Settings': '服务器设置', diff --git a/src/lib/event.ts b/src/lib/event.ts index 54521e95..e6210ae4 100644 --- a/src/lib/event.ts +++ b/src/lib/event.ts @@ -90,10 +90,10 @@ export function getRelayListFromRelayListEvent(event?: Event) { const normalizedUrl = normalizeUrl(url) switch (type) { - case 'w': + case 'write': relayList.write.push(normalizedUrl) break - case 'r': + case 'read': relayList.read.push(normalizedUrl) break default: @@ -102,8 +102,8 @@ export function getRelayListFromRelayListEvent(event?: Event) { } }) return { - write: relayList.write.length ? relayList.write.slice(0, 10) : BIG_RELAY_URLS, - read: relayList.read.length ? relayList.read.slice(0, 10) : BIG_RELAY_URLS + write: relayList.write.length ? relayList.write : BIG_RELAY_URLS, + read: relayList.read.length ? relayList.read : BIG_RELAY_URLS } } diff --git a/src/lib/link.ts b/src/lib/link.ts index 9d2b3075..d1194cfb 100644 --- a/src/lib/link.ts +++ b/src/lib/link.ts @@ -37,6 +37,10 @@ export const toFollowingList = (pubkey: string) => { const npub = nip19.npubEncode(pubkey) return `/users/${npub}/following` } +export const toOthersRelaySettings = (pubkey: string) => { + const npub = nip19.npubEncode(pubkey) + return `/users/${npub}/relays` +} export const toRelaySettings = () => '/relay-settings' export const toSettings = () => '/settings' export const toProfileEditor = () => '/profile-editor' diff --git a/src/lib/relay.ts b/src/lib/relay.ts index 738f1244..24da82bb 100644 --- a/src/lib/relay.ts +++ b/src/lib/relay.ts @@ -1,4 +1,4 @@ -import { TRelayInfo } from '@/types' +import { TMailboxRelay, TRelayInfo, TRelayList } from '@/types' export function checkAlgoRelay(relayInfo: TRelayInfo | undefined) { return relayInfo?.software === 'https://github.com/bitvora/algo-relay' // hardcode for now @@ -7,3 +7,16 @@ export function checkAlgoRelay(relayInfo: TRelayInfo | undefined) { export function checkSearchRelay(relayInfo: TRelayInfo | undefined) { return relayInfo?.supported_nips?.includes(50) } + +export function relayListToMailboxRelay(relayList: TRelayList): TMailboxRelay[] { + const mailboxRelays: TMailboxRelay[] = relayList.read.map((url) => ({ url, scope: 'read' })) + relayList.write.forEach((url) => { + const item = mailboxRelays.find((r) => r.url === url) + if (item) { + item.scope = 'both' + } else { + mailboxRelays.push({ url, scope: 'write' }) + } + }) + return mailboxRelays +} diff --git a/src/pages/secondary/OthersRelaySettingsPage/index.tsx b/src/pages/secondary/OthersRelaySettingsPage/index.tsx new file mode 100644 index 00000000..9f95dc7d --- /dev/null +++ b/src/pages/secondary/OthersRelaySettingsPage/index.tsx @@ -0,0 +1,24 @@ +import OthersRelayList from '@/components/OthersRelayList' +import { useFetchProfile } from '@/hooks' +import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' +import { useTranslation } from 'react-i18next' + +export default function RelaySettingsPage({ id, index }: { id?: string; index?: number }) { + const { t } = useTranslation() + const { profile } = useFetchProfile(id) + + if (!id || !profile) { + return null + } + + return ( + +
+ +
+
+ ) +} diff --git a/src/pages/secondary/ProfilePage/index.tsx b/src/pages/secondary/ProfilePage/index.tsx index 6168e115..4f09a934 100644 --- a/src/pages/secondary/ProfilePage/index.tsx +++ b/src/pages/secondary/ProfilePage/index.tsx @@ -11,7 +11,12 @@ import { Skeleton } from '@/components/ui/skeleton' import { useFetchFollowings, useFetchProfile } from '@/hooks' import { useFetchRelayList } from '@/hooks/useFetchRelayList' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' -import { toFollowingList, toProfileEditor } from '@/lib/link' +import { + toFollowingList, + toOthersRelaySettings, + toProfileEditor, + toRelaySettings +} from '@/lib/link' import { generateImageByPubkey } from '@/lib/pubkey' import { SecondaryPageLink, useSecondaryPage } from '@/PageManager' import { useFeed } from '@/providers/FeedProvider' @@ -46,6 +51,10 @@ export default function ProfilePage({ id, index }: { id?: string; index?: number () => (profile?.pubkey ? generateImageByPubkey(profile?.pubkey) : ''), [profile] ) + const relayCount = useMemo( + () => new Set(relayList.write.concat(relayList.read)).size, + [relayList] + ) const isSelf = accountPubkey === profile?.pubkey if (!profile && isFetching) { @@ -107,13 +116,22 @@ export default function ProfilePage({ id, index }: { id?: string; index?: number
- - {isSelf ? selfFollowings.length : followings.length} -
{t('Following')}
-
+
+ + {isSelf ? selfFollowings.length : followings.length} +
{t('Following')}
+
+ + {relayCount} +
{t('Relays')}
+
+
{!isFetchingRelayInfo && ( diff --git a/src/routes.tsx b/src/routes.tsx index 9bfcb35b..a7f0b024 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -4,6 +4,7 @@ import FollowingListPage from './pages/secondary/FollowingListPage' import HomePage from './pages/secondary/HomePage' import NoteListPage from './pages/secondary/NoteListPage' import NotePage from './pages/secondary/NotePage' +import OthersRelaySettingsPage from './pages/secondary/OthersRelaySettingsPage' import ProfileEditorPage from './pages/secondary/ProfileEditorPage' import ProfileListPage from './pages/secondary/ProfileListPage' import ProfilePage from './pages/secondary/ProfilePage' @@ -17,6 +18,7 @@ const ROUTES = [ { path: '/users', element: }, { path: '/users/:id', element: }, { path: '/users/:id/following', element: }, + { path: '/users/:id/relays', element: }, { path: '/relay-settings', element: }, { path: '/settings', element: }, { path: '/profile-editor', element: }