Browse Source

Expanded home options to all relays.

Use inboxes when responding
imwald
Silberengel 5 months ago
parent
commit
9433982062
  1. 22
      src/components/FavoriteRelaysSetting/AddNewRelay.tsx
  2. 28
      src/components/FavoriteRelaysSetting/AddNewRelaySet.tsx
  3. 40
      src/components/FavoriteRelaysSetting/RelayUrl.tsx
  4. 20
      src/components/FeedSwitcher/index.tsx
  5. 7
      src/components/PostEditor/Mentions.tsx
  6. 1
      src/components/PostEditor/PostContent.tsx
  7. 289
      src/components/PostEditor/PostRelaySelector.tsx
  8. 30
      src/components/SaveRelayDropdownMenu/index.tsx
  9. 5
      src/pages/primary/NoteListPage/FeedButton.tsx
  10. 2
      src/pages/primary/NoteListPage/RelaysFeed.tsx
  11. 23
      src/providers/FeedProvider.tsx
  12. 2
      src/types/index.d.ts

22
src/components/FavoriteRelaysSetting/AddNewRelay.tsx

@ -10,9 +10,10 @@ export default function AddNewRelay() { @@ -10,9 +10,10 @@ export default function AddNewRelay() {
const { favoriteRelays, addFavoriteRelays } = useFavoriteRelays()
const [input, setInput] = useState('')
const [errorMsg, setErrorMsg] = useState('')
const [isLoading, setIsLoading] = useState(false)
const saveRelay = async () => {
if (!input) return
if (!input || isLoading) return
const normalizedUrl = normalizeUrl(input)
if (!normalizedUrl) {
setErrorMsg(t('Invalid URL'))
@ -22,8 +23,19 @@ export default function AddNewRelay() { @@ -22,8 +23,19 @@ export default function AddNewRelay() {
setErrorMsg(t('Already saved'))
return
}
await addFavoriteRelays([normalizedUrl])
setInput('')
setIsLoading(true)
setErrorMsg('')
try {
await addFavoriteRelays([normalizedUrl])
setInput('')
} catch (error) {
console.error('Failed to add favorite relay:', error)
setErrorMsg(t('Failed to add relay. Please try again.'))
} finally {
setIsLoading(false)
}
}
const handleNewRelayInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
@ -48,7 +60,9 @@ export default function AddNewRelay() { @@ -48,7 +60,9 @@ export default function AddNewRelay() {
onKeyDown={handleNewRelayInputKeyDown}
className={errorMsg ? 'border-destructive' : ''}
/>
<Button onClick={saveRelay}>{t('Add')}</Button>
<Button onClick={saveRelay} disabled={isLoading || !input.trim()}>
{isLoading ? t('Adding...') : t('Add')}
</Button>
</div>
{errorMsg && <div className="text-destructive text-sm pl-8">{errorMsg}</div>}
</div>

28
src/components/FavoriteRelaysSetting/AddNewRelaySet.tsx

@ -8,15 +8,29 @@ export default function AddNewRelaySet() { @@ -8,15 +8,29 @@ export default function AddNewRelaySet() {
const { t } = useTranslation()
const { createRelaySet } = useFavoriteRelays()
const [newRelaySetName, setNewRelaySetName] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [errorMsg, setErrorMsg] = useState('')
const saveRelaySet = () => {
if (!newRelaySetName) return
createRelaySet(newRelaySetName)
setNewRelaySetName('')
const saveRelaySet = async () => {
if (!newRelaySetName || isLoading) return
setIsLoading(true)
setErrorMsg('')
try {
await createRelaySet(newRelaySetName)
setNewRelaySetName('')
} catch (error) {
console.error('Failed to create relay set:', error)
setErrorMsg(t('Failed to create relay set. Please try again.'))
} finally {
setIsLoading(false)
}
}
const handleNewRelaySetNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setNewRelaySetName(e.target.value)
setErrorMsg('')
}
const handleNewRelaySetNameKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
@ -34,9 +48,13 @@ export default function AddNewRelaySet() { @@ -34,9 +48,13 @@ export default function AddNewRelaySet() {
value={newRelaySetName}
onChange={handleNewRelaySetNameChange}
onKeyDown={handleNewRelaySetNameKeyDown}
className={errorMsg ? 'border-destructive' : ''}
/>
<Button onClick={saveRelaySet}>{t('Add')}</Button>
<Button onClick={saveRelaySet} disabled={isLoading || !newRelaySetName.trim()}>
{isLoading ? t('Adding...') : t('Add')}
</Button>
</div>
{errorMsg && <div className="text-destructive text-sm pl-8">{errorMsg}</div>}
</div>
)
}

40
src/components/FavoriteRelaysSetting/RelayUrl.tsx

@ -14,6 +14,7 @@ export default function RelayUrls({ relaySetId }: { relaySetId: string }) { @@ -14,6 +14,7 @@ export default function RelayUrls({ relaySetId }: { relaySetId: string }) {
const { relaySets, updateRelaySet } = useFavoriteRelays()
const [newRelayUrl, setNewRelayUrl] = useState('')
const [newRelayUrlError, setNewRelayUrlError] = useState<string | null>(null)
const [isLoading, setIsLoading] = useState(false)
const relaySet = useMemo(
() => relaySets.find((r) => r.id === relaySetId),
[relaySets, relaySetId]
@ -21,15 +22,19 @@ export default function RelayUrls({ relaySetId }: { relaySetId: string }) { @@ -21,15 +22,19 @@ export default function RelayUrls({ relaySetId }: { relaySetId: string }) {
if (!relaySet) return null
const removeRelayUrl = (url: string) => {
updateRelaySet({
...relaySet,
relayUrls: relaySet.relayUrls.filter((u) => u !== url)
})
const removeRelayUrl = async (url: string) => {
try {
await updateRelaySet({
...relaySet,
relayUrls: relaySet.relayUrls.filter((u) => u !== url)
})
} catch (error) {
console.error('Failed to remove relay from set:', error)
}
}
const saveNewRelayUrl = () => {
if (newRelayUrl === '') return
const saveNewRelayUrl = async () => {
if (newRelayUrl === '' || isLoading) return
const normalizedUrl = normalizeUrl(newRelayUrl)
if (!normalizedUrl) {
return setNewRelayUrlError(t('Invalid relay URL'))
@ -40,9 +45,20 @@ export default function RelayUrls({ relaySetId }: { relaySetId: string }) { @@ -40,9 +45,20 @@ export default function RelayUrls({ relaySetId }: { relaySetId: string }) {
if (!isWebsocketUrl(normalizedUrl)) {
return setNewRelayUrlError(t('invalid relay URL'))
}
const newRelayUrls = [...relaySet.relayUrls, normalizedUrl]
updateRelaySet({ ...relaySet, relayUrls: newRelayUrls })
setNewRelayUrl('')
setIsLoading(true)
setNewRelayUrlError(null)
try {
const newRelayUrls = [...relaySet.relayUrls, normalizedUrl]
await updateRelaySet({ ...relaySet, relayUrls: newRelayUrls })
setNewRelayUrl('')
} catch (error) {
console.error('Failed to update relay set:', error)
setNewRelayUrlError(t('Failed to add relay. Please try again.'))
} finally {
setIsLoading(false)
}
}
const handleRelayUrlInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
@ -73,7 +89,9 @@ export default function RelayUrls({ relaySetId }: { relaySetId: string }) { @@ -73,7 +89,9 @@ export default function RelayUrls({ relaySetId }: { relaySetId: string }) {
onChange={handleRelayUrlInputChange}
onBlur={saveNewRelayUrl}
/>
<Button onClick={saveNewRelayUrl}>{t('Add')}</Button>
<Button onClick={saveNewRelayUrl} disabled={isLoading || !newRelayUrl.trim()}>
{isLoading ? t('Adding...') : t('Add')}
</Button>
</div>
{newRelayUrlError && <div className="text-xs text-destructive mt-1">{newRelayUrlError}</div>}
</>

20
src/components/FeedSwitcher/index.tsx

@ -4,7 +4,7 @@ import { SecondaryPageLink } from '@/PageManager' @@ -4,7 +4,7 @@ import { SecondaryPageLink } from '@/PageManager'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useFeed } from '@/providers/FeedProvider'
import { useNostr } from '@/providers/NostrProvider'
import { BookmarkIcon, UsersRound } from 'lucide-react'
import { BookmarkIcon, UsersRound, Server } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import RelayIcon from '../RelayIcon'
import RelaySetCard from '../RelaySetCard'
@ -53,6 +53,24 @@ export default function FeedSwitcher({ close }: { close?: () => void }) { @@ -53,6 +53,24 @@ export default function FeedSwitcher({ close }: { close?: () => void }) {
</FeedSwitcherItem>
)}
{favoriteRelays.length > 0 && (
<FeedSwitcherItem
isActive={feedInfo.feedType === 'all-favorites'}
onClick={() => {
console.log('FeedSwitcher: Switching to all-favorites')
switchFeed('all-favorites')
close?.()
}}
>
<div className="flex gap-2 items-center">
<div className="flex justify-center items-center w-6 h-6 shrink-0">
<Server className="size-4" />
</div>
<div>{t('All favorite relays')}</div>
</div>
</FeedSwitcherItem>
)}
<div className="flex justify-end items-center text-sm">
<SecondaryPageLink
to={toRelaySettings()}

7
src/components/PostEditor/Mentions.tsx

@ -136,12 +136,17 @@ export async function extractMentions(content: string, parentEvent?: Event) { @@ -136,12 +136,17 @@ export async function extractMentions(content: string, parentEvent?: Event) {
const parentEventPubkey = parentEvent ? parentEvent.pubkey : undefined
const pubkeys: string[] = []
const relatedPubkeys: string[] = []
// Always include parent event author in pubkeys if there's a parent event
if (parentEventPubkey) {
pubkeys.push(parentEventPubkey)
}
const matches = content.match(
/nostr:(npub1[a-z0-9]{58}|nprofile1[a-z0-9]+|note1[a-z0-9]{58}|nevent1[a-z0-9]+)/g
)
const addToSet = (arr: string[], pubkey: string) => {
if (pubkey === parentEventPubkey) return
if (!arr.includes(pubkey)) arr.push(pubkey)
}

1
src/components/PostEditor/PostContent.tsx

@ -472,6 +472,7 @@ export default function PostContent({ @@ -472,6 +472,7 @@ export default function PostContent({
setAdditionalRelayUrls={setAdditionalRelayUrls}
parentEvent={parentEvent}
openFrom={openFrom}
content={text}
/>
)}
<div className="flex items-center justify-between">

289
src/components/PostEditor/PostRelaySelector.tsx

@ -2,12 +2,9 @@ import { Button } from '@/components/ui/button' @@ -2,12 +2,9 @@ import { Button } from '@/components/ui/button'
import { Drawer, DrawerContent, DrawerOverlay } from '@/components/ui/drawer'
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { Separator } from '@/components/ui/separator'
import { ExtendedKind } from '@/constants'
import client from '@/services/client.service'
import { isWebsocketUrl, normalizeUrl } from '@/lib/url'
@ -15,84 +12,123 @@ import { simplifyUrl } from '@/lib/url' @@ -15,84 +12,123 @@ import { simplifyUrl } from '@/lib/url'
import { useCurrentRelays } from '@/providers/CurrentRelaysProvider'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { useNostr } from '@/providers/NostrProvider'
import { Check } from 'lucide-react'
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[]
}
import { extractMentions } from './Mentions'
export default function PostRelaySelector({
parentEvent: _parentEvent,
openFrom,
setIsProtectedEvent,
setAdditionalRelayUrls
setAdditionalRelayUrls,
content: postContent = ''
}: {
parentEvent?: NostrEvent
openFrom?: string[]
setIsProtectedEvent: Dispatch<SetStateAction<boolean>>
setAdditionalRelayUrls: Dispatch<SetStateAction<string[]>>
content?: string
}) {
const { t } = useTranslation()
const { isSmallScreen } = useScreenSize()
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
const { relayUrls } = useCurrentRelays()
const { relaySets, favoriteRelays } = useFavoriteRelays()
const [postTargetItems, setPostTargetItems] = useState<TPostTargetItem[]>([])
// Privacy: Only show user's own relays + defaults, never other users' relays
const { pubkey } = useNostr()
const [selectedRelayUrls, setSelectedRelayUrls] = useState<string[]>([])
const [mentionRelays, setMentionRelays] = useState<string[]>([])
// Fetch mention relays for regular replies (not discussion replies)
const isRegularReply = useMemo(() => {
if (!_parentEvent) return false
// Kind 1 or Kind 1111 that is not a reply to Kind 11 (discussion)
return (_parentEvent.kind === 1 || _parentEvent.kind === ExtendedKind.COMMENT) &&
_parentEvent.kind !== ExtendedKind.DISCUSSION
}, [_parentEvent])
// Get all selectable relays (write relays + favorite relays + relays from relay sets + mention relays)
const selectableRelays = useMemo(() => {
return Array.from(new Set(relayUrls.concat(favoriteRelays)))
}, [relayUrls, favoriteRelays])
const allRelays = Array.from(new Set([
...relayUrls,
...favoriteRelays,
...relaySets.flatMap(set => set.relayUrls),
...mentionRelays
]))
return allRelays
}, [relayUrls, favoriteRelays, relaySets, mentionRelays])
const description = useMemo(() => {
if (postTargetItems.length === 0) {
if (selectedRelayUrls.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])
}
if (selectedRelayUrls.length === 1) {
return simplifyUrl(selectedRelayUrls[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 t('{{count}} relays', { count: selectedRelayUrls.length })
}, [selectedRelayUrls])
// Fetch mention relays when content changes for regular replies
useEffect(() => {
if (!isRegularReply) {
setMentionRelays([])
return
}
const fetchMentionRelays = async () => {
try {
console.log('PostRelaySelector: extractMentions called with:', { postContent, parentEvent: _parentEvent?.id })
const { pubkeys, relatedPubkeys } = await extractMentions(postContent, _parentEvent)
console.log('PostRelaySelector: extractMentions returned:', { pubkeys, relatedPubkeys })
// Combine all mentioned pubkeys and filter out current user's pubkey
const allMentionPubkeys = [...pubkeys, ...relatedPubkeys]
const filteredMentionPubkeys = allMentionPubkeys.filter(p => p !== pubkey)
console.log('PostRelaySelector: filtered mention pubkeys:', filteredMentionPubkeys)
if (filteredMentionPubkeys.length === 0) {
setMentionRelays([])
return
}
// Fetch relay lists for all mentioned users (including parent event author)
console.log('PostRelaySelector: Fetching relays for pubkeys:', filteredMentionPubkeys)
const relayListPromises = filteredMentionPubkeys.map(async (pubkey) => {
try {
const relayList = await client.fetchRelayList(pubkey)
console.log(`PostRelaySelector: Fetched relays for ${pubkey}:`, relayList?.write || [])
return relayList?.write || []
} catch (error) {
console.warn(`Failed to fetch relay list for ${pubkey}:`, error)
return []
}
})
const relayLists = await Promise.all(relayListPromises)
const allMentionRelays = relayLists.flat()
const uniqueMentionRelays = Array.from(new Set(allMentionRelays))
console.log('PostRelaySelector: Setting mention relays:', uniqueMentionRelays)
setMentionRelays(uniqueMentionRelays)
} catch (error) {
console.error('Error fetching mention relays:', error)
setMentionRelays([])
}
return count
}, 0)
if (hasWriteRelays) {
return t('Write relays and {{count}} other relays', { count: relayCount })
}
return t('{{count}} relays', { count: relayCount })
}, [postTargetItems])
// Debounce the fetch
const timeoutId = setTimeout(fetchMentionRelays, 300)
return () => clearTimeout(timeoutId)
}, [postContent, isRegularReply, _parentEvent])
// Initialize selected relays based on context
useEffect(() => {
if (openFrom && openFrom.length) {
setPostTargetItems(Array.from(new Set(openFrom)).map((url) => ({ type: 'relay', url })))
// If called with specific relay URLs (e.g., from a discussion thread)
setSelectedRelayUrls(Array.from(new Set(openFrom)))
return
}
@ -126,112 +162,71 @@ export default function PostRelaySelector({ @@ -126,112 +162,71 @@ export default function PostRelaySelector({
if (relayHint && isWebsocketUrl(relayHint)) {
const normalizedRelayHint = normalizeUrl(relayHint)
if (normalizedRelayHint) {
setPostTargetItems([{ type: 'relay', url: normalizedRelayHint }])
setSelectedRelayUrls([normalizedRelayHint])
return
}
}
}
// Default to write relays for all other cases
setPostTargetItems([{ type: 'writeRelays' }])
}, [openFrom, _parentEvent])
// Default to write relays + mention relays for regular replies, or just write relays for other cases
if (isRegularReply) {
// For regular replies, include write relays and mention relays
const defaultRelays = Array.from(new Set([...relayUrls, ...mentionRelays]))
console.log('PostRelaySelector: Setting default relays for regular reply:', {
relayUrls,
mentionRelays,
defaultRelays,
isRegularReply
})
setSelectedRelayUrls(defaultRelays)
} else {
// For other cases, just use write relays
console.log('PostRelaySelector: Setting default relays for non-regular reply:', relayUrls)
setSelectedRelayUrls(relayUrls)
}
}, [openFrom, _parentEvent, relayUrls, isRegularReply, mentionRelays])
// Update parent component with selected relays
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 []
})
const isProtectedEvent = selectedRelayUrls.length > 0 && !selectedRelayUrls.some(url => relayUrls.includes(url))
setIsProtectedEvent(isProtectedEvent)
setAdditionalRelayUrls(relayUrls)
}, [postTargetItems])
setAdditionalRelayUrls(selectedRelayUrls)
}, [selectedRelayUrls, relayUrls, setIsProtectedEvent, setAdditionalRelayUrls])
const handleWriteRelaysCheckedChange = useCallback((checked: boolean) => {
const handleRelayCheckedChange = useCallback((checked: boolean, url: string) => {
if (checked) {
setPostTargetItems((prev) => [...prev, { type: 'writeRelays' }])
setSelectedRelayUrls(prev => [...prev, url])
} else {
setPostTargetItems((prev) => prev.filter((item) => item.type !== 'writeRelays'))
setSelectedRelayUrls(prev => prev.filter(selectedUrl => selectedUrl !== url))
}
}, [])
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 content = useMemo(() => {
if (selectableRelays.length === 0) {
return (
<div className="px-4 py-3 text-sm text-muted-foreground text-center">
{t('No relays available')}
</div>
)
}
}, [])
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))
)
}
},
[]
)
const content = useMemo(() => {
return (
<>
<MenuItem
checked={postTargetItems.some((item) => item.type === 'writeRelays')}
onCheckedChange={handleWriteRelaysCheckedChange}
>
{t('Write relays')}
</MenuItem>
{relaySets.length > 0 && (
<>
<MenuSeparator />
{relaySets
.filter(({ relayUrls }) => relayUrls.length)
.map(({ id, name, relayUrls }) => (
<MenuItem
key={id}
checked={postTargetItems.some(
(item) => item.type === 'relaySet' && item.id === id
)}
onCheckedChange={(checked) => handleRelaySetCheckedChange(checked, id, relayUrls)}
>
<div className="truncate">
{name} ({relayUrls.length})
</div>
</MenuItem>
))}
</>
)}
{selectableRelays.length > 0 && (
<>
<MenuSeparator />
{selectableRelays.map((url) => (
<MenuItem
key={url}
checked={postTargetItems.some((item) => item.type === 'relay' && item.url === url)}
onCheckedChange={(checked) => handleRelayCheckedChange(checked, url)}
>
<div className="flex items-center gap-2">
<RelayIcon url={url} />
<div className="truncate">{simplifyUrl(url)}</div>
</div>
</MenuItem>
))}
</>
)}
</>
<div className="space-y-1 max-h-64 overflow-y-auto">
{selectableRelays.map((url) => (
<MenuItem
key={url}
checked={selectedRelayUrls.includes(url)}
onCheckedChange={(checked) => handleRelayCheckedChange(checked, url)}
>
<div className="flex items-center gap-2">
<RelayIcon url={url} />
<div className="truncate">{simplifyUrl(url)}</div>
</div>
</MenuItem>
))}
</div>
)
}, [postTargetItems, relaySets, selectableRelays])
}, [selectedRelayUrls, selectableRelays])
if (isSmallScreen) {
return (
@ -278,13 +273,6 @@ export default function PostRelaySelector({ @@ -278,13 +273,6 @@ export default function PostRelaySelector({
)
}
function MenuSeparator() {
const { isSmallScreen } = useScreenSize()
if (isSmallScreen) {
return <Separator />
}
return <DropdownMenuSeparator />
}
function MenuItem({
children,
@ -312,13 +300,14 @@ function MenuItem({ @@ -312,13 +300,14 @@ function MenuItem({
}
return (
<DropdownMenuCheckboxItem
checked={checked}
onSelect={(e) => e.preventDefault()}
onCheckedChange={onCheckedChange}
className="flex items-center gap-2"
<div
onClick={() => onCheckedChange(!checked)}
className="flex items-center gap-2 px-2 py-2 hover:bg-muted cursor-pointer rounded-sm"
>
<div className="flex items-center justify-center size-4 shrink-0">
{checked && <Check className="size-4" />}
</div>
{children}
</DropdownMenuCheckboxItem>
</div>
)
}

30
src/components/SaveRelayDropdownMenu/index.tsx

@ -109,32 +109,42 @@ function RelayItem({ urls }: { urls: string[] }) { @@ -109,32 +109,42 @@ function RelayItem({ urls }: { urls: string[] }) {
const { t } = useTranslation()
const { isSmallScreen } = useScreenSize()
const { favoriteRelays, addFavoriteRelays, deleteFavoriteRelays } = useFavoriteRelays()
const [isLoading, setIsLoading] = useState(false)
const saved = useMemo(
() => urls.every((url) => favoriteRelays.includes(url)),
[favoriteRelays, urls]
)
const handleClick = async () => {
if (saved) {
await deleteFavoriteRelays(urls)
} else {
await addFavoriteRelays(urls)
if (isLoading) return
setIsLoading(true)
try {
if (saved) {
await deleteFavoriteRelays(urls)
} else {
await addFavoriteRelays(urls)
}
} catch (error) {
console.error('Failed to toggle favorite relay:', error)
} finally {
setIsLoading(false)
}
}
if (isSmallScreen) {
return (
<DrawerMenuItem onClick={handleClick}>
{saved ? <Check /> : <Plus />}
{saved ? t('Unfavorite') : t('Favorite')}
<DrawerMenuItem onClick={handleClick} disabled={isLoading}>
{isLoading ? '...' : (saved ? <Check /> : <Plus />)}
{isLoading ? t('Loading...') : (saved ? t('Unfavorite') : t('Favorite'))}
</DrawerMenuItem>
)
}
return (
<DropdownMenuItem className="flex gap-2" onClick={handleClick}>
{saved ? <Check /> : <Plus />}
{saved ? t('Unfavorite') : t('Favorite')}
<DropdownMenuItem className="flex gap-2" onClick={handleClick} disabled={isLoading}>
{isLoading ? '...' : (saved ? <Check /> : <Plus />)}
{isLoading ? t('Loading...') : (saved ? t('Unfavorite') : t('Favorite'))}
</DropdownMenuItem>
)
}

5
src/pages/primary/NoteListPage/FeedButton.tsx

@ -65,6 +65,9 @@ const FeedSwitcherTrigger = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEle @@ -65,6 +65,9 @@ const FeedSwitcherTrigger = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEle
if (feedInfo.feedType === 'bookmarks') {
return t('Bookmarks')
}
if (feedInfo.feedType === 'all-favorites') {
return t('All favorite relays')
}
if (relayUrls.length === 0) {
return t('Choose a relay')
}
@ -86,6 +89,8 @@ const FeedSwitcherTrigger = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEle @@ -86,6 +89,8 @@ const FeedSwitcherTrigger = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEle
<UsersRound />
) : feedInfo.feedType === 'bookmarks' ? (
<BookmarkIcon />
) : feedInfo.feedType === 'all-favorites' ? (
<Server />
) : (
<Server />
)}

2
src/pages/primary/NoteListPage/RelaysFeed.tsx

@ -22,7 +22,7 @@ export default function RelaysFeed() { @@ -22,7 +22,7 @@ export default function RelaysFeed() {
return null
}
if (feedInfo.feedType !== 'relay' && feedInfo.feedType !== 'relays') {
if (feedInfo.feedType !== 'relay' && feedInfo.feedType !== 'relays' && feedInfo.feedType !== 'all-favorites') {
return null
}

23
src/providers/FeedProvider.tsx

@ -73,11 +73,24 @@ export function FeedProvider({ children }: { children: React.ReactNode }) { @@ -73,11 +73,24 @@ export function FeedProvider({ children }: { children: React.ReactNode }) {
if (feedInfo.feedType === 'bookmarks' && pubkey) {
return await switchFeed('bookmarks', { pubkey })
}
if (feedInfo.feedType === 'all-favorites') {
console.log('Initializing all-favorites feed')
return await switchFeed('all-favorites')
}
}
init()
}, [pubkey, isInitialized])
// Update relay URLs when favoriteRelays change and we're in all-favorites mode
useEffect(() => {
if (feedInfo.feedType === 'all-favorites') {
console.log('Updating relay URLs for all-favorites:', favoriteRelays)
setRelayUrls(favoriteRelays)
}
}, [favoriteRelays, feedInfo.feedType])
const switchFeed = async (
feedType: TFeedType,
options: {
@ -147,6 +160,16 @@ export function FeedProvider({ children }: { children: React.ReactNode }) { @@ -147,6 +160,16 @@ export function FeedProvider({ children }: { children: React.ReactNode }) {
setIsReady(true)
return
}
if (feedType === 'all-favorites') {
console.log('Switching to all-favorites, favoriteRelays:', favoriteRelays)
const newFeedInfo = { feedType }
setFeedInfo(newFeedInfo)
feedInfoRef.current = newFeedInfo
setRelayUrls(favoriteRelays)
storage.setFeedInfo(newFeedInfo, pubkey)
setIsReady(true)
return
}
if (feedType === 'bookmarks') {
if (!options.pubkey) {
setIsReady(true)

2
src/types/index.d.ts vendored

@ -106,7 +106,7 @@ export type TAccount = { @@ -106,7 +106,7 @@ export type TAccount = {
export type TAccountPointer = Pick<TAccount, 'pubkey' | 'signerType'>
export type TFeedType = 'following' | 'relays' | 'relay' | 'bookmarks'
export type TFeedType = 'following' | 'relays' | 'relay' | 'bookmarks' | 'all-favorites'
export type TFeedInfo = { feedType: TFeedType; id?: string }
export type TLanguage = 'en' | 'zh' | 'pl'

Loading…
Cancel
Save