Browse Source

working discussions

imwald
Silberengel 5 months ago
parent
commit
93ac4f7678
  1. 5
      src/components/ContentPreview/index.tsx
  2. 2
      src/components/Note/index.tsx
  3. 31
      src/pages/primary/DiscussionsPage/CreateThreadDialog.tsx
  4. 9
      src/pages/primary/DiscussionsPage/ThreadCard.tsx
  5. 25
      src/pages/primary/DiscussionsPage/TopicFilter.tsx
  6. 45
      src/pages/primary/DiscussionsPage/index.tsx

5
src/components/ContentPreview/index.tsx

@ -15,6 +15,7 @@ import NormalContentPreview from './NormalContentPreview' @@ -15,6 +15,7 @@ import NormalContentPreview from './NormalContentPreview'
import PictureNotePreview from './PictureNotePreview'
import PollPreview from './PollPreview'
import VideoNotePreview from './VideoNotePreview'
import DiscussionNote from '../DiscussionNote'
export default function ContentPreview({
event,
@ -69,6 +70,10 @@ export default function ContentPreview({ @@ -69,6 +70,10 @@ export default function ContentPreview({
return <NormalContentPreview event={event} className={className} />
}
if (event.kind === ExtendedKind.DISCUSSION) {
return <DiscussionNote event={event} className={className} size="small" />
}
if (event.kind === kinds.Highlights) {
return <HighlightPreview event={event} className={className} />
}

2
src/components/Note/index.tsx

@ -87,6 +87,8 @@ export default function Note({ @@ -87,6 +87,8 @@ export default function Note({
content = <GroupMetadata className="mt-2" event={event} originalNoteId={originalNoteId} />
} else if (event.kind === kinds.CommunityDefinition) {
content = <CommunityDefinition className="mt-2" event={event} />
} else if (event.kind === ExtendedKind.DISCUSSION) {
content = <DiscussionNote className="mt-2" event={event} size={size} />
} else if (event.kind === ExtendedKind.POLL) {
content = (
<>

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

@ -151,12 +151,22 @@ export default function CreateThreadDialog({ @@ -151,12 +151,22 @@ export default function CreateThreadDialog({
created_at: dayjs().unix()
}
console.log('Creating kind 11 thread event:', {
kind: threadEvent.kind,
content: threadEvent.content.substring(0, 50) + '...',
tags: threadEvent.tags,
selectedRelay,
minPow
})
// Publish to the selected relay only
const publishedEvent = await publish(threadEvent, {
specifiedRelayUrls: [selectedRelay],
minPow
})
console.log('Published event result:', publishedEvent)
if (publishedEvent) {
onThreadCreated()
onClose()
@ -165,7 +175,26 @@ export default function CreateThreadDialog({ @@ -165,7 +175,26 @@ export default function CreateThreadDialog({
}
} catch (error) {
console.error('Error creating thread:', error)
alert(t('Failed to create thread. Please try again.'))
console.error('Error details:', {
name: error instanceof Error ? error.name : 'Unknown',
message: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined
})
let errorMessage = t('Failed to create thread')
if (error instanceof Error) {
if (error.message.includes('auth-required') || error.message.includes('auth required')) {
errorMessage = t('Relay requires authentication for write access. Please try a different relay or contact the relay operator.')
} else if (error.message.includes('blocked')) {
errorMessage = t('Your account is blocked from posting to this relay.')
} else if (error.message.includes('rate limit')) {
errorMessage = t('Rate limited. Please wait before trying again.')
} else {
errorMessage = `${t('Failed to create thread')}: ${error.message}`
}
}
alert(errorMessage)
} finally {
setIsSubmitting(false)
}

9
src/pages/primary/DiscussionsPage/ThreadCard.tsx

@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next' @@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next'
import { cn } from '@/lib/utils'
import { truncateText } from '@/lib/utils'
import { DISCUSSION_TOPICS } from './CreateThreadDialog'
import Username from '@/components/Username'
interface ThreadWithRelaySource extends NostrEvent {
_relaySource?: string
@ -105,9 +106,11 @@ export default function ThreadCard({ thread, onThreadClick, className }: ThreadC @@ -105,9 +106,11 @@ export default function ThreadCard({ thread, onThreadClick, className }: ThreadC
<div className="flex items-center justify-between mt-3 pt-3 border-t">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<User className="w-4 h-4" />
<span className="truncate">
{thread.pubkey.slice(0, 8)}...{thread.pubkey.slice(-8)}
</span>
<Username
userId={thread.pubkey}
className="truncate font-medium"
skeletonClassName="h-4 w-20"
/>
</div>
<Button variant="ghost" size="sm" className="h-8 px-2">
{t('Read more')}

25
src/pages/primary/DiscussionsPage/TopicFilter.tsx

@ -1,8 +1,9 @@ @@ -1,8 +1,9 @@
import { Button } from '@/components/ui/button'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
import { ChevronDown } from 'lucide-react'
import { ChevronDown, Grid3X3 } from 'lucide-react'
import { NostrEvent } from 'nostr-tools'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
interface Topic {
id: string
@ -19,6 +20,8 @@ interface TopicFilterProps { @@ -19,6 +20,8 @@ interface TopicFilterProps {
}
export default function TopicFilter({ topics, selectedTopic, onTopicChange, threads, replies }: TopicFilterProps) {
const { t } = useTranslation()
// Sort topics by activity (most recent kind 11 or kind 1111 events first)
const sortedTopics = useMemo(() => {
const allEvents = [...threads, ...replies]
@ -47,7 +50,12 @@ export default function TopicFilter({ topics, selectedTopic, onTopicChange, thre @@ -47,7 +50,12 @@ export default function TopicFilter({ topics, selectedTopic, onTopicChange, thre
})
}, [topics, threads, replies])
const selectedTopicInfo = sortedTopics.find(topic => topic.id === selectedTopic) || sortedTopics[0]
// Create all topics option
const allTopicsOption = { id: 'all', label: t('All Topics'), icon: Grid3X3 }
const selectedTopicInfo = selectedTopic === 'all'
? allTopicsOption
: sortedTopics.find(topic => topic.id === selectedTopic) || sortedTopics[0]
return (
<DropdownMenu>
@ -57,11 +65,22 @@ export default function TopicFilter({ topics, selectedTopic, onTopicChange, thre @@ -57,11 +65,22 @@ export default function TopicFilter({ topics, selectedTopic, onTopicChange, thre
className="flex items-center gap-2 h-10 px-3 min-w-44"
>
<selectedTopicInfo.icon className="w-4 h-4" />
<span className="flex-1 text-left">{selectedTopicInfo.id}</span>
<span className="flex-1 text-left">{selectedTopicInfo.label}</span>
<ChevronDown className="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-72">
<DropdownMenuItem
key="all"
onClick={() => onTopicChange('all')}
className="flex items-center gap-2"
>
<Grid3X3 className="w-4 h-4" />
<span>{t('All Topics')}</span>
{selectedTopic === 'all' && (
<span className="ml-auto text-primary"></span>
)}
</DropdownMenuItem>
{sortedTopics.map(topic => (
<DropdownMenuItem
key={topic.id}

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

@ -48,13 +48,14 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -48,13 +48,14 @@ const DiscussionsPage = forwardRef((_, ref) => {
const relayUrls = selectedRelay ? [selectedRelay] : availableRelays
// Fetch all kind 11 events (limit 100, newest first) with relay source tracking
console.log('Fetching kind 11 events from relays:', relayUrls)
const events = await client.fetchEvents(relayUrls, [
{
kinds: [11], // Thread events
'#-': ['-'], // Must have the "-" tag for relay privacy
limit: 100
}
])
console.log('Fetched kind 11 events:', events.length, events.map(e => ({ id: e.id, title: e.tags.find(t => t[0] === 'title')?.[1], pubkey: e.pubkey })))
// Filter and sort threads, adding relay source information
const validThreads = events
@ -99,14 +100,20 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -99,14 +100,20 @@ const DiscussionsPage = forwardRef((_, ref) => {
}
})
// Filter threads for the selected topic
const threadsForTopic = categorizedThreads
.filter(thread => thread._categorizedTopic === selectedTopic)
.map(thread => {
// Remove the temporary categorization property but keep relay source
const { _categorizedTopic, ...cleanThread } = thread
return cleanThread
})
// Filter threads for the selected topic (or show all if "all" is selected)
const threadsForTopic = selectedTopic === 'all'
? categorizedThreads.map(thread => {
// Remove the temporary categorization property but keep relay source
const { _categorizedTopic, ...cleanThread } = thread
return cleanThread
})
: categorizedThreads
.filter(thread => thread._categorizedTopic === selectedTopic)
.map(thread => {
// Remove the temporary categorization property but keep relay source
const { _categorizedTopic, ...cleanThread } = thread
return cleanThread
})
setThreads(threadsForTopic)
}
@ -166,7 +173,7 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -166,7 +173,7 @@ const DiscussionsPage = forwardRef((_, ref) => {
<div className="p-4 space-y-4">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold">
{t('Discussions')} - {DISCUSSION_TOPICS.find(t => t.id === selectedTopic)?.label}
{t('Discussions')} - {selectedTopic === 'all' ? t('All Topics') : DISCUSSION_TOPICS.find(t => t.id === selectedTopic)?.label}
</h1>
</div>
@ -180,12 +187,20 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -180,12 +187,20 @@ const DiscussionsPage = forwardRef((_, ref) => {
<MessageSquarePlus className="w-12 h-12 mx-auto mb-4 text-muted-foreground" />
<h3 className="text-lg font-semibold mb-2">{t('No threads yet')}</h3>
<p className="text-muted-foreground mb-4">
{t('Be the first to start a discussion in this topic!')}
{selectedTopic === 'all'
? t('No discussion threads found. Try refreshing or check your relay connection.')
: t('Be the first to start a discussion in this topic!')
}
</p>
<Button onClick={handleCreateThread}>
<MessageSquarePlus className="w-4 h-4 mr-2" />
{t('Create Thread')}
</Button>
<div className="flex gap-2 justify-center">
<Button onClick={handleCreateThread}>
<MessageSquarePlus className="w-4 h-4 mr-2" />
{t('Create Thread')}
</Button>
<Button variant="outline" onClick={fetchAllThreads}>
{t('Refresh')}
</Button>
</div>
</CardContent>
</Card>
) : (

Loading…
Cancel
Save