Browse Source

bug-fix collapsable side panels and vite URLs

imwald
Silberengel 5 months ago
parent
commit
de7204a051
  1. 22
      src/PageManager.tsx
  2. 14
      src/components/Note/Highlight/index.tsx
  3. 19
      src/components/ProfileCard/index.tsx
  4. 18
      src/components/UserAvatar/index.tsx
  5. 14
      src/components/Username/index.tsx
  6. 1
      src/i18n/locales/en.ts
  7. 30
      src/lib/url.ts
  8. 5
      src/services/relay-selection.service.ts

22
src/PageManager.tsx

@ -11,6 +11,7 @@ import WalletPage from '@/pages/secondary/WalletPage' @@ -11,6 +11,7 @@ import WalletPage from '@/pages/secondary/WalletPage'
import PostSettingsPage from '@/pages/secondary/PostSettingsPage'
import GeneralSettingsPage from '@/pages/secondary/GeneralSettingsPage'
import TranslationPage from '@/pages/secondary/TranslationPage'
import SecondaryProfilePage from '@/pages/secondary/ProfilePage'
import { CurrentRelaysProvider } from '@/providers/CurrentRelaysProvider'
import { NotificationProvider } from '@/providers/NotificationProvider'
import { useUserPreferences } from '@/providers/UserPreferencesProvider'
@ -158,6 +159,27 @@ export function useSmartRelayNavigation() { @@ -158,6 +159,27 @@ export function useSmartRelayNavigation() {
return { navigateToRelay }
}
// Custom hook for intelligent profile navigation
export function useSmartProfileNavigation() {
const { showRecommendedRelaysPanel } = useUserPreferences()
const { push: pushSecondary } = useSecondaryPage()
const { setPrimaryNoteView } = usePrimaryNoteView()
const navigateToProfile = (url: string) => {
if (!showRecommendedRelaysPanel) {
// When right panel is hidden, show profile in primary area
// Extract profile ID from URL (e.g., "/users/npub1..." -> "npub1...")
const profileId = url.replace('/users/', '')
setPrimaryNoteView(<SecondaryProfilePage id={profileId} index={0} hideTitlebar={true} />, 'settings')
} else {
// Normal behavior - use secondary navigation
pushSecondary(url)
}
}
return { navigateToProfile }
}
// Custom hook for intelligent settings navigation
export function useSmartSettingsNavigation() {
const { showRecommendedRelaysPanel } = useUserPreferences()

14
src/components/Note/Highlight/index.tsx

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { SecondaryPageLink } from '@/PageManager'
import { useSmartNoteNavigation } from '@/PageManager'
import { Event } from 'nostr-tools'
import { ExternalLink, Highlighter } from 'lucide-react'
import { useTranslation } from 'react-i18next'
@ -13,6 +13,7 @@ export default function Highlight({ @@ -13,6 +13,7 @@ export default function Highlight({
className?: string
}) {
const { t } = useTranslation()
const { navigateToNote } = useSmartNoteNavigation()
try {
@ -132,15 +133,18 @@ export default function Highlight({ @@ -132,15 +133,18 @@ export default function Highlight({
<ExternalLink className="w-3 h-3" />
</a>
) : (
<SecondaryPageLink
to={toNote(source.bech32)}
className="text-blue-500 hover:underline font-mono"
<span
onClick={(e) => {
e.stopPropagation()
navigateToNote(toNote(source.bech32))
}}
className="text-blue-500 hover:underline font-mono cursor-pointer"
>
{source.type === 'event'
? `note1${source.bech32.substring(5, 13)}...`
: `naddr1${source.bech32.substring(6, 14)}...`
}
</SecondaryPageLink>
</span>
)}
</div>
)}

19
src/components/ProfileCard/index.tsx

@ -1,4 +1,9 @@ @@ -1,4 +1,9 @@
import { Button } from '@/components/ui/button'
import { useFetchProfile } from '@/hooks'
import { toProfile } from '@/lib/link'
import { useSmartProfileNavigation } from '@/PageManager'
import { UserRound } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import FollowButton from '../FollowButton'
import Nip05 from '../Nip05'
import ProfileAbout from '../ProfileAbout'
@ -7,6 +12,8 @@ import { SimpleUserAvatar } from '../UserAvatar' @@ -7,6 +12,8 @@ import { SimpleUserAvatar } from '../UserAvatar'
export default function ProfileCard({ pubkey }: { pubkey: string }) {
const { profile } = useFetchProfile(pubkey)
const { username, about } = profile || {}
const { navigateToProfile } = useSmartProfileNavigation()
const { t } = useTranslation()
return (
<div className="w-full flex flex-col gap-2 not-prose">
@ -24,6 +31,18 @@ export default function ProfileCard({ pubkey }: { pubkey: string }) { @@ -24,6 +31,18 @@ export default function ProfileCard({ pubkey }: { pubkey: string }) {
className="text-sm text-wrap break-words w-full overflow-hidden text-ellipsis line-clamp-6"
/>
)}
<Button
variant="outline"
size="sm"
className="w-full mt-2"
onClick={(e) => {
e.stopPropagation()
navigateToProfile(toProfile(pubkey))
}}
>
<UserRound className="w-4 h-4 mr-2" />
{t('View full profile')}
</Button>
</div>
)
}

18
src/components/UserAvatar/index.tsx

@ -2,10 +2,8 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' @@ -2,10 +2,8 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
import { Skeleton } from '@/components/ui/skeleton'
import { useFetchProfile } from '@/hooks'
import { toProfile } from '@/lib/link'
import { generateImageByPubkey } from '@/lib/pubkey'
import { cn } from '@/lib/utils'
import { SecondaryPageLink } from '@/PageManager'
import { useMemo } from 'react'
import ProfileCard from '../ProfileCard'
@ -44,15 +42,13 @@ export default function UserAvatar({ @@ -44,15 +42,13 @@ export default function UserAvatar({
return (
<HoverCard>
<HoverCardTrigger>
<SecondaryPageLink to={toProfile(pubkey)} onClick={(e) => e.stopPropagation()}>
<Avatar className={cn('shrink-0', UserAvatarSizeCnMap[size], className)}>
<AvatarImage src={avatar} className="object-cover object-center" />
<AvatarFallback>
<img src={defaultAvatar} alt={pubkey} />
</AvatarFallback>
</Avatar>
</SecondaryPageLink>
<HoverCardTrigger asChild>
<Avatar className={cn('shrink-0 cursor-pointer', UserAvatarSizeCnMap[size], className)}>
<AvatarImage src={avatar} className="object-cover object-center" />
<AvatarFallback>
<img src={defaultAvatar} alt={pubkey} />
</AvatarFallback>
</Avatar>
</HoverCardTrigger>
<HoverCardContent className="w-72">
<ProfileCard pubkey={pubkey} />

14
src/components/Username/index.tsx

@ -1,9 +1,7 @@ @@ -1,9 +1,7 @@
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
import { Skeleton } from '@/components/ui/skeleton'
import { useFetchProfile } from '@/hooks'
import { toProfile } from '@/lib/link'
import { cn } from '@/lib/utils'
import { SecondaryPageLink } from '@/PageManager'
import ProfileCard from '../ProfileCard'
export default function Username({
@ -34,15 +32,9 @@ export default function Username({ @@ -34,15 +32,9 @@ export default function Username({
return (
<HoverCard>
<HoverCardTrigger asChild>
<div className={className}>
<SecondaryPageLink
to={toProfile(pubkey)}
className="truncate hover:underline"
onClick={(e) => e.stopPropagation()}
>
{showAt && '@'}
{username}
</SecondaryPageLink>
<div className={cn('truncate hover:underline cursor-pointer', className)}>
{showAt && '@'}
{username}
</div>
</HoverCardTrigger>
<HoverCardContent className="w-80">

1
src/i18n/locales/en.ts

@ -45,6 +45,7 @@ export default { @@ -45,6 +45,7 @@ export default {
'Copy event ID': 'Copy event ID',
'Copy user ID': 'Copy user ID',
'View raw event': 'View raw event',
'View full profile': 'View full profile',
Like: 'Like',
'switch to light theme': 'switch to light theme',
'switch to dark theme': 'switch to dark theme',

30
src/lib/url.ts

@ -12,7 +12,21 @@ export function normalizeUrl(url: string): string { @@ -12,7 +12,21 @@ export function normalizeUrl(url: string): string {
url = 'wss://' + url
}
}
// Parse the URL first to validate it
const p = new URL(url)
// Check if URL has query parameters or hash fragments that suggest it's not a relay
// Relay URLs shouldn't have query params like ?token= or hash fragments
const hasQueryParams = url.includes('?')
const hasHashFragment = url.includes('#')
// Block URLs with query params or hash fragments (these are likely not relays)
if (hasQueryParams || hasHashFragment) {
console.warn('Skipping URL with query/hash (not a relay):', url)
return ''
}
p.pathname = p.pathname.replace(/\/+/g, '/')
if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1)
if (p.protocol === 'https:') {
@ -21,6 +35,12 @@ export function normalizeUrl(url: string): string { @@ -21,6 +35,12 @@ export function normalizeUrl(url: string): string {
p.protocol = 'ws:'
}
// After protocol normalization, validate it's actually a websocket URL
if (!isWebsocketUrl(p.toString())) {
console.warn('Skipping non-websocket URL:', url)
return ''
}
// Normalize localhost and local network addresses to always use ws:// instead of wss://
// This fixes the common typo where people use wss:// for local relays
if (isLocalNetworkUrl(p.toString())) {
@ -32,7 +52,15 @@ export function normalizeUrl(url: string): string { @@ -32,7 +52,15 @@ export function normalizeUrl(url: string): string {
}
p.searchParams.sort()
p.hash = ''
return p.toString()
// Final validation: ensure we have a proper websocket URL
const finalUrl = p.toString()
if (!isWebsocketUrl(finalUrl)) {
console.warn('Normalization resulted in invalid websocket URL:', finalUrl)
return ''
}
return finalUrl
} catch {
console.error('Invalid URL:', url)
return ''

5
src/services/relay-selection.service.ts

@ -86,9 +86,8 @@ class RelaySelectionService { @@ -86,9 +86,8 @@ class RelaySelectionService {
if (normalized) {
selectableRelays.add(normalized)
} else {
// If normalization fails, add the original URL but log a warning
console.warn('Failed to normalize relay URL:', url)
selectableRelays.add(url)
// If normalization fails or returns empty (invalid URL), skip it
console.warn('Skipping invalid relay URL:', url)
}
}

Loading…
Cancel
Save