feat: add relay selector for posting
This commit is contained in:
@@ -2,22 +2,12 @@ import { getNoteBech32Id, isProtectedEvent } from '@/lib/event'
|
|||||||
import { toNjump } from '@/lib/link'
|
import { toNjump } from '@/lib/link'
|
||||||
import { pubkeyToNpub } from '@/lib/pubkey'
|
import { pubkeyToNpub } from '@/lib/pubkey'
|
||||||
import { simplifyUrl } from '@/lib/url'
|
import { simplifyUrl } from '@/lib/url'
|
||||||
|
import { useCurrentRelays } from '@/providers/CurrentRelaysProvider'
|
||||||
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
|
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
|
||||||
import { useMuteList } from '@/providers/MuteListProvider'
|
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 {
|
import { Bell, BellOff, Code, Copy, Link, SatelliteDish, Trash2, TriangleAlert } from 'lucide-react'
|
||||||
Bell,
|
|
||||||
BellOff,
|
|
||||||
Code,
|
|
||||||
Copy,
|
|
||||||
Link,
|
|
||||||
Mail,
|
|
||||||
SatelliteDish,
|
|
||||||
Server,
|
|
||||||
Trash2,
|
|
||||||
TriangleAlert
|
|
||||||
} from 'lucide-react'
|
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@@ -59,7 +49,11 @@ export function useMenuActions({
|
|||||||
}: UseMenuActionsProps) {
|
}: UseMenuActionsProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { pubkey, attemptDelete } = useNostr()
|
const { pubkey, attemptDelete } = useNostr()
|
||||||
|
const { relayUrls: currentBrowsingRelayUrls } = useCurrentRelays()
|
||||||
const { relaySets, favoriteRelays } = useFavoriteRelays()
|
const { relaySets, favoriteRelays } = useFavoriteRelays()
|
||||||
|
const relayUrls = useMemo(() => {
|
||||||
|
return Array.from(new Set(currentBrowsingRelayUrls.concat(favoriteRelays)))
|
||||||
|
}, [currentBrowsingRelayUrls, favoriteRelays])
|
||||||
const { mutePubkeyPublicly, mutePubkeyPrivately, unmutePubkey, mutePubkeySet } = useMuteList()
|
const { mutePubkeyPublicly, mutePubkeyPrivately, unmutePubkey, mutePubkeySet } = useMuteList()
|
||||||
const isMuted = useMemo(() => mutePubkeySet.has(event.pubkey), [mutePubkeySet, event])
|
const isMuted = useMemo(() => mutePubkeySet.has(event.pubkey), [mutePubkeySet, event])
|
||||||
|
|
||||||
@@ -67,12 +61,7 @@ export function useMenuActions({
|
|||||||
const items = []
|
const items = []
|
||||||
if (pubkey && event.pubkey === pubkey) {
|
if (pubkey && event.pubkey === pubkey) {
|
||||||
items.push({
|
items.push({
|
||||||
label: (
|
label: <div className="text-left"> {t('Write relays')}</div>,
|
||||||
<div className="flex items-center gap-2 w-full pl-1">
|
|
||||||
<Mail />
|
|
||||||
<div className="flex-1 truncate text-left">{t('Suitable Relays')}</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
closeDrawer()
|
closeDrawer()
|
||||||
const relays = await client.determineTargetRelays(event)
|
const relays = await client.determineTargetRelays(event)
|
||||||
@@ -97,12 +86,7 @@ export function useMenuActions({
|
|||||||
...relaySets
|
...relaySets
|
||||||
.filter((set) => set.relayUrls.length)
|
.filter((set) => set.relayUrls.length)
|
||||||
.map((set, index) => ({
|
.map((set, index) => ({
|
||||||
label: (
|
label: <div className="text-left truncate">{set.name}</div>,
|
||||||
<div className="flex items-center gap-2 w-full pl-1">
|
|
||||||
<Server />
|
|
||||||
<div className="flex-1 truncate text-left">{set.name}</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
closeDrawer()
|
closeDrawer()
|
||||||
await client
|
await client
|
||||||
@@ -126,9 +110,9 @@ export function useMenuActions({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (favoriteRelays.length) {
|
if (relayUrls.length) {
|
||||||
items.push(
|
items.push(
|
||||||
...favoriteRelays.map((relay, index) => ({
|
...relayUrls.map((relay, index) => ({
|
||||||
label: (
|
label: (
|
||||||
<div className="flex items-center gap-2 w-full">
|
<div className="flex items-center gap-2 w-full">
|
||||||
<RelayIcon url={relay} />
|
<RelayIcon url={relay} />
|
||||||
@@ -159,7 +143,7 @@ export function useMenuActions({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}, [pubkey, favoriteRelays, relaySets])
|
}, [pubkey, relayUrls, relaySets])
|
||||||
|
|
||||||
const menuActions: MenuAction[] = useMemo(() => {
|
const menuActions: MenuAction[] = useMemo(() => {
|
||||||
const actions: MenuAction[] = [
|
const actions: MenuAction[] = [
|
||||||
|
|||||||
@@ -14,15 +14,15 @@ import postEditorCache from '@/services/post-editor-cache.service'
|
|||||||
import { TPollCreateData } from '@/types'
|
import { TPollCreateData } from '@/types'
|
||||||
import { ImageUp, ListTodo, LoaderCircle, Settings, Smile, X } from 'lucide-react'
|
import { ImageUp, ListTodo, LoaderCircle, Settings, Smile, X } from 'lucide-react'
|
||||||
import { Event, kinds } from 'nostr-tools'
|
import { Event, kinds } from 'nostr-tools'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import EmojiPickerDialog from '../EmojiPickerDialog'
|
import EmojiPickerDialog from '../EmojiPickerDialog'
|
||||||
import Mentions from './Mentions'
|
import Mentions from './Mentions'
|
||||||
import PollEditor from './PollEditor'
|
import PollEditor from './PollEditor'
|
||||||
import PostOptions from './PostOptions'
|
import PostOptions from './PostOptions'
|
||||||
|
import PostRelaySelector from './PostRelaySelector'
|
||||||
import PostTextarea, { TPostTextareaHandle } from './PostTextarea'
|
import PostTextarea, { TPostTextareaHandle } from './PostTextarea'
|
||||||
import SendOnlyToSwitch from './SendOnlyToSwitch'
|
|
||||||
import Uploader from './Uploader'
|
import Uploader from './Uploader'
|
||||||
|
|
||||||
export default function PostContent({
|
export default function PostContent({
|
||||||
@@ -47,10 +47,11 @@ export default function PostContent({
|
|||||||
>([])
|
>([])
|
||||||
const [showMoreOptions, setShowMoreOptions] = useState(false)
|
const [showMoreOptions, setShowMoreOptions] = useState(false)
|
||||||
const [addClientTag, setAddClientTag] = useState(false)
|
const [addClientTag, setAddClientTag] = useState(false)
|
||||||
const [specifiedRelayUrls, setSpecifiedRelayUrls] = useState<string[] | undefined>(undefined)
|
|
||||||
const [mentions, setMentions] = useState<string[]>([])
|
const [mentions, setMentions] = useState<string[]>([])
|
||||||
const [isNsfw, setIsNsfw] = useState(false)
|
const [isNsfw, setIsNsfw] = useState(false)
|
||||||
const [isPoll, setIsPoll] = useState(false)
|
const [isPoll, setIsPoll] = useState(false)
|
||||||
|
const [isProtectedEvent, setIsProtectedEvent] = useState(false)
|
||||||
|
const [additionalRelayUrls, setAdditionalRelayUrls] = useState<string[]>([])
|
||||||
const [pollCreateData, setPollCreateData] = useState<TPollCreateData>({
|
const [pollCreateData, setPollCreateData] = useState<TPollCreateData>({
|
||||||
isMultipleChoice: false,
|
isMultipleChoice: false,
|
||||||
options: ['', ''],
|
options: ['', ''],
|
||||||
@@ -59,12 +60,25 @@ export default function PostContent({
|
|||||||
})
|
})
|
||||||
const [minPow, setMinPow] = useState(0)
|
const [minPow, setMinPow] = useState(0)
|
||||||
const isFirstRender = useRef(true)
|
const isFirstRender = useRef(true)
|
||||||
const canPost =
|
const canPost = useMemo(() => {
|
||||||
|
return (
|
||||||
!!pubkey &&
|
!!pubkey &&
|
||||||
!!text &&
|
!!text &&
|
||||||
!posting &&
|
!posting &&
|
||||||
!uploadProgresses.length &&
|
!uploadProgresses.length &&
|
||||||
(!isPoll || pollCreateData.options.filter((option) => !!option.trim()).length >= 2)
|
(!isPoll || pollCreateData.options.filter((option) => !!option.trim()).length >= 2) &&
|
||||||
|
(!isProtectedEvent || additionalRelayUrls.length > 0)
|
||||||
|
)
|
||||||
|
}, [
|
||||||
|
pubkey,
|
||||||
|
text,
|
||||||
|
posting,
|
||||||
|
uploadProgresses,
|
||||||
|
isPoll,
|
||||||
|
pollCreateData,
|
||||||
|
isProtectedEvent,
|
||||||
|
additionalRelayUrls
|
||||||
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isFirstRender.current) {
|
if (isFirstRender.current) {
|
||||||
@@ -97,15 +111,7 @@ export default function PostContent({
|
|||||||
addClientTag
|
addClientTag
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}, [
|
}, [defaultContent, parentEvent, isNsfw, isPoll, pollCreateData, addClientTag])
|
||||||
defaultContent,
|
|
||||||
parentEvent,
|
|
||||||
isNsfw,
|
|
||||||
isPoll,
|
|
||||||
pollCreateData,
|
|
||||||
specifiedRelayUrls,
|
|
||||||
addClientTag
|
|
||||||
])
|
|
||||||
|
|
||||||
const post = async (e?: React.MouseEvent) => {
|
const post = async (e?: React.MouseEvent) => {
|
||||||
e?.stopPropagation()
|
e?.stopPropagation()
|
||||||
@@ -118,24 +124,24 @@ export default function PostContent({
|
|||||||
parentEvent && parentEvent.kind !== kinds.ShortTextNote
|
parentEvent && parentEvent.kind !== kinds.ShortTextNote
|
||||||
? await createCommentDraftEvent(text, parentEvent, mentions, {
|
? await createCommentDraftEvent(text, parentEvent, mentions, {
|
||||||
addClientTag,
|
addClientTag,
|
||||||
protectedEvent: !!specifiedRelayUrls,
|
protectedEvent: isProtectedEvent,
|
||||||
isNsfw
|
isNsfw
|
||||||
})
|
})
|
||||||
: isPoll
|
: isPoll
|
||||||
? await createPollDraftEvent(pubkey, text, mentions, pollCreateData, {
|
? await createPollDraftEvent(pubkey!, text, mentions, pollCreateData, {
|
||||||
addClientTag,
|
addClientTag,
|
||||||
isNsfw
|
isNsfw
|
||||||
})
|
})
|
||||||
: await createShortTextNoteDraftEvent(text, mentions, {
|
: await createShortTextNoteDraftEvent(text, mentions, {
|
||||||
parentEvent,
|
parentEvent,
|
||||||
addClientTag,
|
addClientTag,
|
||||||
protectedEvent: !!specifiedRelayUrls,
|
protectedEvent: isProtectedEvent,
|
||||||
isNsfw
|
isNsfw
|
||||||
})
|
})
|
||||||
|
|
||||||
const newEvent = await publish(draftEvent, {
|
const newEvent = await publish(draftEvent, {
|
||||||
specifiedRelayUrls,
|
specifiedRelayUrls: isProtectedEvent ? additionalRelayUrls : undefined,
|
||||||
additionalRelayUrls: isPoll ? pollCreateData.relays : [],
|
additionalRelayUrls: isPoll ? pollCreateData.relays : additionalRelayUrls,
|
||||||
minPow
|
minPow
|
||||||
})
|
})
|
||||||
postEditorCache.clearPostCache({ defaultContent, parentEvent })
|
postEditorCache.clearPostCache({ defaultContent, parentEvent })
|
||||||
@@ -233,10 +239,10 @@ export default function PostContent({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{!isPoll && (
|
{!isPoll && (
|
||||||
<SendOnlyToSwitch
|
<PostRelaySelector
|
||||||
|
setIsProtectedEvent={setIsProtectedEvent}
|
||||||
|
setAdditionalRelayUrls={setAdditionalRelayUrls}
|
||||||
parentEvent={parentEvent}
|
parentEvent={parentEvent}
|
||||||
specifiedRelayUrls={specifiedRelayUrls}
|
|
||||||
setSpecifiedRelayUrls={setSpecifiedRelayUrls}
|
|
||||||
openFrom={openFrom}
|
openFrom={openFrom}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
209
src/components/PostEditor/PostRelaySelector.tsx
Normal file
209
src/components/PostEditor/PostRelaySelector.tsx
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger
|
||||||
|
} from '@/components/ui/dropdown-menu'
|
||||||
|
import { isProtectedEvent } from '@/lib/event'
|
||||||
|
import { simplifyUrl } from '@/lib/url'
|
||||||
|
import { useCurrentRelays } from '@/providers/CurrentRelaysProvider'
|
||||||
|
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
|
||||||
|
import client from '@/services/client.service'
|
||||||
|
import { NostrEvent } from 'nostr-tools'
|
||||||
|
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import RelayIcon from '../RelayIcon'
|
||||||
|
|
||||||
|
type TPostTargetItem =
|
||||||
|
| {
|
||||||
|
type: 'writeRelays'
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'relay'
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'relaySet'
|
||||||
|
id: string
|
||||||
|
urls: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PostRelaySelector({
|
||||||
|
parentEvent,
|
||||||
|
openFrom,
|
||||||
|
setIsProtectedEvent,
|
||||||
|
setAdditionalRelayUrls
|
||||||
|
}: {
|
||||||
|
parentEvent?: NostrEvent
|
||||||
|
openFrom?: string[]
|
||||||
|
setIsProtectedEvent: Dispatch<SetStateAction<boolean>>
|
||||||
|
setAdditionalRelayUrls: Dispatch<SetStateAction<string[]>>
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { relayUrls } = useCurrentRelays()
|
||||||
|
const { relaySets, favoriteRelays } = useFavoriteRelays()
|
||||||
|
const [postTargetItems, setPostTargetItems] = useState<TPostTargetItem[]>([])
|
||||||
|
const parentEventSeenOnRelays = useMemo(() => {
|
||||||
|
if (!parentEvent || !isProtectedEvent(parentEvent)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return client.getSeenEventRelayUrls(parentEvent.id)
|
||||||
|
}, [parentEvent])
|
||||||
|
const selectableRelays = useMemo(() => {
|
||||||
|
return Array.from(new Set(parentEventSeenOnRelays.concat(relayUrls).concat(favoriteRelays)))
|
||||||
|
}, [parentEventSeenOnRelays, relayUrls, favoriteRelays])
|
||||||
|
const description = useMemo(() => {
|
||||||
|
if (postTargetItems.length === 0) {
|
||||||
|
return t('No relays selected')
|
||||||
|
}
|
||||||
|
if (postTargetItems.length === 1) {
|
||||||
|
const item = postTargetItems[0]
|
||||||
|
if (item.type === 'writeRelays') {
|
||||||
|
return t('Write relays')
|
||||||
|
}
|
||||||
|
if (item.type === 'relay') {
|
||||||
|
return simplifyUrl(item.url)
|
||||||
|
}
|
||||||
|
if (item.type === 'relaySet') {
|
||||||
|
return item.urls.length > 1
|
||||||
|
? t('{{count}} relays', { count: item.urls.length })
|
||||||
|
: simplifyUrl(item.urls[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const hasWriteRelays = postTargetItems.some((item) => item.type === 'writeRelays')
|
||||||
|
const relayCount = postTargetItems.reduce((count, item) => {
|
||||||
|
if (item.type === 'relay') {
|
||||||
|
return count + 1
|
||||||
|
}
|
||||||
|
if (item.type === 'relaySet') {
|
||||||
|
return count + item.urls.length
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}, 0)
|
||||||
|
if (hasWriteRelays) {
|
||||||
|
return t('Write relays and {{count}} other relays', { count: relayCount })
|
||||||
|
}
|
||||||
|
return t('{{count}} relays', { count: relayCount })
|
||||||
|
}, [postTargetItems])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (openFrom && openFrom.length) {
|
||||||
|
setPostTargetItems(Array.from(new Set(openFrom)).map((url) => ({ type: 'relay', url })))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (parentEventSeenOnRelays && parentEventSeenOnRelays.length) {
|
||||||
|
setPostTargetItems(parentEventSeenOnRelays.map((url) => ({ type: 'relay', url })))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setPostTargetItems([{ type: 'writeRelays' }])
|
||||||
|
}, [openFrom, parentEventSeenOnRelays])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const isProtectedEvent = postTargetItems.every((item) => item.type !== 'writeRelays')
|
||||||
|
const relayUrls = postTargetItems.flatMap((item) => {
|
||||||
|
if (item.type === 'relay') {
|
||||||
|
return [item.url]
|
||||||
|
}
|
||||||
|
if (item.type === 'relaySet') {
|
||||||
|
return item.urls
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
|
||||||
|
setIsProtectedEvent(isProtectedEvent)
|
||||||
|
setAdditionalRelayUrls(relayUrls)
|
||||||
|
}, [postTargetItems])
|
||||||
|
|
||||||
|
const handleWriteRelaysCheckedChange = useCallback((checked: boolean) => {
|
||||||
|
if (checked) {
|
||||||
|
setPostTargetItems((prev) => [...prev, { type: 'writeRelays' }])
|
||||||
|
} else {
|
||||||
|
setPostTargetItems((prev) => prev.filter((item) => item.type !== 'writeRelays'))
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleRelayCheckedChange = useCallback((checked: boolean, url: string) => {
|
||||||
|
if (checked) {
|
||||||
|
setPostTargetItems((prev) => [...prev, { type: 'relay', url }])
|
||||||
|
} else {
|
||||||
|
setPostTargetItems((prev) =>
|
||||||
|
prev.filter((item) => !(item.type === 'relay' && item.url === url))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleRelaySetCheckedChange = useCallback(
|
||||||
|
(checked: boolean, id: string, urls: string[]) => {
|
||||||
|
if (checked) {
|
||||||
|
setPostTargetItems((prev) => [...prev, { type: 'relaySet', id, urls }])
|
||||||
|
} else {
|
||||||
|
setPostTargetItems((prev) =>
|
||||||
|
prev.filter((item) => !(item.type === 'relaySet' && item.id === id))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{t('Post to')}
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" className="px-2">
|
||||||
|
{description}
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
</div>
|
||||||
|
<DropdownMenuContent align="start" className="max-w-96">
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
checked={postTargetItems.some((item) => item.type === 'writeRelays')}
|
||||||
|
onSelect={(e) => e.preventDefault()}
|
||||||
|
onCheckedChange={handleWriteRelaysCheckedChange}
|
||||||
|
>
|
||||||
|
{t('Write relays')}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
{relaySets.length > 0 && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
{relaySets
|
||||||
|
.filter(({ relayUrls }) => relayUrls.length)
|
||||||
|
.map(({ id, name, relayUrls }) => (
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
key={id}
|
||||||
|
checked={postTargetItems.some(
|
||||||
|
(item) => item.type === 'relaySet' && item.id === id
|
||||||
|
)}
|
||||||
|
onSelect={(e) => e.preventDefault()}
|
||||||
|
onCheckedChange={(checked) => handleRelaySetCheckedChange(checked, id, relayUrls)}
|
||||||
|
>
|
||||||
|
<div className="truncate">
|
||||||
|
{name} ({relayUrls.length})
|
||||||
|
</div>
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{selectableRelays.length > 0 && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
{selectableRelays.map((url) => (
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
key={url}
|
||||||
|
checked={postTargetItems.some((item) => item.type === 'relay' && item.url === url)}
|
||||||
|
onSelect={(e) => e.preventDefault()}
|
||||||
|
onCheckedChange={(checked) => handleRelayCheckedChange(checked, url)}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<RelayIcon url={url} />
|
||||||
|
<div className="truncate">{simplifyUrl(url)}</div>
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
import { Label } from '@/components/ui/label'
|
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
|
||||||
import { Switch } from '@/components/ui/switch'
|
|
||||||
import { isProtectedEvent } from '@/lib/event'
|
|
||||||
import { simplifyUrl } from '@/lib/url'
|
|
||||||
import { useCurrentRelays } from '@/providers/CurrentRelaysProvider'
|
|
||||||
import client from '@/services/client.service'
|
|
||||||
import { Info } from 'lucide-react'
|
|
||||||
import { Event } from 'nostr-tools'
|
|
||||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
|
|
||||||
export default function SendOnlyToSwitch({
|
|
||||||
parentEvent,
|
|
||||||
specifiedRelayUrls,
|
|
||||||
setSpecifiedRelayUrls,
|
|
||||||
openFrom
|
|
||||||
}: {
|
|
||||||
parentEvent?: Event
|
|
||||||
specifiedRelayUrls?: string[]
|
|
||||||
setSpecifiedRelayUrls: Dispatch<SetStateAction<string[] | undefined>>
|
|
||||||
openFrom?: string[]
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const { currentRelayUrls } = useCurrentRelays()
|
|
||||||
const [urls, setUrls] = useState<string[]>([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (openFrom?.length) {
|
|
||||||
setUrls(openFrom)
|
|
||||||
setSpecifiedRelayUrls(openFrom)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!parentEvent) {
|
|
||||||
setUrls(currentRelayUrls)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const isProtected = isProtectedEvent(parentEvent)
|
|
||||||
const seenOn = client.getSeenEventRelayUrls(parentEvent.id)
|
|
||||||
if (isProtected && seenOn.length) {
|
|
||||||
setSpecifiedRelayUrls(seenOn)
|
|
||||||
setUrls(seenOn)
|
|
||||||
} else {
|
|
||||||
setUrls(currentRelayUrls)
|
|
||||||
}
|
|
||||||
}, [parentEvent, currentRelayUrls, openFrom])
|
|
||||||
|
|
||||||
if (!urls.length) return null
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="flex items-center gap-1 truncate">
|
|
||||||
<Label htmlFor="send-only-to-current-relays" className="truncate">
|
|
||||||
{urls.length === 1
|
|
||||||
? t('Send only to r', { r: simplifyUrl(urls[0]) })
|
|
||||||
: t('Send only to these relays')}
|
|
||||||
</Label>
|
|
||||||
{urls.length > 1 && (
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger>
|
|
||||||
<Info size={14} />
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-fit text-sm">
|
|
||||||
{urls.map((url) => (
|
|
||||||
<div key={url}>{simplifyUrl(url)}</div>
|
|
||||||
))}
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
className="shrink-0"
|
|
||||||
id="send-only-to-current-relays"
|
|
||||||
checked={!!specifiedRelayUrls}
|
|
||||||
onCheckedChange={(checked) => setSpecifiedRelayUrls(checked ? urls : undefined)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -3,17 +3,28 @@ import RelayInfo from '@/components/RelayInfo'
|
|||||||
import SearchInput from '@/components/SearchInput'
|
import SearchInput from '@/components/SearchInput'
|
||||||
import { useFetchRelayInfo } from '@/hooks'
|
import { useFetchRelayInfo } from '@/hooks'
|
||||||
import { normalizeUrl } from '@/lib/url'
|
import { normalizeUrl } from '@/lib/url'
|
||||||
|
import { useCurrentRelays } from '@/providers/CurrentRelaysProvider'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import NotFound from '../NotFound'
|
import NotFound from '../NotFound'
|
||||||
|
|
||||||
export default function Relay({ url, className }: { url?: string; className?: string }) {
|
export default function Relay({ url, className }: { url?: string; className?: string }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { addRelayUrls, removeRelayUrls } = useCurrentRelays()
|
||||||
const normalizedUrl = useMemo(() => (url ? normalizeUrl(url) : undefined), [url])
|
const normalizedUrl = useMemo(() => (url ? normalizeUrl(url) : undefined), [url])
|
||||||
const { relayInfo } = useFetchRelayInfo(normalizedUrl)
|
const { relayInfo } = useFetchRelayInfo(normalizedUrl)
|
||||||
const [searchInput, setSearchInput] = useState('')
|
const [searchInput, setSearchInput] = useState('')
|
||||||
const [debouncedInput, setDebouncedInput] = useState(searchInput)
|
const [debouncedInput, setDebouncedInput] = useState(searchInput)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (normalizedUrl) {
|
||||||
|
addRelayUrls([normalizedUrl])
|
||||||
|
return () => {
|
||||||
|
removeRelayUrls([normalizedUrl])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [normalizedUrl])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = setTimeout(() => {
|
const handler = setTimeout(() => {
|
||||||
setDebouncedInput(searchInput)
|
setDebouncedInput(searchInput)
|
||||||
|
|||||||
@@ -415,6 +415,10 @@ export default {
|
|||||||
'Failed to review': 'فشل في المراجعة',
|
'Failed to review': 'فشل في المراجعة',
|
||||||
'Write a review and pick a star rating': 'اكتب مراجعة واختر تقييماً بالنجوم',
|
'Write a review and pick a star rating': 'اكتب مراجعة واختر تقييماً بالنجوم',
|
||||||
Submit: 'إرسال',
|
Submit: 'إرسال',
|
||||||
'Reviews for {{relay}}': 'مراجعات لـ {{relay}}'
|
'Reviews for {{relay}}': 'مراجعات لـ {{relay}}',
|
||||||
|
'No relays selected': 'لم يتم اختيار أي مرحل',
|
||||||
|
'Post to': 'نشر إلى',
|
||||||
|
'Write relays and {{count}} other relays': 'مرحلات الكتابة و {{count}} مرحل آخر',
|
||||||
|
'{{count}} relays': '{{count}} ريلايات'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -427,6 +427,10 @@ export default {
|
|||||||
'Write a review and pick a star rating':
|
'Write a review and pick a star rating':
|
||||||
'Schreiben Sie eine Bewertung und wählen Sie eine Sternebewertung',
|
'Schreiben Sie eine Bewertung und wählen Sie eine Sternebewertung',
|
||||||
Submit: 'Absenden',
|
Submit: 'Absenden',
|
||||||
'Reviews for {{relay}}': 'Bewertungen für {{relay}}'
|
'Reviews for {{relay}}': 'Bewertungen für {{relay}}',
|
||||||
|
'No relays selected': 'Keine Relays ausgewählt',
|
||||||
|
'Post to': 'Posten an',
|
||||||
|
'Write relays and {{count}} other relays': 'Schreib-Relays und {{count}} andere Relays',
|
||||||
|
'{{count}} relays': '{{count}} Relays'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -414,6 +414,10 @@ export default {
|
|||||||
'Failed to review': 'Failed to review',
|
'Failed to review': 'Failed to review',
|
||||||
'Write a review and pick a star rating': 'Write a review and pick a star rating',
|
'Write a review and pick a star rating': 'Write a review and pick a star rating',
|
||||||
Submit: 'Submit',
|
Submit: 'Submit',
|
||||||
'Reviews for {{relay}}': 'Reviews for {{relay}}'
|
'Reviews for {{relay}}': 'Reviews for {{relay}}',
|
||||||
|
'No relays selected': 'No relays selected',
|
||||||
|
'Post to': 'Post to',
|
||||||
|
'Write relays and {{count}} other relays': 'Write relays and {{count}} other relays',
|
||||||
|
'{{count}} relays': '{{count}} relays'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -422,6 +422,10 @@ export default {
|
|||||||
'Write a review and pick a star rating':
|
'Write a review and pick a star rating':
|
||||||
'Escriba una reseña y elija una calificación de estrellas',
|
'Escriba una reseña y elija una calificación de estrellas',
|
||||||
Submit: 'Enviar',
|
Submit: 'Enviar',
|
||||||
'Reviews for {{relay}}': 'Reseñas para {{relay}}'
|
'Reviews for {{relay}}': 'Reseñas para {{relay}}',
|
||||||
|
'No relays selected': 'No hay relés seleccionados',
|
||||||
|
'Post to': 'Publicar en',
|
||||||
|
'Write relays and {{count}} other relays': 'Relés de escritura y {{count}} otros relés',
|
||||||
|
'{{count}} relays': '{{count}} relés'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -417,6 +417,10 @@ export default {
|
|||||||
'Failed to review': 'نقد ناموفق',
|
'Failed to review': 'نقد ناموفق',
|
||||||
'Write a review and pick a star rating': 'نقدی بنویسید و امتیاز ستارهای انتخاب کنید',
|
'Write a review and pick a star rating': 'نقدی بنویسید و امتیاز ستارهای انتخاب کنید',
|
||||||
Submit: 'ارسال',
|
Submit: 'ارسال',
|
||||||
'Reviews for {{relay}}': 'نقدها برای {{relay}}'
|
'Reviews for {{relay}}': 'نقدها برای {{relay}}',
|
||||||
|
'No relays selected': 'هیچ رلهای انتخاب نشده',
|
||||||
|
'Post to': 'پست کردن به',
|
||||||
|
'Write relays and {{count}} other relays': 'رلههای نوشتن و {{count}} رله دیگر',
|
||||||
|
'{{count}} relays': '{{count}} رله'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -426,6 +426,10 @@ export default {
|
|||||||
'Failed to review': 'Échec de l’avis',
|
'Failed to review': 'Échec de l’avis',
|
||||||
'Write a review and pick a star rating': 'Écrivez un avis et choisissez une note en étoiles',
|
'Write a review and pick a star rating': 'Écrivez un avis et choisissez une note en étoiles',
|
||||||
Submit: 'Soumettre',
|
Submit: 'Soumettre',
|
||||||
'Reviews for {{relay}}': 'Avis pour {{relay}}'
|
'Reviews for {{relay}}': 'Avis pour {{relay}}',
|
||||||
|
'No relays selected': 'Aucun relais sélectionné',
|
||||||
|
'Post to': 'Publier sur',
|
||||||
|
'Write relays and {{count}} other relays': 'Relais d’écriture et {{count}} autres relais',
|
||||||
|
'{{count}} relays': '{{count}} relais'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -419,6 +419,10 @@ export default {
|
|||||||
'Failed to review': 'समीक्षा असफल',
|
'Failed to review': 'समीक्षा असफल',
|
||||||
'Write a review and pick a star rating': 'एक समीक्षा लिखें और स्टार रेटिंग चुनें',
|
'Write a review and pick a star rating': 'एक समीक्षा लिखें और स्टार रेटिंग चुनें',
|
||||||
Submit: 'सबमिट करें',
|
Submit: 'सबमिट करें',
|
||||||
'Reviews for {{relay}}': '{{relay}} के लिए समीक्षाएं'
|
'Reviews for {{relay}}': '{{relay}} के लिए समीक्षाएं',
|
||||||
|
'No relays selected': 'कोई रिले चयनित नहीं',
|
||||||
|
'Post to': 'पोस्ट करें',
|
||||||
|
'Write relays and {{count}} other relays': 'राइट रिले और {{count}} अन्य रिले',
|
||||||
|
'{{count}} relays': '{{count}} रिले'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -422,6 +422,10 @@ export default {
|
|||||||
'Write a review and pick a star rating':
|
'Write a review and pick a star rating':
|
||||||
'Scrivi una recensione e scegli una valutazione a stelle',
|
'Scrivi una recensione e scegli una valutazione a stelle',
|
||||||
Submit: 'Invia',
|
Submit: 'Invia',
|
||||||
'Reviews for {{relay}}': 'Recensioni per {{relay}}'
|
'Reviews for {{relay}}': 'Recensioni per {{relay}}',
|
||||||
|
'No relays selected': 'Nessun relay selezionato',
|
||||||
|
'Post to': 'Pubblica su',
|
||||||
|
'Write relays and {{count}} other relays': 'Relay di scrittura e {{count}} altri relay',
|
||||||
|
'{{count}} relays': '{{count}} relay'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -418,6 +418,10 @@ export default {
|
|||||||
'Failed to review': 'レビュー失敗',
|
'Failed to review': 'レビュー失敗',
|
||||||
'Write a review and pick a star rating': 'レビューを書いて星評価を選択してください',
|
'Write a review and pick a star rating': 'レビューを書いて星評価を選択してください',
|
||||||
Submit: '送信',
|
Submit: '送信',
|
||||||
'Reviews for {{relay}}': '{{relay}} のレビュー'
|
'Reviews for {{relay}}': '{{relay}} のレビュー',
|
||||||
|
'No relays selected': 'リレーが選択されていません',
|
||||||
|
'Post to': '投稿先',
|
||||||
|
'Write relays and {{count}} other relays': '書き込みリレーと他の {{count}} 個のリレー',
|
||||||
|
'{{count}} relays': '{{count}} 個のリレー'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -418,6 +418,10 @@ export default {
|
|||||||
'Failed to review': '리뷰 실패',
|
'Failed to review': '리뷰 실패',
|
||||||
'Write a review and pick a star rating': '리뷰를 작성하고 별점을 선택하세요',
|
'Write a review and pick a star rating': '리뷰를 작성하고 별점을 선택하세요',
|
||||||
Submit: '제출',
|
Submit: '제출',
|
||||||
'Reviews for {{relay}}': '{{relay}}에 대한 리뷰'
|
'Reviews for {{relay}}': '{{relay}}에 대한 리뷰',
|
||||||
|
'No relays selected': '선택된 릴레이가 없습니다',
|
||||||
|
'Post to': '게시 대상',
|
||||||
|
'Write relays and {{count}} other relays': '쓰기 릴레이 및 기타 {{count}}개 릴레이',
|
||||||
|
'{{count}} relays': '{{count}}개 릴레이'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -422,6 +422,10 @@ export default {
|
|||||||
'Failed to review': 'Błąd opinii',
|
'Failed to review': 'Błąd opinii',
|
||||||
'Write a review and pick a star rating': 'Napisz opinię i wybierz ocenę gwiazdkową',
|
'Write a review and pick a star rating': 'Napisz opinię i wybierz ocenę gwiazdkową',
|
||||||
Submit: 'Prześlij',
|
Submit: 'Prześlij',
|
||||||
'Reviews for {{relay}}': 'Opinie o {{relay}}'
|
'Reviews for {{relay}}': 'Opinie o {{relay}}',
|
||||||
|
'No relays selected': 'Nie wybrano przekaźników',
|
||||||
|
'Post to': 'Opublikuj na',
|
||||||
|
'Write relays and {{count}} other relays': 'Przekaźniki zapisu i {{count}} innych przekaźników',
|
||||||
|
'{{count}} relays': '{{count}} przekaźników'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -419,6 +419,10 @@ export default {
|
|||||||
'Write a review and pick a star rating':
|
'Write a review and pick a star rating':
|
||||||
'Escreva uma avaliação e escolha uma classificação por estrelas',
|
'Escreva uma avaliação e escolha uma classificação por estrelas',
|
||||||
Submit: 'Enviar',
|
Submit: 'Enviar',
|
||||||
'Reviews for {{relay}}': 'Avaliações para {{relay}}'
|
'Reviews for {{relay}}': 'Avaliações para {{relay}}',
|
||||||
|
'No relays selected': 'Nenhum relay selecionado',
|
||||||
|
'Post to': 'Postar para',
|
||||||
|
'Write relays and {{count}} other relays': 'Relays de escrita e {{count}} outros relays',
|
||||||
|
'{{count}} relays': '{{count}} relays'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -422,6 +422,10 @@ export default {
|
|||||||
'Write a review and pick a star rating':
|
'Write a review and pick a star rating':
|
||||||
'Escreva uma avaliação e escolha uma classificação por estrelas',
|
'Escreva uma avaliação e escolha uma classificação por estrelas',
|
||||||
Submit: 'Enviar',
|
Submit: 'Enviar',
|
||||||
'Reviews for {{relay}}': 'Avaliações para {{relay}}'
|
'Reviews for {{relay}}': 'Avaliações para {{relay}}',
|
||||||
|
'No relays selected': 'Nenhum relay selecionado',
|
||||||
|
'Post to': 'Publicar para',
|
||||||
|
'Write relays and {{count}} other relays': 'Relays de escrita e {{count}} outros relays',
|
||||||
|
'{{count}} relays': '{{count}} relays'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -423,6 +423,11 @@ export default {
|
|||||||
'Failed to review': 'Ошибка отзыва',
|
'Failed to review': 'Ошибка отзыва',
|
||||||
'Write a review and pick a star rating': 'Напишите отзыв и выберите звездный рейтинг',
|
'Write a review and pick a star rating': 'Напишите отзыв и выберите звездный рейтинг',
|
||||||
Submit: 'Отправить',
|
Submit: 'Отправить',
|
||||||
'Reviews for {{relay}}': 'Отзывы для {{relay}}'
|
'Reviews for {{relay}}': 'Отзывы для {{relay}}',
|
||||||
|
'No relays selected': 'Ретрансляторы не выбраны',
|
||||||
|
'Post to': 'Опубликовать в',
|
||||||
|
'Write relays and {{count}} other relays':
|
||||||
|
'Ретрансляторы записи и {{count}} других ретрансляторов',
|
||||||
|
'{{count}} relays': '{{count}} ретрансляторов'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -413,6 +413,10 @@ export default {
|
|||||||
'Failed to review': 'รีวิวล้มเหลว',
|
'Failed to review': 'รีวิวล้มเหลว',
|
||||||
'Write a review and pick a star rating': 'เขียนรีวิวและเลือกคะแนนดาว',
|
'Write a review and pick a star rating': 'เขียนรีวิวและเลือกคะแนนดาว',
|
||||||
Submit: 'ส่ง',
|
Submit: 'ส่ง',
|
||||||
'Reviews for {{relay}}': 'รีวิวสำหรับ {{relay}}'
|
'Reviews for {{relay}}': 'รีวิวสำหรับ {{relay}}',
|
||||||
|
'No relays selected': 'ไม่ได้เลือกรีเลย์',
|
||||||
|
'Post to': 'โพสต์ไปยัง',
|
||||||
|
'Write relays and {{count}} other relays': 'รีเลย์เขียนและรีเลย์อื่น ๆ {{count}} ตัว',
|
||||||
|
'{{count}} relays': 'รีเลย์ {{count}} ตัว'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -411,6 +411,10 @@ export default {
|
|||||||
'Failed to review': '评价失败',
|
'Failed to review': '评价失败',
|
||||||
'Write a review and pick a star rating': '写下评价并选择星级评分',
|
'Write a review and pick a star rating': '写下评价并选择星级评分',
|
||||||
Submit: '提交',
|
Submit: '提交',
|
||||||
'Reviews for {{relay}}': '关于 {{relay}} 的评价'
|
'Reviews for {{relay}}': '关于 {{relay}} 的评价',
|
||||||
|
'No relays selected': '未选择服务器',
|
||||||
|
'Post to': '发布到',
|
||||||
|
'Write relays and {{count}} other relays': '写服务器和其他 {{count}} 个服务器',
|
||||||
|
'{{count}} relays': '{{count}} 个服务器'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import RelayInfo from '@/components/RelayInfo'
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
|
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
|
||||||
import { toSearch } from '@/lib/link'
|
import { toSearch } from '@/lib/link'
|
||||||
|
import { useCurrentRelays } from '@/providers/CurrentRelaysProvider'
|
||||||
import { useFeed } from '@/providers/FeedProvider'
|
import { useFeed } from '@/providers/FeedProvider'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||||
@@ -26,6 +27,7 @@ import RelaysFeed from './RelaysFeed'
|
|||||||
|
|
||||||
const NoteListPage = forwardRef((_, ref) => {
|
const NoteListPage = forwardRef((_, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { addRelayUrls, removeRelayUrls } = useCurrentRelays()
|
||||||
const layoutRef = useRef<TPageRef>(null)
|
const layoutRef = useRef<TPageRef>(null)
|
||||||
const { pubkey, checkLogin } = useNostr()
|
const { pubkey, checkLogin } = useNostr()
|
||||||
const { feedInfo, relayUrls, isReady } = useFeed()
|
const { feedInfo, relayUrls, isReady } = useFeed()
|
||||||
@@ -38,6 +40,15 @@ const NoteListPage = forwardRef((_, ref) => {
|
|||||||
}
|
}
|
||||||
}, [JSON.stringify(relayUrls), feedInfo])
|
}, [JSON.stringify(relayUrls), feedInfo])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (relayUrls.length) {
|
||||||
|
addRelayUrls(relayUrls)
|
||||||
|
return () => {
|
||||||
|
removeRelayUrls(relayUrls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [relayUrls])
|
||||||
|
|
||||||
let content: React.ReactNode = null
|
let content: React.ReactNode = null
|
||||||
if (!isReady) {
|
if (!isReady) {
|
||||||
content = <div className="text-center text-sm text-muted-foreground">{t('loading...')}</div>
|
content = <div className="text-center text-sm text-muted-foreground">{t('loading...')}</div>
|
||||||
|
|||||||
@@ -1,20 +1,12 @@
|
|||||||
import Relay from '@/components/Relay'
|
import Relay from '@/components/Relay'
|
||||||
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
|
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
|
||||||
import { normalizeUrl, simplifyUrl } from '@/lib/url'
|
import { normalizeUrl, simplifyUrl } from '@/lib/url'
|
||||||
import { useCurrentRelays } from '@/providers/CurrentRelaysProvider'
|
|
||||||
import { Server } from 'lucide-react'
|
import { Server } from 'lucide-react'
|
||||||
import { forwardRef, useEffect, useMemo } from 'react'
|
import { forwardRef, useMemo } from 'react'
|
||||||
|
|
||||||
const RelayPage = forwardRef(({ url }: { url?: string }, ref) => {
|
const RelayPage = forwardRef(({ url }: { url?: string }, ref) => {
|
||||||
const { setTemporaryRelayUrls } = useCurrentRelays()
|
|
||||||
const normalizedUrl = useMemo(() => (url ? normalizeUrl(url) : undefined), [url])
|
const normalizedUrl = useMemo(() => (url ? normalizeUrl(url) : undefined), [url])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (normalizedUrl) {
|
|
||||||
setTemporaryRelayUrls([normalizedUrl])
|
|
||||||
}
|
|
||||||
}, [normalizedUrl])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PrimaryPageLayout
|
<PrimaryPageLayout
|
||||||
pageName="relay"
|
pageName="relay"
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { usePrimaryPage } from '@/PageManager'
|
import { createContext, useCallback, useContext, useMemo, useState } from 'react'
|
||||||
import { createContext, useContext, useEffect, useState } from 'react'
|
|
||||||
import { useFeed } from './FeedProvider'
|
|
||||||
|
|
||||||
type TCurrentRelaysContext = {
|
type TCurrentRelaysContext = {
|
||||||
currentRelayUrls: string[]
|
relayUrls: string[]
|
||||||
setTemporaryRelayUrls: (urls: string[]) => void
|
addRelayUrls: (urls: string[]) => void
|
||||||
|
removeRelayUrls: (urls: string[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const CurrentRelaysContext = createContext<TCurrentRelaysContext | undefined>(undefined)
|
const CurrentRelaysContext = createContext<TCurrentRelaysContext | undefined>(undefined)
|
||||||
@@ -18,17 +17,36 @@ export const useCurrentRelays = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CurrentRelaysProvider({ children }: { children: React.ReactNode }) {
|
export function CurrentRelaysProvider({ children }: { children: React.ReactNode }) {
|
||||||
const { current } = usePrimaryPage()
|
const [relayRefCount, setRelayRefCount] = useState<Record<string, number>>({})
|
||||||
const { relayUrls } = useFeed()
|
const relayUrls = useMemo(() => Object.keys(relayRefCount), [relayRefCount])
|
||||||
const [currentRelayUrls, setCurrentRelayUrls] = useState<string[]>([])
|
|
||||||
const [temporaryRelayUrls, setTemporaryRelayUrls] = useState<string[]>([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
const addRelayUrls = useCallback((urls: string[]) => {
|
||||||
setCurrentRelayUrls(current === 'relay' ? temporaryRelayUrls : relayUrls)
|
setRelayRefCount((prev) => {
|
||||||
}, [temporaryRelayUrls, current, relayUrls])
|
const newCounts = { ...prev }
|
||||||
|
urls.forEach((url) => {
|
||||||
|
newCounts[url] = (newCounts[url] || 0) + 1
|
||||||
|
})
|
||||||
|
return newCounts
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const removeRelayUrls = useCallback((urls: string[]) => {
|
||||||
|
setRelayRefCount((prev) => {
|
||||||
|
const newCounts = { ...prev }
|
||||||
|
urls.forEach((url) => {
|
||||||
|
if (newCounts[url]) {
|
||||||
|
newCounts[url] -= 1
|
||||||
|
if (newCounts[url] <= 0) {
|
||||||
|
delete newCounts[url]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return newCounts
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CurrentRelaysContext.Provider value={{ currentRelayUrls, setTemporaryRelayUrls }}>
|
<CurrentRelaysContext.Provider value={{ relayUrls, addRelayUrls, removeRelayUrls }}>
|
||||||
{children}
|
{children}
|
||||||
</CurrentRelaysContext.Provider>
|
</CurrentRelaysContext.Provider>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -94,6 +94,10 @@ class ClientService extends EventTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let relays: string[]
|
||||||
|
if (specifiedRelayUrls?.length) {
|
||||||
|
relays = specifiedRelayUrls
|
||||||
|
} else {
|
||||||
const _additionalRelayUrls: string[] = additionalRelayUrls ?? []
|
const _additionalRelayUrls: string[] = additionalRelayUrls ?? []
|
||||||
if (!specifiedRelayUrls?.length && ![kinds.Contacts, kinds.Mutelist].includes(event.kind)) {
|
if (!specifiedRelayUrls?.length && ![kinds.Contacts, kinds.Mutelist].includes(event.kind)) {
|
||||||
const mentions: string[] = []
|
const mentions: string[] = []
|
||||||
@@ -125,10 +129,6 @@ class ClientService extends EventTarget {
|
|||||||
_additionalRelayUrls.push(...BIG_RELAY_URLS)
|
_additionalRelayUrls.push(...BIG_RELAY_URLS)
|
||||||
}
|
}
|
||||||
|
|
||||||
let relays: string[]
|
|
||||||
if (specifiedRelayUrls?.length) {
|
|
||||||
relays = specifiedRelayUrls
|
|
||||||
} else {
|
|
||||||
const relayList = await this.fetchRelayList(event.pubkey)
|
const relayList = await this.fetchRelayList(event.pubkey)
|
||||||
relays = (relayList?.write.slice(0, 10) ?? []).concat(
|
relays = (relayList?.write.slice(0, 10) ?? []).concat(
|
||||||
Array.from(new Set(_additionalRelayUrls)) ?? []
|
Array.from(new Set(_additionalRelayUrls)) ?? []
|
||||||
|
|||||||
Reference in New Issue
Block a user