Browse Source

bug-fixes

imwald
Silberengel 1 month ago
parent
commit
c2bbf52aac
  1. 3
      src/components/PostEditor/PostContent.tsx
  2. 5
      src/components/PostEditor/PostRelaySelector.tsx
  3. 18
      src/components/PostEditor/PostTextarea/Preview.tsx
  4. 16
      src/components/PostEditor/PostTextarea/index.tsx
  5. 10
      src/components/Sidebar/AccountButton.tsx
  6. 3
      src/constants.ts
  7. 2
      src/lib/kind-description.ts
  8. 1364
      src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx
  9. 5
      src/pages/primary/DiscussionsPage/index.tsx
  10. 8
      src/pages/primary/NoteListPage/index.tsx
  11. 30
      src/providers/FavoriteRelaysProvider.tsx
  12. 24
      src/providers/FeedProvider.tsx
  13. 34
      src/providers/favorite-relays-context.tsx
  14. 30
      src/providers/feed-context.tsx

3
src/components/PostEditor/PostContent.tsx

@ -2432,9 +2432,6 @@ export default function PostContent({
</NeventPickerProvider> </NeventPickerProvider>
{createThreadOpen && ( {createThreadOpen && (
<CreateThreadDialog <CreateThreadDialog
topic="general"
availableRelays={[]}
relaySets={[]}
onClose={() => setCreateThreadOpen(false)} onClose={() => setCreateThreadOpen(false)}
onThreadCreated={() => { onThreadCreated={() => {
discussionFeedCache.clearDiscussionsListCache() discussionFeedCache.clearDiscussionsListCache()

5
src/components/PostEditor/PostRelaySelector.tsx

@ -18,6 +18,9 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet' import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'
import logger from '@/lib/logger' import logger from '@/lib/logger'
/** Stable default when `mentions` is omitted — inline `= []` is a new array every render and retriggers effects. */
const NO_MENTIONS: string[] = []
export default function PostRelaySelector({ export default function PostRelaySelector({
parentEvent: _parentEvent, parentEvent: _parentEvent,
openFrom, openFrom,
@ -25,7 +28,7 @@ export default function PostRelaySelector({
setAdditionalRelayUrls, setAdditionalRelayUrls,
content: postContent = '', content: postContent = '',
isPublicMessage = false, isPublicMessage = false,
mentions = [] mentions = NO_MENTIONS
}: { }: {
parentEvent?: NostrEvent parentEvent?: NostrEvent
openFrom?: string[] openFrom?: string[]

18
src/components/PostEditor/PostTextarea/Preview.tsx

@ -25,7 +25,8 @@ export default function Preview({
pollCreateData, pollCreateData,
mediaImetaTags, mediaImetaTags,
mediaUrl, mediaUrl,
articleMetadata articleMetadata,
extraPreviewTags
}: { }: {
content: string content: string
className?: string className?: string
@ -41,6 +42,8 @@ export default function Preview({
dTag?: string dTag?: string
topics?: string[] topics?: string[]
} }
/** Merged into the fake event (e.g. kind 11 discussion title / topic tags). */
extraPreviewTags?: string[][]
}) { }) {
const { content: processedContent, emojiTags, highlightTags, pollTags } = useMemo( const { content: processedContent, emojiTags, highlightTags, pollTags } = useMemo(
() => { () => {
@ -148,8 +151,11 @@ export default function Preview({
tags.push(...normalizedTopics.map((topic) => ['t', topic])) tags.push(...normalizedTopics.map((topic) => ['t', topic]))
} }
} }
if (extraPreviewTags?.length) {
tags.push(...extraPreviewTags)
}
return tags return tags
}, [emojiTags, highlightTags, pollTags, mediaImetaTags, articleMetadata, kind]) }, [emojiTags, highlightTags, pollTags, mediaImetaTags, articleMetadata, kind, extraPreviewTags])
const fakeEvent = useMemo(() => { const fakeEvent = useMemo(() => {
// For voice comments, include the media URL in content if not already there // For voice comments, include the media URL in content if not already there
@ -194,6 +200,14 @@ export default function Preview({
) )
} }
if (kind === ExtendedKind.DISCUSSION) {
return (
<Card className={cn('p-3', className, selectableClass)}>
<MarkdownArticle event={fakeEvent} hideMetadata={true} />
</Card>
)
}
// For LongFormArticle, use MarkdownArticle // For LongFormArticle, use MarkdownArticle
if (kind === kinds.LongFormArticle) { if (kind === kinds.LongFormArticle) {
return ( return (

16
src/components/PostEditor/PostTextarea/index.tsx

@ -58,6 +58,7 @@ const PostTextarea = forwardRef<
dTag?: string dTag?: string
topics?: string[] topics?: string[]
} }
extraPreviewTags?: string[][]
} }
>( >(
( (
@ -78,7 +79,8 @@ const PostTextarea = forwardRef<
getDraftEventJson, getDraftEventJson,
mediaImetaTags, mediaImetaTags,
mediaUrl, mediaUrl,
articleMetadata articleMetadata,
extraPreviewTags
}, },
ref ref
) => { ) => {
@ -243,7 +245,17 @@ const PostTextarea = forwardRef<
<div className="text-xs text-muted-foreground"> <div className="text-xs text-muted-foreground">
kind {kindDescription.number}: {kindDescription.description} kind {kindDescription.number}: {kindDescription.description}
</div> </div>
<Preview content={text} className={className} kind={kind} highlightData={highlightData} pollCreateData={pollCreateData} mediaImetaTags={mediaImetaTags} mediaUrl={mediaUrl} articleMetadata={articleMetadata} /> <Preview
content={text}
className={className}
kind={kind}
highlightData={highlightData}
pollCreateData={pollCreateData}
mediaImetaTags={mediaImetaTags}
mediaUrl={mediaUrl}
articleMetadata={articleMetadata}
extraPreviewTags={extraPreviewTags}
/>
</div> </div>
</TabsContent> </TabsContent>
<TabsContent value="json"> <TabsContent value="json">

10
src/components/Sidebar/AccountButton.tsx

@ -7,11 +7,10 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger DropdownMenuTrigger
} from '@/components/ui/dropdown-menu' } from '@/components/ui/dropdown-menu'
import { toWallet } from '@/lib/link'
import { formatPubkey, generateImageByPubkey, pubkeyToNpub, formatNpub } from '@/lib/pubkey' import { formatPubkey, generateImageByPubkey, pubkeyToNpub, formatNpub } from '@/lib/pubkey'
import { usePrimaryPage, useSecondaryPage } from '@/PageManager' import { usePrimaryPage } from '@/PageManager'
import { useNostr } from '@/providers/NostrProvider' import { useNostr } from '@/providers/NostrProvider'
import { ArrowDownUp, LogIn, LogOut, MoreVertical, Settings, Wallet } from 'lucide-react' import { ArrowDownUp, LogIn, LogOut, MoreVertical, Settings } from 'lucide-react'
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import LoginDialog from '../LoginDialog' import LoginDialog from '../LoginDialog'
@ -33,7 +32,6 @@ function ProfileButton() {
const { account, profile } = useNostr() const { account, profile } = useNostr()
const pubkey = account?.pubkey const pubkey = account?.pubkey
const { navigate } = usePrimaryPage() const { navigate } = usePrimaryPage()
const { push } = useSecondaryPage()
const [loginDialogOpen, setLoginDialogOpen] = useState(false) const [loginDialogOpen, setLoginDialogOpen] = useState(false)
const [logoutDialogOpen, setLogoutDialogOpen] = useState(false) const [logoutDialogOpen, setLogoutDialogOpen] = useState(false)
if (!pubkey) return null if (!pubkey) return null
@ -74,10 +72,6 @@ function ProfileButton() {
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent side="top" align="end"> <DropdownMenuContent side="top" align="end">
<DropdownMenuItem onClick={() => push(toWallet())}>
<Wallet />
{t('Wallet')}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => navigate('settings')}> <DropdownMenuItem onClick={() => navigate('settings')}>
<Settings /> <Settings />
{t('Settings')} {t('Settings')}

3
src/constants.ts

@ -132,7 +132,8 @@ export const KIND_1_BLOCKED_RELAY_URLS = [
'wss://thecitadel.nostr1.com', 'wss://thecitadel.nostr1.com',
'wss://hist.nostr.land', 'wss://hist.nostr.land',
'wss://profiles.nostr1.com', 'wss://profiles.nostr1.com',
'wss://purplepag.es' 'wss://purplepag.es',
'wss://wikifreedia.xyz'
] ]
// Optimized relay list for read operations (includes aggregator) // Optimized relay list for read operations (includes aggregator)

2
src/lib/kind-description.ts

@ -44,6 +44,8 @@ export function getKindDescription(kind: number): { number: number; description:
return { number: 1068, description: 'Poll' } return { number: 1068, description: 'Poll' }
case ExtendedKind.PUBLIC_MESSAGE: case ExtendedKind.PUBLIC_MESSAGE:
return { number: 24, description: 'Public Message' } return { number: 24, description: 'Public Message' }
case ExtendedKind.DISCUSSION:
return { number: 11, description: 'Discussion' }
default: default:
return { number: kind, description: `Event (kind ${kind})` } return { number: kind, description: `Event (kind ${kind})` }
} }

1364
src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx

File diff suppressed because it is too large Load Diff

5
src/pages/primary/DiscussionsPage/index.tsx

@ -1234,12 +1234,9 @@ const DiscussionsPage = forwardRef<TPageRef, { embedded?: boolean }>(function Di
{/* Create Thread Dialog */} {/* Create Thread Dialog */}
{showCreateDialog && ( {showCreateDialog && (
<CreateThreadDialog <CreateThreadDialog
topic="general"
availableRelays={[]}
relaySets={[]}
dynamicTopics={dynamicTopics} dynamicTopics={dynamicTopics}
onClose={handleCloseDialog} onClose={handleCloseDialog}
onThreadCreated={handleCreateThread} onThreadCreated={handleCreateThread}
/> />
)} )}
</> </>

8
src/pages/primary/NoteListPage/index.tsx

@ -173,8 +173,12 @@ function NoteListPageTitlebar({
<Info /> <Info />
</Button> </Button>
)} )}
<KeyboardShortcutsHelpButton /> {isSmallScreen && (
<AccountButton /> <>
<KeyboardShortcutsHelpButton />
<AccountButton />
</>
)}
</div> </div>
</div> </div>
) )

30
src/providers/FavoriteRelaysProvider.tsx

@ -9,34 +9,12 @@ import indexedDb from '@/services/indexed-db.service'
import storage from '@/services/local-storage.service' import storage from '@/services/local-storage.service'
import { TRelaySet } from '@/types' import { TRelaySet } from '@/types'
import { Event, kinds } from 'nostr-tools' import { Event, kinds } from 'nostr-tools'
import { createContext, useContext, useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { FavoriteRelaysContext } from './favorite-relays-context'
import { useNostr } from './NostrProvider' import { useNostr } from './NostrProvider'
type TFavoriteRelaysContext = { export { useFavoriteRelays } from './favorite-relays-context'
favoriteRelays: string[] export type { TFavoriteRelaysContext } from './favorite-relays-context'
addFavoriteRelays: (relayUrls: string[]) => Promise<void>
deleteFavoriteRelays: (relayUrls: string[]) => Promise<void>
reorderFavoriteRelays: (reorderedRelays: string[]) => Promise<void>
blockedRelays: string[]
addBlockedRelays: (relayUrls: string[]) => Promise<void>
deleteBlockedRelays: (relayUrls: string[]) => Promise<void>
relaySets: TRelaySet[]
createRelaySet: (relaySetName: string, relayUrls?: string[]) => Promise<void>
addRelaySets: (newRelaySetEvents: Event[]) => Promise<void>
deleteRelaySet: (id: string) => Promise<void>
updateRelaySet: (newSet: TRelaySet) => Promise<void>
reorderRelaySets: (reorderedSets: TRelaySet[]) => Promise<void>
}
const FavoriteRelaysContext = createContext<TFavoriteRelaysContext | undefined>(undefined)
export const useFavoriteRelays = () => {
const context = useContext(FavoriteRelaysContext)
if (!context) {
throw new Error('useFavoriteRelays must be used within a FavoriteRelaysProvider')
}
return context
}
export function FavoriteRelaysProvider({ children }: { children: React.ReactNode }) { export function FavoriteRelaysProvider({ children }: { children: React.ReactNode }) {
const { favoriteRelaysEvent, blockedRelaysEvent, updateFavoriteRelaysEvent, updateBlockedRelaysEvent, pubkey, relayList, publish } = useNostr() const { favoriteRelaysEvent, blockedRelaysEvent, updateFavoriteRelaysEvent, updateBlockedRelaysEvent, pubkey, relayList, publish } = useNostr()

24
src/providers/FeedProvider.tsx

@ -6,29 +6,13 @@ import indexedDb from '@/services/indexed-db.service'
import storage from '@/services/local-storage.service' import storage from '@/services/local-storage.service'
import { TFeedInfo, TFeedType } from '@/types' import { TFeedInfo, TFeedType } from '@/types'
import { kinds } from 'nostr-tools' import { kinds } from 'nostr-tools'
import { createContext, useContext, useEffect, useRef, useState, useCallback } from 'react' import { useEffect, useRef, useState, useCallback } from 'react'
import { FeedContext } from './feed-context'
import { useFavoriteRelays } from './FavoriteRelaysProvider' import { useFavoriteRelays } from './FavoriteRelaysProvider'
import { useNostr } from './NostrProvider' import { useNostr } from './NostrProvider'
type TFeedContext = { export { useFeed } from './feed-context'
feedInfo: TFeedInfo export type { TFeedContext } from './feed-context'
relayUrls: string[]
isReady: boolean
switchFeed: (
feedType: TFeedType,
options?: { activeRelaySetId?: string; pubkey?: string; relay?: string | null }
) => Promise<void>
}
const FeedContext = createContext<TFeedContext | undefined>(undefined)
export const useFeed = () => {
const context = useContext(FeedContext)
if (!context) {
throw new Error('useFeed must be used within a FeedProvider')
}
return context
}
export function FeedProvider({ children }: { children: React.ReactNode }) { export function FeedProvider({ children }: { children: React.ReactNode }) {
const { pubkey, isInitialized } = useNostr() const { pubkey, isInitialized } = useNostr()

34
src/providers/favorite-relays-context.tsx

@ -0,0 +1,34 @@
/**
* Standalone React context for favorite relays so HMR on `FavoriteRelaysProvider.tsx` does not
* recreate `createContext()` (which breaks `useFavoriteRelays` in InterestListProvider,
* FeedProvider, etc. after Fast Refresh).
*/
import { TRelaySet } from '@/types'
import { Event } from 'nostr-tools'
import { createContext, useContext } from 'react'
export type TFavoriteRelaysContext = {
favoriteRelays: string[]
addFavoriteRelays: (relayUrls: string[]) => Promise<void>
deleteFavoriteRelays: (relayUrls: string[]) => Promise<void>
reorderFavoriteRelays: (reorderedRelays: string[]) => Promise<void>
blockedRelays: string[]
addBlockedRelays: (relayUrls: string[]) => Promise<void>
deleteBlockedRelays: (relayUrls: string[]) => Promise<void>
relaySets: TRelaySet[]
createRelaySet: (relaySetName: string, relayUrls?: string[]) => Promise<void>
addRelaySets: (newRelaySetEvents: Event[]) => Promise<void>
deleteRelaySet: (id: string) => Promise<void>
updateRelaySet: (newSet: TRelaySet) => Promise<void>
reorderRelaySets: (reorderedSets: TRelaySet[]) => Promise<void>
}
export const FavoriteRelaysContext = createContext<TFavoriteRelaysContext | undefined>(undefined)
export function useFavoriteRelays(): TFavoriteRelaysContext {
const context = useContext(FavoriteRelaysContext)
if (!context) {
throw new Error('useFavoriteRelays must be used within a FavoriteRelaysProvider')
}
return context
}

30
src/providers/feed-context.tsx

@ -0,0 +1,30 @@
/**
* Standalone React context for feed state so HMR on `FeedProvider.tsx` does not recreate
* `createContext()` (which breaks `useFeed` after Fast Refresh).
*/
import { TFeedInfo, TFeedType } from '@/types'
import { createContext, useContext } from 'react'
export type TFeedContext = {
feedInfo: TFeedInfo
relayUrls: string[]
isReady: boolean
switchFeed: (
feedType: TFeedType,
options?: {
activeRelaySetId?: string | null
pubkey?: string | null
relay?: string | null
}
) => Promise<void>
}
export const FeedContext = createContext<TFeedContext | undefined>(undefined)
export function useFeed(): TFeedContext {
const context = useContext(FeedContext)
if (!context) {
throw new Error('useFeed must be used within a FeedProvider')
}
return context
}
Loading…
Cancel
Save