diff --git a/src/components/NoteOptions/useMenuActions.tsx b/src/components/NoteOptions/useMenuActions.tsx
index 82cfe583..2dc2d655 100644
--- a/src/components/NoteOptions/useMenuActions.tsx
+++ b/src/components/NoteOptions/useMenuActions.tsx
@@ -2,22 +2,12 @@ import { getNoteBech32Id, isProtectedEvent } from '@/lib/event'
import { toNjump } from '@/lib/link'
import { pubkeyToNpub } from '@/lib/pubkey'
import { simplifyUrl } from '@/lib/url'
+import { useCurrentRelays } from '@/providers/CurrentRelaysProvider'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useMuteList } from '@/providers/MuteListProvider'
import { useNostr } from '@/providers/NostrProvider'
import client from '@/services/client.service'
-import {
- Bell,
- BellOff,
- Code,
- Copy,
- Link,
- Mail,
- SatelliteDish,
- Server,
- Trash2,
- TriangleAlert
-} from 'lucide-react'
+import { Bell, BellOff, Code, Copy, Link, SatelliteDish, Trash2, TriangleAlert } from 'lucide-react'
import { Event } from 'nostr-tools'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
@@ -59,7 +49,11 @@ export function useMenuActions({
}: UseMenuActionsProps) {
const { t } = useTranslation()
const { pubkey, attemptDelete } = useNostr()
+ const { relayUrls: currentBrowsingRelayUrls } = useCurrentRelays()
const { relaySets, favoriteRelays } = useFavoriteRelays()
+ const relayUrls = useMemo(() => {
+ return Array.from(new Set(currentBrowsingRelayUrls.concat(favoriteRelays)))
+ }, [currentBrowsingRelayUrls, favoriteRelays])
const { mutePubkeyPublicly, mutePubkeyPrivately, unmutePubkey, mutePubkeySet } = useMuteList()
const isMuted = useMemo(() => mutePubkeySet.has(event.pubkey), [mutePubkeySet, event])
@@ -67,12 +61,7 @@ export function useMenuActions({
const items = []
if (pubkey && event.pubkey === pubkey) {
items.push({
- label: (
-
-
-
{t('Suitable Relays')}
-
- ),
+ label: {t('Write relays')}
,
onClick: async () => {
closeDrawer()
const relays = await client.determineTargetRelays(event)
@@ -97,12 +86,7 @@ export function useMenuActions({
...relaySets
.filter((set) => set.relayUrls.length)
.map((set, index) => ({
- label: (
-
- ),
+ label: {set.name}
,
onClick: async () => {
closeDrawer()
await client
@@ -126,9 +110,9 @@ export function useMenuActions({
)
}
- if (favoriteRelays.length) {
+ if (relayUrls.length) {
items.push(
- ...favoriteRelays.map((relay, index) => ({
+ ...relayUrls.map((relay, index) => ({
label: (
@@ -159,7 +143,7 @@ export function useMenuActions({
}
return items
- }, [pubkey, favoriteRelays, relaySets])
+ }, [pubkey, relayUrls, relaySets])
const menuActions: MenuAction[] = useMemo(() => {
const actions: MenuAction[] = [
diff --git a/src/components/PostEditor/PostContent.tsx b/src/components/PostEditor/PostContent.tsx
index 1d0c728d..410b4e48 100644
--- a/src/components/PostEditor/PostContent.tsx
+++ b/src/components/PostEditor/PostContent.tsx
@@ -14,15 +14,15 @@ import postEditorCache from '@/services/post-editor-cache.service'
import { TPollCreateData } from '@/types'
import { ImageUp, ListTodo, LoaderCircle, Settings, Smile, X } from 'lucide-react'
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 { toast } from 'sonner'
import EmojiPickerDialog from '../EmojiPickerDialog'
import Mentions from './Mentions'
import PollEditor from './PollEditor'
import PostOptions from './PostOptions'
+import PostRelaySelector from './PostRelaySelector'
import PostTextarea, { TPostTextareaHandle } from './PostTextarea'
-import SendOnlyToSwitch from './SendOnlyToSwitch'
import Uploader from './Uploader'
export default function PostContent({
@@ -47,10 +47,11 @@ export default function PostContent({
>([])
const [showMoreOptions, setShowMoreOptions] = useState(false)
const [addClientTag, setAddClientTag] = useState(false)
- const [specifiedRelayUrls, setSpecifiedRelayUrls] = useState(undefined)
const [mentions, setMentions] = useState([])
const [isNsfw, setIsNsfw] = useState(false)
const [isPoll, setIsPoll] = useState(false)
+ const [isProtectedEvent, setIsProtectedEvent] = useState(false)
+ const [additionalRelayUrls, setAdditionalRelayUrls] = useState([])
const [pollCreateData, setPollCreateData] = useState({
isMultipleChoice: false,
options: ['', ''],
@@ -59,12 +60,25 @@ export default function PostContent({
})
const [minPow, setMinPow] = useState(0)
const isFirstRender = useRef(true)
- const canPost =
- !!pubkey &&
- !!text &&
- !posting &&
- !uploadProgresses.length &&
- (!isPoll || pollCreateData.options.filter((option) => !!option.trim()).length >= 2)
+ const canPost = useMemo(() => {
+ return (
+ !!pubkey &&
+ !!text &&
+ !posting &&
+ !uploadProgresses.length &&
+ (!isPoll || pollCreateData.options.filter((option) => !!option.trim()).length >= 2) &&
+ (!isProtectedEvent || additionalRelayUrls.length > 0)
+ )
+ }, [
+ pubkey,
+ text,
+ posting,
+ uploadProgresses,
+ isPoll,
+ pollCreateData,
+ isProtectedEvent,
+ additionalRelayUrls
+ ])
useEffect(() => {
if (isFirstRender.current) {
@@ -97,15 +111,7 @@ export default function PostContent({
addClientTag
}
)
- }, [
- defaultContent,
- parentEvent,
- isNsfw,
- isPoll,
- pollCreateData,
- specifiedRelayUrls,
- addClientTag
- ])
+ }, [defaultContent, parentEvent, isNsfw, isPoll, pollCreateData, addClientTag])
const post = async (e?: React.MouseEvent) => {
e?.stopPropagation()
@@ -118,24 +124,24 @@ export default function PostContent({
parentEvent && parentEvent.kind !== kinds.ShortTextNote
? await createCommentDraftEvent(text, parentEvent, mentions, {
addClientTag,
- protectedEvent: !!specifiedRelayUrls,
+ protectedEvent: isProtectedEvent,
isNsfw
})
: isPoll
- ? await createPollDraftEvent(pubkey, text, mentions, pollCreateData, {
+ ? await createPollDraftEvent(pubkey!, text, mentions, pollCreateData, {
addClientTag,
isNsfw
})
: await createShortTextNoteDraftEvent(text, mentions, {
parentEvent,
addClientTag,
- protectedEvent: !!specifiedRelayUrls,
+ protectedEvent: isProtectedEvent,
isNsfw
})
const newEvent = await publish(draftEvent, {
- specifiedRelayUrls,
- additionalRelayUrls: isPoll ? pollCreateData.relays : [],
+ specifiedRelayUrls: isProtectedEvent ? additionalRelayUrls : undefined,
+ additionalRelayUrls: isPoll ? pollCreateData.relays : additionalRelayUrls,
minPow
})
postEditorCache.clearPostCache({ defaultContent, parentEvent })
@@ -233,10 +239,10 @@ export default function PostContent({
))}
{!isPoll && (
-
)}
diff --git a/src/components/PostEditor/PostRelaySelector.tsx b/src/components/PostEditor/PostRelaySelector.tsx
new file mode 100644
index 00000000..38a3bc2a
--- /dev/null
+++ b/src/components/PostEditor/PostRelaySelector.tsx
@@ -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>
+ setAdditionalRelayUrls: Dispatch>
+}) {
+ const { t } = useTranslation()
+ const { relayUrls } = useCurrentRelays()
+ const { relaySets, favoriteRelays } = useFavoriteRelays()
+ const [postTargetItems, setPostTargetItems] = useState([])
+ 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 (
+
+
+ {t('Post to')}
+
+
+
+
+
+ item.type === 'writeRelays')}
+ onSelect={(e) => e.preventDefault()}
+ onCheckedChange={handleWriteRelaysCheckedChange}
+ >
+ {t('Write relays')}
+
+ {relaySets.length > 0 && (
+ <>
+
+ {relaySets
+ .filter(({ relayUrls }) => relayUrls.length)
+ .map(({ id, name, relayUrls }) => (
+ item.type === 'relaySet' && item.id === id
+ )}
+ onSelect={(e) => e.preventDefault()}
+ onCheckedChange={(checked) => handleRelaySetCheckedChange(checked, id, relayUrls)}
+ >
+
+ {name} ({relayUrls.length})
+
+
+ ))}
+ >
+ )}
+ {selectableRelays.length > 0 && (
+ <>
+
+ {selectableRelays.map((url) => (
+ item.type === 'relay' && item.url === url)}
+ onSelect={(e) => e.preventDefault()}
+ onCheckedChange={(checked) => handleRelayCheckedChange(checked, url)}
+ className="flex items-center gap-2"
+ >
+
+ {simplifyUrl(url)}
+
+ ))}
+ >
+ )}
+
+
+ )
+}
diff --git a/src/components/PostEditor/SendOnlyToSwitch.tsx b/src/components/PostEditor/SendOnlyToSwitch.tsx
deleted file mode 100644
index ddad7d1d..00000000
--- a/src/components/PostEditor/SendOnlyToSwitch.tsx
+++ /dev/null
@@ -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>
- openFrom?: string[]
-}) {
- const { t } = useTranslation()
- const { currentRelayUrls } = useCurrentRelays()
- const [urls, setUrls] = useState([])
-
- 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 (
-
-
-
- {urls.length > 1 && (
-
-
-
-
-
- {urls.map((url) => (
- {simplifyUrl(url)}
- ))}
-
-
- )}
-
-
setSpecifiedRelayUrls(checked ? urls : undefined)}
- />
-
- )
-}
diff --git a/src/components/Relay/index.tsx b/src/components/Relay/index.tsx
index 2fa9bb8f..9791367c 100644
--- a/src/components/Relay/index.tsx
+++ b/src/components/Relay/index.tsx
@@ -3,17 +3,28 @@ import RelayInfo from '@/components/RelayInfo'
import SearchInput from '@/components/SearchInput'
import { useFetchRelayInfo } from '@/hooks'
import { normalizeUrl } from '@/lib/url'
+import { useCurrentRelays } from '@/providers/CurrentRelaysProvider'
import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import NotFound from '../NotFound'
export default function Relay({ url, className }: { url?: string; className?: string }) {
const { t } = useTranslation()
+ const { addRelayUrls, removeRelayUrls } = useCurrentRelays()
const normalizedUrl = useMemo(() => (url ? normalizeUrl(url) : undefined), [url])
const { relayInfo } = useFetchRelayInfo(normalizedUrl)
const [searchInput, setSearchInput] = useState('')
const [debouncedInput, setDebouncedInput] = useState(searchInput)
+ useEffect(() => {
+ if (normalizedUrl) {
+ addRelayUrls([normalizedUrl])
+ return () => {
+ removeRelayUrls([normalizedUrl])
+ }
+ }
+ }, [normalizedUrl])
+
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedInput(searchInput)
diff --git a/src/i18n/locales/ar.ts b/src/i18n/locales/ar.ts
index f437369c..8b7bf875 100644
--- a/src/i18n/locales/ar.ts
+++ b/src/i18n/locales/ar.ts
@@ -415,6 +415,10 @@ export default {
'Failed to review': 'فشل في المراجعة',
'Write a review and pick a star rating': 'اكتب مراجعة واختر تقييماً بالنجوم',
Submit: 'إرسال',
- 'Reviews for {{relay}}': 'مراجعات لـ {{relay}}'
+ 'Reviews for {{relay}}': 'مراجعات لـ {{relay}}',
+ 'No relays selected': 'لم يتم اختيار أي مرحل',
+ 'Post to': 'نشر إلى',
+ 'Write relays and {{count}} other relays': 'مرحلات الكتابة و {{count}} مرحل آخر',
+ '{{count}} relays': '{{count}} ريلايات'
}
}
diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts
index c0a401a9..a18dfbb6 100644
--- a/src/i18n/locales/de.ts
+++ b/src/i18n/locales/de.ts
@@ -427,6 +427,10 @@ export default {
'Write a review and pick a star rating':
'Schreiben Sie eine Bewertung und wählen Sie eine Sternebewertung',
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'
}
}
diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts
index 49eead03..010237f7 100644
--- a/src/i18n/locales/en.ts
+++ b/src/i18n/locales/en.ts
@@ -414,6 +414,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}}': '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'
}
}
diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts
index 0c438b71..ff1882b8 100644
--- a/src/i18n/locales/es.ts
+++ b/src/i18n/locales/es.ts
@@ -422,6 +422,10 @@ export default {
'Write a review and pick a star rating':
'Escriba una reseña y elija una calificación de estrellas',
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'
}
}
diff --git a/src/i18n/locales/fa.ts b/src/i18n/locales/fa.ts
index c0a98210..5bc57451 100644
--- a/src/i18n/locales/fa.ts
+++ b/src/i18n/locales/fa.ts
@@ -417,6 +417,10 @@ export default {
'Failed to review': 'نقد ناموفق',
'Write a review and pick a star rating': 'نقدی بنویسید و امتیاز ستارهای انتخاب کنید',
Submit: 'ارسال',
- 'Reviews for {{relay}}': 'نقدها برای {{relay}}'
+ 'Reviews for {{relay}}': 'نقدها برای {{relay}}',
+ 'No relays selected': 'هیچ رلهای انتخاب نشده',
+ 'Post to': 'پست کردن به',
+ 'Write relays and {{count}} other relays': 'رلههای نوشتن و {{count}} رله دیگر',
+ '{{count}} relays': '{{count}} رله'
}
}
diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts
index c6804405..36e7fe94 100644
--- a/src/i18n/locales/fr.ts
+++ b/src/i18n/locales/fr.ts
@@ -426,6 +426,10 @@ export default {
'Failed to review': 'Échec de l’avis',
'Write a review and pick a star rating': 'Écrivez un avis et choisissez une note en étoiles',
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'
}
}
diff --git a/src/i18n/locales/hi.ts b/src/i18n/locales/hi.ts
index 4fb3f97b..618cf2cf 100644
--- a/src/i18n/locales/hi.ts
+++ b/src/i18n/locales/hi.ts
@@ -419,6 +419,10 @@ export default {
'Failed to review': 'समीक्षा असफल',
'Write a review and pick a star rating': 'एक समीक्षा लिखें और स्टार रेटिंग चुनें',
Submit: 'सबमिट करें',
- 'Reviews for {{relay}}': '{{relay}} के लिए समीक्षाएं'
+ 'Reviews for {{relay}}': '{{relay}} के लिए समीक्षाएं',
+ 'No relays selected': 'कोई रिले चयनित नहीं',
+ 'Post to': 'पोस्ट करें',
+ 'Write relays and {{count}} other relays': 'राइट रिले और {{count}} अन्य रिले',
+ '{{count}} relays': '{{count}} रिले'
}
}
diff --git a/src/i18n/locales/it.ts b/src/i18n/locales/it.ts
index 9cc41460..81a99733 100644
--- a/src/i18n/locales/it.ts
+++ b/src/i18n/locales/it.ts
@@ -422,6 +422,10 @@ export default {
'Write a review and pick a star rating':
'Scrivi una recensione e scegli una valutazione a stelle',
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'
}
}
diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts
index 4fde5c52..732ec188 100644
--- a/src/i18n/locales/ja.ts
+++ b/src/i18n/locales/ja.ts
@@ -418,6 +418,10 @@ export default {
'Failed to review': 'レビュー失敗',
'Write a review and pick a star rating': 'レビューを書いて星評価を選択してください',
Submit: '送信',
- 'Reviews for {{relay}}': '{{relay}} のレビュー'
+ 'Reviews for {{relay}}': '{{relay}} のレビュー',
+ 'No relays selected': 'リレーが選択されていません',
+ 'Post to': '投稿先',
+ 'Write relays and {{count}} other relays': '書き込みリレーと他の {{count}} 個のリレー',
+ '{{count}} relays': '{{count}} 個のリレー'
}
}
diff --git a/src/i18n/locales/ko.ts b/src/i18n/locales/ko.ts
index 222d43c5..9c34bc77 100644
--- a/src/i18n/locales/ko.ts
+++ b/src/i18n/locales/ko.ts
@@ -418,6 +418,10 @@ export default {
'Failed to review': '리뷰 실패',
'Write a review and pick a star rating': '리뷰를 작성하고 별점을 선택하세요',
Submit: '제출',
- 'Reviews for {{relay}}': '{{relay}}에 대한 리뷰'
+ 'Reviews for {{relay}}': '{{relay}}에 대한 리뷰',
+ 'No relays selected': '선택된 릴레이가 없습니다',
+ 'Post to': '게시 대상',
+ 'Write relays and {{count}} other relays': '쓰기 릴레이 및 기타 {{count}}개 릴레이',
+ '{{count}} relays': '{{count}}개 릴레이'
}
}
diff --git a/src/i18n/locales/pl.ts b/src/i18n/locales/pl.ts
index 286ec7ae..d13c0f87 100644
--- a/src/i18n/locales/pl.ts
+++ b/src/i18n/locales/pl.ts
@@ -422,6 +422,10 @@ export default {
'Failed to review': 'Błąd opinii',
'Write a review and pick a star rating': 'Napisz opinię i wybierz ocenę gwiazdkową',
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'
}
}
diff --git a/src/i18n/locales/pt-BR.ts b/src/i18n/locales/pt-BR.ts
index 584139fd..0b5701c8 100644
--- a/src/i18n/locales/pt-BR.ts
+++ b/src/i18n/locales/pt-BR.ts
@@ -419,6 +419,10 @@ export default {
'Write a review and pick a star rating':
'Escreva uma avaliação e escolha uma classificação por estrelas',
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'
}
}
diff --git a/src/i18n/locales/pt-PT.ts b/src/i18n/locales/pt-PT.ts
index 6ebf6888..d37ea540 100644
--- a/src/i18n/locales/pt-PT.ts
+++ b/src/i18n/locales/pt-PT.ts
@@ -422,6 +422,10 @@ export default {
'Write a review and pick a star rating':
'Escreva uma avaliação e escolha uma classificação por estrelas',
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'
}
}
diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts
index 33c260aa..d8938847 100644
--- a/src/i18n/locales/ru.ts
+++ b/src/i18n/locales/ru.ts
@@ -423,6 +423,11 @@ export default {
'Failed to review': 'Ошибка отзыва',
'Write a review and pick a star rating': 'Напишите отзыв и выберите звездный рейтинг',
Submit: 'Отправить',
- 'Reviews for {{relay}}': 'Отзывы для {{relay}}'
+ 'Reviews for {{relay}}': 'Отзывы для {{relay}}',
+ 'No relays selected': 'Ретрансляторы не выбраны',
+ 'Post to': 'Опубликовать в',
+ 'Write relays and {{count}} other relays':
+ 'Ретрансляторы записи и {{count}} других ретрансляторов',
+ '{{count}} relays': '{{count}} ретрансляторов'
}
}
diff --git a/src/i18n/locales/th.ts b/src/i18n/locales/th.ts
index 385dea52..b724fefb 100644
--- a/src/i18n/locales/th.ts
+++ b/src/i18n/locales/th.ts
@@ -413,6 +413,10 @@ export default {
'Failed to review': 'รีวิวล้มเหลว',
'Write a review and pick a star rating': 'เขียนรีวิวและเลือกคะแนนดาว',
Submit: 'ส่ง',
- 'Reviews for {{relay}}': 'รีวิวสำหรับ {{relay}}'
+ 'Reviews for {{relay}}': 'รีวิวสำหรับ {{relay}}',
+ 'No relays selected': 'ไม่ได้เลือกรีเลย์',
+ 'Post to': 'โพสต์ไปยัง',
+ 'Write relays and {{count}} other relays': 'รีเลย์เขียนและรีเลย์อื่น ๆ {{count}} ตัว',
+ '{{count}} relays': 'รีเลย์ {{count}} ตัว'
}
}
diff --git a/src/i18n/locales/zh.ts b/src/i18n/locales/zh.ts
index e8d1c8c0..868ea62d 100644
--- a/src/i18n/locales/zh.ts
+++ b/src/i18n/locales/zh.ts
@@ -411,6 +411,10 @@ export default {
'Failed to review': '评价失败',
'Write a review and pick a star rating': '写下评价并选择星级评分',
Submit: '提交',
- 'Reviews for {{relay}}': '关于 {{relay}} 的评价'
+ 'Reviews for {{relay}}': '关于 {{relay}} 的评价',
+ 'No relays selected': '未选择服务器',
+ 'Post to': '发布到',
+ 'Write relays and {{count}} other relays': '写服务器和其他 {{count}} 个服务器',
+ '{{count}} relays': '{{count}} 个服务器'
}
}
diff --git a/src/pages/primary/NoteListPage/index.tsx b/src/pages/primary/NoteListPage/index.tsx
index 25d2a844..5af33a59 100644
--- a/src/pages/primary/NoteListPage/index.tsx
+++ b/src/pages/primary/NoteListPage/index.tsx
@@ -5,6 +5,7 @@ import RelayInfo from '@/components/RelayInfo'
import { Button } from '@/components/ui/button'
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
import { toSearch } from '@/lib/link'
+import { useCurrentRelays } from '@/providers/CurrentRelaysProvider'
import { useFeed } from '@/providers/FeedProvider'
import { useNostr } from '@/providers/NostrProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
@@ -26,6 +27,7 @@ import RelaysFeed from './RelaysFeed'
const NoteListPage = forwardRef((_, ref) => {
const { t } = useTranslation()
+ const { addRelayUrls, removeRelayUrls } = useCurrentRelays()
const layoutRef = useRef(null)
const { pubkey, checkLogin } = useNostr()
const { feedInfo, relayUrls, isReady } = useFeed()
@@ -38,6 +40,15 @@ const NoteListPage = forwardRef((_, ref) => {
}
}, [JSON.stringify(relayUrls), feedInfo])
+ useEffect(() => {
+ if (relayUrls.length) {
+ addRelayUrls(relayUrls)
+ return () => {
+ removeRelayUrls(relayUrls)
+ }
+ }
+ }, [relayUrls])
+
let content: React.ReactNode = null
if (!isReady) {
content = {t('loading...')}
diff --git a/src/pages/primary/RelayPage/index.tsx b/src/pages/primary/RelayPage/index.tsx
index cb14e349..523e7da5 100644
--- a/src/pages/primary/RelayPage/index.tsx
+++ b/src/pages/primary/RelayPage/index.tsx
@@ -1,20 +1,12 @@
import Relay from '@/components/Relay'
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
import { normalizeUrl, simplifyUrl } from '@/lib/url'
-import { useCurrentRelays } from '@/providers/CurrentRelaysProvider'
import { Server } from 'lucide-react'
-import { forwardRef, useEffect, useMemo } from 'react'
+import { forwardRef, useMemo } from 'react'
const RelayPage = forwardRef(({ url }: { url?: string }, ref) => {
- const { setTemporaryRelayUrls } = useCurrentRelays()
const normalizedUrl = useMemo(() => (url ? normalizeUrl(url) : undefined), [url])
- useEffect(() => {
- if (normalizedUrl) {
- setTemporaryRelayUrls([normalizedUrl])
- }
- }, [normalizedUrl])
-
return (
void
+ relayUrls: string[]
+ addRelayUrls: (urls: string[]) => void
+ removeRelayUrls: (urls: string[]) => void
}
const CurrentRelaysContext = createContext(undefined)
@@ -18,17 +17,36 @@ export const useCurrentRelays = () => {
}
export function CurrentRelaysProvider({ children }: { children: React.ReactNode }) {
- const { current } = usePrimaryPage()
- const { relayUrls } = useFeed()
- const [currentRelayUrls, setCurrentRelayUrls] = useState([])
- const [temporaryRelayUrls, setTemporaryRelayUrls] = useState([])
+ const [relayRefCount, setRelayRefCount] = useState>({})
+ const relayUrls = useMemo(() => Object.keys(relayRefCount), [relayRefCount])
- useEffect(() => {
- setCurrentRelayUrls(current === 'relay' ? temporaryRelayUrls : relayUrls)
- }, [temporaryRelayUrls, current, relayUrls])
+ const addRelayUrls = useCallback((urls: string[]) => {
+ setRelayRefCount((prev) => {
+ 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 (
-
+
{children}
)
diff --git a/src/services/client.service.ts b/src/services/client.service.ts
index 38608d42..ae268c48 100644
--- a/src/services/client.service.ts
+++ b/src/services/client.service.ts
@@ -94,41 +94,41 @@ class ClientService extends EventTarget {
}
}
- const _additionalRelayUrls: string[] = additionalRelayUrls ?? []
- if (!specifiedRelayUrls?.length && ![kinds.Contacts, kinds.Mutelist].includes(event.kind)) {
- const mentions: string[] = []
- event.tags.forEach(([tagName, tagValue]) => {
- if (
- ['p', 'P'].includes(tagName) &&
- !!tagValue &&
- isValidPubkey(tagValue) &&
- !mentions.includes(tagValue)
- ) {
- mentions.push(tagValue)
- }
- })
- if (mentions.length > 0) {
- const relayLists = await this.fetchRelayLists(mentions)
- relayLists.forEach((relayList) => {
- _additionalRelayUrls.push(...relayList.read.slice(0, 4))
- })
- }
- }
- if (
- [
- kinds.RelayList,
- kinds.Contacts,
- ExtendedKind.FAVORITE_RELAYS,
- ExtendedKind.BLOSSOM_SERVER_LIST
- ].includes(event.kind)
- ) {
- _additionalRelayUrls.push(...BIG_RELAY_URLS)
- }
-
let relays: string[]
if (specifiedRelayUrls?.length) {
relays = specifiedRelayUrls
} else {
+ const _additionalRelayUrls: string[] = additionalRelayUrls ?? []
+ if (!specifiedRelayUrls?.length && ![kinds.Contacts, kinds.Mutelist].includes(event.kind)) {
+ const mentions: string[] = []
+ event.tags.forEach(([tagName, tagValue]) => {
+ if (
+ ['p', 'P'].includes(tagName) &&
+ !!tagValue &&
+ isValidPubkey(tagValue) &&
+ !mentions.includes(tagValue)
+ ) {
+ mentions.push(tagValue)
+ }
+ })
+ if (mentions.length > 0) {
+ const relayLists = await this.fetchRelayLists(mentions)
+ relayLists.forEach((relayList) => {
+ _additionalRelayUrls.push(...relayList.read.slice(0, 4))
+ })
+ }
+ }
+ if (
+ [
+ kinds.RelayList,
+ kinds.Contacts,
+ ExtendedKind.FAVORITE_RELAYS,
+ ExtendedKind.BLOSSOM_SERVER_LIST
+ ].includes(event.kind)
+ ) {
+ _additionalRelayUrls.push(...BIG_RELAY_URLS)
+ }
+
const relayList = await this.fetchRelayList(event.pubkey)
relays = (relayList?.write.slice(0, 10) ?? []).concat(
Array.from(new Set(_additionalRelayUrls)) ?? []