Compare commits
1 Commits
v0.2.3
...
refactor-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7deb3fe2d3 |
@@ -6,7 +6,8 @@ import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
|
|||||||
import { useFeed } from '@/providers/FeedProvider'
|
import { useFeed } from '@/providers/FeedProvider'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
import { usePinnedUsers } from '@/providers/PinnedUsersProvider'
|
import { usePinnedUsers } from '@/providers/PinnedUsersProvider'
|
||||||
import { Star, UsersRound } from 'lucide-react'
|
import { Settings2, Star, UsersRound } from 'lucide-react'
|
||||||
|
import { useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import RelayIcon from '../RelayIcon'
|
import RelayIcon from '../RelayIcon'
|
||||||
import RelaySetCard from '../RelaySetCard'
|
import RelaySetCard from '../RelaySetCard'
|
||||||
@@ -17,81 +18,112 @@ export default function FeedSwitcher({ close }: { close?: () => void }) {
|
|||||||
const { relaySets, favoriteRelays } = useFavoriteRelays()
|
const { relaySets, favoriteRelays } = useFavoriteRelays()
|
||||||
const { feedInfo, switchFeed } = useFeed()
|
const { feedInfo, switchFeed } = useFeed()
|
||||||
const { pinnedPubkeySet } = usePinnedUsers()
|
const { pinnedPubkeySet } = usePinnedUsers()
|
||||||
|
const filteredRelaySets = useMemo(
|
||||||
|
() => relaySets.filter((set) => set.relayUrls.length > 0),
|
||||||
|
[relaySets]
|
||||||
|
)
|
||||||
|
const hasRelays = filteredRelaySets.length > 0 || favoriteRelays.length > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-4">
|
||||||
<FeedSwitcherItem
|
{/* Personal Feeds Section */}
|
||||||
isActive={feedInfo?.feedType === 'following'}
|
<div className="space-y-2">
|
||||||
disabled={!pubkey}
|
<SectionHeader title={t('Personal Feeds')} />
|
||||||
onClick={() => {
|
<div className="space-y-1.5">
|
||||||
if (!pubkey) return
|
<FeedSwitcherItem
|
||||||
switchFeed('following', { pubkey })
|
isActive={feedInfo?.feedType === 'following'}
|
||||||
close?.()
|
disabled={!pubkey}
|
||||||
}}
|
onClick={() => {
|
||||||
>
|
if (!pubkey) return
|
||||||
<div className="flex gap-2 items-center">
|
switchFeed('following', { pubkey })
|
||||||
<div className="flex justify-center items-center w-6 h-6 shrink-0">
|
|
||||||
<UsersRound className="size-4" />
|
|
||||||
</div>
|
|
||||||
<div>{t('Following')}</div>
|
|
||||||
</div>
|
|
||||||
</FeedSwitcherItem>
|
|
||||||
|
|
||||||
<FeedSwitcherItem
|
|
||||||
isActive={feedInfo?.feedType === 'pinned'}
|
|
||||||
disabled={!pubkey || pinnedPubkeySet.size === 0}
|
|
||||||
onClick={() => {
|
|
||||||
if (!pubkey) return
|
|
||||||
switchFeed('pinned', { pubkey })
|
|
||||||
close?.()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<div className="flex justify-center items-center w-6 h-6 shrink-0">
|
|
||||||
<Star className="size-4" />
|
|
||||||
</div>
|
|
||||||
<div>{t('Special Follow')}</div>
|
|
||||||
</div>
|
|
||||||
</FeedSwitcherItem>
|
|
||||||
|
|
||||||
<div className="flex justify-end items-center text-sm">
|
|
||||||
<SecondaryPageLink
|
|
||||||
to={toRelaySettings()}
|
|
||||||
className="text-primary font-semibold"
|
|
||||||
onClick={() => close?.()}
|
|
||||||
>
|
|
||||||
{t('edit')}
|
|
||||||
</SecondaryPageLink>
|
|
||||||
</div>
|
|
||||||
{relaySets
|
|
||||||
.filter((set) => set.relayUrls.length > 0)
|
|
||||||
.map((set) => (
|
|
||||||
<RelaySetCard
|
|
||||||
key={set.id}
|
|
||||||
relaySet={set}
|
|
||||||
select={feedInfo?.feedType === 'relays' && set.id === feedInfo.id}
|
|
||||||
onSelectChange={(select) => {
|
|
||||||
if (!select) return
|
|
||||||
switchFeed('relays', { activeRelaySetId: set.id })
|
|
||||||
close?.()
|
close?.()
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex gap-3 items-center">
|
||||||
|
<div className="flex justify-center items-center size-6 shrink-0">
|
||||||
|
<UsersRound className="size-5" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">{t('Following')}</div>
|
||||||
|
</div>
|
||||||
|
</FeedSwitcherItem>
|
||||||
|
|
||||||
|
<FeedSwitcherItem
|
||||||
|
isActive={feedInfo?.feedType === 'pinned'}
|
||||||
|
disabled={!pubkey || pinnedPubkeySet.size === 0}
|
||||||
|
onClick={() => {
|
||||||
|
if (!pubkey) return
|
||||||
|
switchFeed('pinned', { pubkey })
|
||||||
|
close?.()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex gap-3 items-center">
|
||||||
|
<div className="flex justify-center items-center size-6 shrink-0">
|
||||||
|
<Star className="size-5" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">{t('Special Follow')}</div>
|
||||||
|
</div>
|
||||||
|
</FeedSwitcherItem>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Relay Feeds Section */}
|
||||||
|
{hasRelays && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<SectionHeader
|
||||||
|
title={t('Relay Feeds')}
|
||||||
|
action={
|
||||||
|
<SecondaryPageLink
|
||||||
|
to={toRelaySettings()}
|
||||||
|
className="flex items-center gap-1 text-xs text-primary hover:text-primary-hover transition-colors font-medium"
|
||||||
|
onClick={() => close?.()}
|
||||||
|
>
|
||||||
|
<Settings2 className="size-3" />
|
||||||
|
{t('edit')}
|
||||||
|
</SecondaryPageLink>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
<div className="space-y-1.5">
|
||||||
{favoriteRelays.map((relay) => (
|
{filteredRelaySets.map((set) => (
|
||||||
<FeedSwitcherItem
|
<RelaySetCard
|
||||||
key={relay}
|
key={set.id}
|
||||||
isActive={feedInfo?.feedType === 'relay' && feedInfo.id === relay}
|
relaySet={set}
|
||||||
onClick={() => {
|
select={feedInfo?.feedType === 'relays' && set.id === feedInfo.id}
|
||||||
switchFeed('relay', { relay })
|
onSelectChange={(select) => {
|
||||||
close?.()
|
if (!select) return
|
||||||
}}
|
switchFeed('relays', { activeRelaySetId: set.id })
|
||||||
>
|
close?.()
|
||||||
<div className="flex gap-2 items-center w-full">
|
}}
|
||||||
<RelayIcon url={relay} />
|
/>
|
||||||
<div className="flex-1 w-0 truncate">{simplifyUrl(relay)}</div>
|
))}
|
||||||
|
{favoriteRelays.map((relay) => (
|
||||||
|
<FeedSwitcherItem
|
||||||
|
key={relay}
|
||||||
|
isActive={feedInfo?.feedType === 'relay' && feedInfo.id === relay}
|
||||||
|
onClick={() => {
|
||||||
|
switchFeed('relay', { relay })
|
||||||
|
close?.()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex gap-3 items-center w-full">
|
||||||
|
<RelayIcon url={relay} className="shrink-0" />
|
||||||
|
<div className="flex-1 w-0 truncate">{simplifyUrl(relay)}</div>
|
||||||
|
</div>
|
||||||
|
</FeedSwitcherItem>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</FeedSwitcherItem>
|
</div>
|
||||||
))}
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SectionHeader({ title, action }: { title: string; action?: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-between items-center px-1 py-1">
|
||||||
|
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
{action}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -100,30 +132,29 @@ function FeedSwitcherItem({
|
|||||||
children,
|
children,
|
||||||
isActive,
|
isActive,
|
||||||
disabled,
|
disabled,
|
||||||
onClick,
|
onClick
|
||||||
controls
|
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
controls?: React.ReactNode
|
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full border rounded-lg p-4',
|
'group relative w-full border rounded-lg px-3 py-2.5 transition-all duration-200',
|
||||||
disabled && 'opacity-50 pointer-events-none',
|
disabled && 'opacity-50 pointer-events-none',
|
||||||
isActive ? 'border-primary bg-primary/5' : 'clickable'
|
isActive
|
||||||
|
? 'border-primary bg-primary/5 shadow-sm'
|
||||||
|
: 'border-border hover:border-primary/50 hover:bg-accent/50 clickable'
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (disabled) return
|
if (disabled) return
|
||||||
onClick()
|
onClick()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center gap-2">
|
||||||
<div className="font-semibold flex-1">{children}</div>
|
<div className="font-medium flex-1 min-w-0">{children}</div>
|
||||||
{controls}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export default function MailboxRelay({
|
|||||||
onClick={() => push(toRelay(mailboxRelay.url))}
|
onClick={() => push(toRelay(mailboxRelay.url))}
|
||||||
>
|
>
|
||||||
<RelayIcon url={mailboxRelay.url} />
|
<RelayIcon url={mailboxRelay.url} />
|
||||||
<div className="truncate">{mailboxRelay.url}</div>
|
<div className="truncate flex-1 w-0">{mailboxRelay.url}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
|
||||||
import { useFetchRelayInfo } from '@/hooks'
|
import { useFetchRelayInfo } from '@/hooks'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { Server } from 'lucide-react'
|
import { Server } from 'lucide-react'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
import Image from '../Image'
|
||||||
|
|
||||||
export default function RelayIcon({
|
export default function RelayIcon({
|
||||||
url,
|
url,
|
||||||
className,
|
className,
|
||||||
iconSize = 14
|
classNames
|
||||||
}: {
|
}: {
|
||||||
url?: string
|
url?: string
|
||||||
className?: string
|
className?: string
|
||||||
iconSize?: number
|
classNames?: {
|
||||||
|
fallback?: string
|
||||||
|
}
|
||||||
}) {
|
}) {
|
||||||
const { relayInfo } = useFetchRelayInfo(url)
|
const { relayInfo } = useFetchRelayInfo(url)
|
||||||
const iconUrl = useMemo(() => {
|
const iconUrl = useMemo(() => {
|
||||||
@@ -23,12 +25,21 @@ export default function RelayIcon({
|
|||||||
return `${u.protocol === 'wss:' ? 'https:' : 'http:'}//${u.host}/favicon.ico`
|
return `${u.protocol === 'wss:' ? 'https:' : 'http:'}//${u.host}/favicon.ico`
|
||||||
}, [url, relayInfo])
|
}, [url, relayInfo])
|
||||||
|
|
||||||
|
const fallback = <Server className={cn('size-5 bg-transparent', classNames?.fallback)} />
|
||||||
|
|
||||||
|
if (!iconUrl) {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Avatar className={cn('w-6 h-6', className)}>
|
<Image
|
||||||
<AvatarImage src={iconUrl} className="object-cover object-center" />
|
image={{ url: iconUrl, dim: { width: 20, height: 20 } }}
|
||||||
<AvatarFallback>
|
className={cn('size-6 rounded-full', className)}
|
||||||
<Server size={iconSize} />
|
classNames={{
|
||||||
</AvatarFallback>
|
skeleton: cn('size-6 rounded-full', className),
|
||||||
</Avatar>
|
errorPlaceholder: 'bg-transparent rounded-none shrink-0'
|
||||||
|
}}
|
||||||
|
errorPlaceholder={fallback}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { cn } from '@/lib/utils'
|
||||||
import { TRelaySet } from '@/types'
|
import { TRelaySet } from '@/types'
|
||||||
import { ChevronDown, FolderClosed } from 'lucide-react'
|
import { ChevronDown, FolderClosed } from 'lucide-react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
@@ -18,17 +19,22 @@ export default function RelaySetCard({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`w-full border rounded-lg p-4 clickable ${select ? 'border-primary bg-primary/5' : ''}`}
|
className={cn(
|
||||||
|
'group relative w-full border rounded-lg px-3 py-2.5 transition-all duration-200',
|
||||||
|
select
|
||||||
|
? 'border-primary bg-primary/5 shadow-sm'
|
||||||
|
: 'border-border hover:border-primary/50 hover:bg-accent/50 clickable'
|
||||||
|
)}
|
||||||
onClick={() => onSelectChange(!select)}
|
onClick={() => onSelectChange(!select)}
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center gap-2">
|
||||||
<div className="flex space-x-2 items-center cursor-pointer">
|
<div className="flex gap-3 items-center flex-1 min-w-0">
|
||||||
<div className="flex justify-center items-center w-6 h-6 shrink-0">
|
<div className="flex justify-center items-center size-6 shrink-0">
|
||||||
<FolderClosed className="size-4" />
|
<FolderClosed className="size-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="h-8 font-semibold flex items-center select-none">{relaySet.name}</div>
|
<div className="font-medium select-none truncate">{relaySet.name}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1 items-center shrink-0">
|
||||||
<RelayUrlsExpandToggle expand={expand} onExpandChange={setExpand}>
|
<RelayUrlsExpandToggle expand={expand} onExpandChange={setExpand}>
|
||||||
{t('n relays', { n: relaySet.relayUrls.length })}
|
{t('n relays', { n: relaySet.relayUrls.length })}
|
||||||
</RelayUrlsExpandToggle>
|
</RelayUrlsExpandToggle>
|
||||||
@@ -50,16 +56,16 @@ function RelayUrlsExpandToggle({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="text-sm text-muted-foreground flex items-center gap-1 cursor-pointer hover:text-foreground"
|
className="text-xs text-muted-foreground flex items-center gap-0.5 cursor-pointer hover:text-foreground transition-colors"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onExpandChange(!expand)
|
onExpandChange(!expand)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="select-none">{children}</div>
|
<div className="select-none font-medium">{children}</div>
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
size={16}
|
size={14}
|
||||||
className={`transition-transform duration-200 ${expand ? 'rotate-180' : ''}`}
|
className={cn('transition-transform duration-200', expand && 'rotate-180')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -69,11 +75,11 @@ function RelayUrls({ urls }: { urls: string[] }) {
|
|||||||
if (!urls) return null
|
if (!urls) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pl-1 space-y-1">
|
<div className="mt-2.5 pt-2.5 border-t space-y-1.5">
|
||||||
{urls.map((url) => (
|
{urls.map((url) => (
|
||||||
<div key={url} className="flex items-center gap-3">
|
<div key={url} className="flex items-center gap-2.5 pl-1">
|
||||||
<RelayIcon url={url} className="w-4 h-4" iconSize={10} />
|
<RelayIcon url={url} className="size-4 shrink-0" classNames={{ fallback: 'size-3' }} />
|
||||||
<div className="text-muted-foreground text-sm truncate">{url}</div>
|
<div className="text-muted-foreground text-xs truncate">{url}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -581,6 +581,8 @@ export default {
|
|||||||
'Show directly': 'Show directly',
|
'Show directly': 'Show directly',
|
||||||
'Click to view': 'Click to view',
|
'Click to view': 'Click to view',
|
||||||
'Special Follow': 'متابعة خاصة',
|
'Special Follow': 'متابعة خاصة',
|
||||||
'Unfollow Special': 'إلغاء المتابعة الخاصة'
|
'Unfollow Special': 'إلغاء المتابعة الخاصة',
|
||||||
|
'Personal Feeds': 'التدفقات الشخصية',
|
||||||
|
'Relay Feeds': 'تدفقات الترحيل'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -597,6 +597,8 @@ export default {
|
|||||||
'Show directly': 'Show directly',
|
'Show directly': 'Show directly',
|
||||||
'Click to view': 'Click to view',
|
'Click to view': 'Click to view',
|
||||||
'Special Follow': 'Besonders Folgen',
|
'Special Follow': 'Besonders Folgen',
|
||||||
'Unfollow Special': 'Besonders Entfolgen'
|
'Unfollow Special': 'Besonders Entfolgen',
|
||||||
|
'Personal Feeds': 'Persönliche Feeds',
|
||||||
|
'Relay Feeds': 'Relay-Feeds'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -584,6 +584,8 @@ export default {
|
|||||||
'Show directly': 'Show directly',
|
'Show directly': 'Show directly',
|
||||||
'Click to view': 'Click to view',
|
'Click to view': 'Click to view',
|
||||||
'Special Follow': 'Special Follow',
|
'Special Follow': 'Special Follow',
|
||||||
'Unfollow Special': 'Unfollow Special'
|
'Unfollow Special': 'Unfollow Special',
|
||||||
|
'Personal Feeds': 'Personal Feeds',
|
||||||
|
'Relay Feeds': 'Relay Feeds'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -593,6 +593,8 @@ export default {
|
|||||||
'Show directly': 'Show directly',
|
'Show directly': 'Show directly',
|
||||||
'Click to view': 'Click to view',
|
'Click to view': 'Click to view',
|
||||||
'Special Follow': 'Seguir Especial',
|
'Special Follow': 'Seguir Especial',
|
||||||
'Unfollow Special': 'Dejar de Seguir Especial'
|
'Unfollow Special': 'Dejar de Seguir Especial',
|
||||||
|
'Personal Feeds': 'Feeds Personales',
|
||||||
|
'Relay Feeds': 'Feeds de Relays'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -587,6 +587,8 @@ export default {
|
|||||||
'Show directly': 'Show directly',
|
'Show directly': 'Show directly',
|
||||||
'Click to view': 'Click to view',
|
'Click to view': 'Click to view',
|
||||||
'Special Follow': 'دنبال کردن ویژه',
|
'Special Follow': 'دنبال کردن ویژه',
|
||||||
'Unfollow Special': 'لغو دنبال کردن ویژه'
|
'Unfollow Special': 'لغو دنبال کردن ویژه',
|
||||||
|
'Personal Feeds': 'فیدهای شخصی',
|
||||||
|
'Relay Feeds': 'فیدهای رله'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -596,6 +596,8 @@ export default {
|
|||||||
'Show directly': 'Show directly',
|
'Show directly': 'Show directly',
|
||||||
'Click to view': 'Click to view',
|
'Click to view': 'Click to view',
|
||||||
'Special Follow': 'Suivre Spécial',
|
'Special Follow': 'Suivre Spécial',
|
||||||
'Unfollow Special': 'Ne Plus Suivre Spécial'
|
'Unfollow Special': 'Ne Plus Suivre Spécial',
|
||||||
|
'Personal Feeds': 'Flux Personnels',
|
||||||
|
'Relay Feeds': 'Flux de Relais'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -588,6 +588,8 @@ export default {
|
|||||||
'Show directly': 'Show directly',
|
'Show directly': 'Show directly',
|
||||||
'Click to view': 'Click to view',
|
'Click to view': 'Click to view',
|
||||||
'Special Follow': 'विशेष फ़ॉलो',
|
'Special Follow': 'विशेष फ़ॉलो',
|
||||||
'Unfollow Special': 'विशेष अनफ़ॉलो'
|
'Unfollow Special': 'विशेष अनफ़ॉलो',
|
||||||
|
'Personal Feeds': 'व्यक्तिगत फ़ीड',
|
||||||
|
'Relay Feeds': 'रिले फ़ीड'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -582,6 +582,8 @@ export default {
|
|||||||
'Show directly': 'Show directly',
|
'Show directly': 'Show directly',
|
||||||
'Click to view': 'Click to view',
|
'Click to view': 'Click to view',
|
||||||
'Special Follow': 'Különleges Követés',
|
'Special Follow': 'Különleges Követés',
|
||||||
'Unfollow Special': 'Különleges Követés Megszüntetése'
|
'Unfollow Special': 'Különleges Követés Megszüntetése',
|
||||||
|
'Personal Feeds': 'Személyes Feedek',
|
||||||
|
'Relay Feeds': 'Relay Feedek'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -592,6 +592,8 @@ export default {
|
|||||||
'Show directly': 'Show directly',
|
'Show directly': 'Show directly',
|
||||||
'Click to view': 'Click to view',
|
'Click to view': 'Click to view',
|
||||||
'Special Follow': 'Segui Speciale',
|
'Special Follow': 'Segui Speciale',
|
||||||
'Unfollow Special': 'Smetti di Seguire Speciale'
|
'Unfollow Special': 'Smetti di Seguire Speciale',
|
||||||
|
'Personal Feeds': 'Feed Personali',
|
||||||
|
'Relay Feeds': 'Feed di Relay'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -587,6 +587,8 @@ export default {
|
|||||||
'Show directly': '直接表示',
|
'Show directly': '直接表示',
|
||||||
'Click to view': 'クリックして表示',
|
'Click to view': 'クリックして表示',
|
||||||
'Special Follow': '特別フォロー',
|
'Special Follow': '特別フォロー',
|
||||||
'Unfollow Special': '特別フォロー解除'
|
'Unfollow Special': '特別フォロー解除',
|
||||||
|
'Personal Feeds': '個人フィード',
|
||||||
|
'Relay Feeds': 'リレーフィード'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -586,6 +586,8 @@ export default {
|
|||||||
'Show directly': 'Show directly',
|
'Show directly': 'Show directly',
|
||||||
'Click to view': 'Click to view',
|
'Click to view': 'Click to view',
|
||||||
'Special Follow': '특별 팔로우',
|
'Special Follow': '특별 팔로우',
|
||||||
'Unfollow Special': '특별 팔로우 해제'
|
'Unfollow Special': '특별 팔로우 해제',
|
||||||
|
'Personal Feeds': '개인 피드',
|
||||||
|
'Relay Feeds': '릴레이 피드'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -593,6 +593,8 @@ export default {
|
|||||||
'Show directly': 'Pokaż bezpośrednio',
|
'Show directly': 'Pokaż bezpośrednio',
|
||||||
'Click to view': 'Wyświetl',
|
'Click to view': 'Wyświetl',
|
||||||
'Special Follow': 'Specjalne Śledzenie',
|
'Special Follow': 'Specjalne Śledzenie',
|
||||||
'Unfollow Special': 'Cofnij Specjalne Śledzenie'
|
'Unfollow Special': 'Cofnij Specjalne Śledzenie',
|
||||||
|
'Personal Feeds': 'Osobiste Kanały',
|
||||||
|
'Relay Feeds': 'Kanały Przekaźników'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -588,6 +588,8 @@ export default {
|
|||||||
'Show directly': 'Show directly',
|
'Show directly': 'Show directly',
|
||||||
'Click to view': 'Click to view',
|
'Click to view': 'Click to view',
|
||||||
'Special Follow': 'Seguir Especial',
|
'Special Follow': 'Seguir Especial',
|
||||||
'Unfollow Special': 'Deixar de Seguir Especial'
|
'Unfollow Special': 'Deixar de Seguir Especial',
|
||||||
|
'Personal Feeds': 'Feeds Pessoais',
|
||||||
|
'Relay Feeds': 'Feeds de Relays'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -591,6 +591,8 @@ export default {
|
|||||||
'Show directly': 'Show directly',
|
'Show directly': 'Show directly',
|
||||||
'Click to view': 'Click to view',
|
'Click to view': 'Click to view',
|
||||||
'Special Follow': 'Seguir Especial',
|
'Special Follow': 'Seguir Especial',
|
||||||
'Unfollow Special': 'Deixar de Seguir Especial'
|
'Unfollow Special': 'Deixar de Seguir Especial',
|
||||||
|
'Personal Feeds': 'Feeds Pessoais',
|
||||||
|
'Relay Feeds': 'Feeds de Relays'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -593,6 +593,8 @@ export default {
|
|||||||
'Show directly': 'Show directly',
|
'Show directly': 'Show directly',
|
||||||
'Click to view': 'Click to view',
|
'Click to view': 'Click to view',
|
||||||
'Special Follow': 'Особая Подписка',
|
'Special Follow': 'Особая Подписка',
|
||||||
'Unfollow Special': 'Отменить Особую Подписку'
|
'Unfollow Special': 'Отменить Особую Подписку',
|
||||||
|
'Personal Feeds': 'Личные Ленты',
|
||||||
|
'Relay Feeds': 'Ленты Релеев'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -580,6 +580,8 @@ export default {
|
|||||||
'Show directly': 'Show directly',
|
'Show directly': 'Show directly',
|
||||||
'Click to view': 'Click to view',
|
'Click to view': 'Click to view',
|
||||||
'Special Follow': 'ติดตามพิเศษ',
|
'Special Follow': 'ติดตามพิเศษ',
|
||||||
'Unfollow Special': 'ยกเลิกติดตามพิเศษ'
|
'Unfollow Special': 'ยกเลิกติดตามพิเศษ',
|
||||||
|
'Personal Feeds': 'ฟีดส่วนตัว',
|
||||||
|
'Relay Feeds': 'ฟีดรีเลย์'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -573,6 +573,8 @@ export default {
|
|||||||
'Show directly': '直接显示',
|
'Show directly': '直接显示',
|
||||||
'Click to view': '点击查看',
|
'Click to view': '点击查看',
|
||||||
'Special Follow': '特别关注',
|
'Special Follow': '特别关注',
|
||||||
'Unfollow Special': '取消特别关注'
|
'Unfollow Special': '取消特别关注',
|
||||||
|
'Personal Feeds': '个人订阅',
|
||||||
|
'Relay Feeds': '中继订阅'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import FeedSwitcher from '@/components/FeedSwitcher'
|
import FeedSwitcher from '@/components/FeedSwitcher'
|
||||||
|
import RelayIcon from '@/components/RelayIcon'
|
||||||
import { Drawer, DrawerContent } from '@/components/ui/drawer'
|
import { Drawer, DrawerContent } from '@/components/ui/drawer'
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||||
import { simplifyUrl } from '@/lib/url'
|
import { simplifyUrl } from '@/lib/url'
|
||||||
@@ -19,10 +20,13 @@ export default function FeedButton({ className }: { className?: string }) {
|
|||||||
<>
|
<>
|
||||||
<FeedSwitcherTrigger className={className} onClick={() => setOpen(true)} />
|
<FeedSwitcherTrigger className={className} onClick={() => setOpen(true)} />
|
||||||
<Drawer open={open} onOpenChange={setOpen}>
|
<Drawer open={open} onOpenChange={setOpen}>
|
||||||
<DrawerContent className="max-h-[80vh]">
|
<DrawerContent className="max-h-[85vh]">
|
||||||
<div
|
<div
|
||||||
className="overflow-y-auto overscroll-contain py-2 px-4"
|
className="flex-1 overflow-y-auto overscroll-contain py-3 px-4"
|
||||||
style={{ touchAction: 'pan-y' }}
|
style={{
|
||||||
|
touchAction: 'pan-y',
|
||||||
|
WebkitOverflowScrolling: 'touch'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<FeedSwitcher close={() => setOpen(false)} />
|
<FeedSwitcher close={() => setOpen(false)} />
|
||||||
</div>
|
</div>
|
||||||
@@ -37,12 +41,14 @@ export default function FeedButton({ className }: { className?: string }) {
|
|||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<FeedSwitcherTrigger className={className} />
|
<FeedSwitcherTrigger className={className} />
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent
|
<PopoverContent sideOffset={0} side="bottom" className="w-[400px] p-0 overflow-hidden">
|
||||||
sideOffset={0}
|
<div
|
||||||
side="bottom"
|
className="max-h-[calc(100vh-16rem)] overflow-y-auto overscroll-contain py-3 px-4"
|
||||||
className="w-96 p-4 max-h-[80vh] overflow-auto scrollbar-hide"
|
onWheel={(e) => e.stopPropagation()}
|
||||||
>
|
onTouchMove={(e) => e.stopPropagation()}
|
||||||
<FeedSwitcher close={() => setOpen(false)} />
|
>
|
||||||
|
<FeedSwitcher close={() => setOpen(false)} />
|
||||||
|
</div>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
)
|
)
|
||||||
@@ -79,6 +85,10 @@ const FeedSwitcherTrigger = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEle
|
|||||||
const icon = useMemo(() => {
|
const icon = useMemo(() => {
|
||||||
if (feedInfo?.feedType === 'following') return <UsersRound />
|
if (feedInfo?.feedType === 'following') return <UsersRound />
|
||||||
if (feedInfo?.feedType === 'pinned') return <Star />
|
if (feedInfo?.feedType === 'pinned') return <Star />
|
||||||
|
if (feedInfo?.feedType === 'relay' && feedInfo.id) {
|
||||||
|
return <RelayIcon url={feedInfo.id} />
|
||||||
|
}
|
||||||
|
|
||||||
return <Server />
|
return <Server />
|
||||||
}, [feedInfo])
|
}, [feedInfo])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user