Add onNoteClick support for NoteCard and EventFeed

- Introduced `onNoteClick` prop in `NoteCard` and `EventFeed` for improved interactivity.
- Implemented note selection functionality to display threads in the right panel.
- Updated router and layout to support responsive behavior when selecting notes.
This commit is contained in:
2025-10-03 14:18:50 +01:00
parent 42394c7e0c
commit 42b3e65313
3 changed files with 53 additions and 11 deletions

View File

@@ -5,9 +5,10 @@ import NoteCard from './NoteCard'
interface EventFeedProps {
feedType: 'global' | 'follows' | 'note' | 'hashtag' | 'user' | 'relay'
onNoteClick?: (event: NostrEvent, metadata?: UserMetadata | null) => void
}
const EventFeed: React.FC<EventFeedProps> = ({ feedType }) => {
const EventFeed: React.FC<EventFeedProps> = ({ feedType, onNoteClick }) => {
const [userMetadataCache, setUserMetadataCache] = useState<Map<string, UserMetadata | null>>(new Map())
const loadingRef = useRef<HTMLDivElement>(null)
@@ -143,6 +144,7 @@ const EventFeed: React.FC<EventFeedProps> = ({ feedType }) => {
key={event.id}
event={event}
userMetadata={userMetadataCache.get(event.pubkey)}
onNoteClick={onNoteClick}
/>
))}

View File

@@ -7,9 +7,10 @@ import { NostrReference, fetchReferencedEvent } from '../utils/nostrUtils'
interface NoteCardProps {
event: NostrEvent
userMetadata?: UserMetadata | null
onNoteClick?: (event: NostrEvent, metadata?: UserMetadata | null) => void
}
const NoteCard: React.FC<NoteCardProps> = ({ event, userMetadata }) => {
const NoteCard: React.FC<NoteCardProps> = ({ event, userMetadata, onNoteClick }) => {
const [showJson, setShowJson] = useState(false)
const [reactions, setReactions] = useState<NostrEvent[]>([])
const [loadingReactions, setLoadingReactions] = useState(false)
@@ -224,6 +225,7 @@ const NoteCard: React.FC<NoteCardProps> = ({ event, userMetadata }) => {
className="p-4 hover:bg-black/10 cursor-pointer transition-colors"
onMouseEnter={(e) => e.stopPropagation()}
onMouseLeave={(e) => e.stopPropagation()}
onClick={() => onNoteClick?.(event, userMetadata)}
>
{/* Header */}
<div className="flex items-center justify-between mb-3">
@@ -293,6 +295,7 @@ const NoteCard: React.FC<NoteCardProps> = ({ event, userMetadata }) => {
<NoteCard
event={repostedEvent}
userMetadata={repostedEventMetadata}
onNoteClick={onNoteClick}
/>
</div>
</div>
@@ -322,6 +325,7 @@ const NoteCard: React.FC<NoteCardProps> = ({ event, userMetadata }) => {
<NoteCard
event={fetchedData.event}
userMetadata={fetchedData.metadata}
onNoteClick={onNoteClick}
/>
</div>
)}

View File

@@ -7,8 +7,9 @@ import {
} from '@tanstack/react-router'
import { Home } from './routes/Home'
import orlyImg from '../docs/orly.png'
import { nostrService, UserMetadata } from './lib/nostr'
import { nostrService, UserMetadata, NostrEvent } from './lib/nostr'
import EventFeed from './components/EventFeed'
import NoteCard from './components/NoteCard'
const clamp = (n: number, min: number, max: number) => Math.max(min, Math.min(max, n))
@@ -65,6 +66,10 @@ const HeaderRoute = createRootRoute({
}
}, [leftPct])
// Selected note state for thread panel
const [selectedNote, setSelectedNote] = useState<NostrEvent | null>(null)
const [selectedNoteMetadata, setSelectedNoteMetadata] = useState<UserMetadata | null>(null)
// Minimal auth UI state + NIP-07 integration
const [isLoggedIn, setIsLoggedIn] = useState(false)
const [pubkey, setPubkey] = useState<string | null>(null)
@@ -76,6 +81,29 @@ const HeaderRoute = createRootRoute({
const username = userMetadata?.display_name || userMetadata?.name || (isLoggedIn ? 'you' : 'guest')
const avatarEmoji = isLoggedIn ? '🙂' : '👤'
// Handle note click to show in thread panel
const handleNoteClick = useCallback(async (event: NostrEvent, metadata?: UserMetadata | null) => {
setSelectedNote(event)
setSelectedNoteMetadata(metadata || null)
// If metadata is not provided, try to fetch it
if (!metadata && event.pubkey) {
try {
const fetchedMetadata = await nostrService.fetchUserMetadata(event.pubkey)
setSelectedNoteMetadata(fetchedMetadata)
} catch (error) {
console.warn('Failed to fetch metadata for selected note:', error)
}
}
// Handle responsive behavior
if (isSmallScreen) {
setSmallScreenPanel('thread')
} else {
setLeftPct(50)
}
}, [isSmallScreen])
// Check screen width on mount and resize
useEffect(() => {
const checkScreenWidth = () => {
@@ -513,12 +541,12 @@ const HeaderRoute = createRootRoute({
<section ref={containerRef} className="fixed top-14 left-0 right-0 bottom-0" style={{ ...gridStyle, left: sidebarWidth }}>
{/* Left: main */}
<div className="pane overflow-y-scroll">
{activeTab === 'Global' && <EventFeed feedType="global" />}
{activeTab === 'Follows' && <EventFeed feedType="follows" />}
{activeTab === 'Note' && <EventFeed feedType="note" />}
{activeTab === 'Hashtag' && <EventFeed feedType="hashtag" />}
{activeTab === 'User' && <EventFeed feedType="user" />}
{activeTab === 'Relay' && <EventFeed feedType="relay" />}
{activeTab === 'Global' && <EventFeed feedType="global" onNoteClick={handleNoteClick} />}
{activeTab === 'Follows' && <EventFeed feedType="follows" onNoteClick={handleNoteClick} />}
{activeTab === 'Note' && <EventFeed feedType="note" onNoteClick={handleNoteClick} />}
{activeTab === 'Hashtag' && <EventFeed feedType="hashtag" onNoteClick={handleNoteClick} />}
{activeTab === 'User' && <EventFeed feedType="user" onNoteClick={handleNoteClick} />}
{activeTab === 'Relay' && <EventFeed feedType="relay" onNoteClick={handleNoteClick} />}
{activeTab === 'Write' && (
<div className="h-full flex items-center justify-center">
<span className="text-xl tracking-wide">Write new note</span>
@@ -608,9 +636,17 @@ const HeaderRoute = createRootRoute({
{/* Right: thread */}
<div className="pane overflow-y-scroll bg-[#263238]">
{activeTab === 'Global' && (
{selectedNote ? (
<div className="max-w-2xl mx-auto">
<NoteCard
event={selectedNote}
userMetadata={selectedNoteMetadata}
onNoteClick={handleNoteClick}
/>
</div>
) : (
<div className="h-full flex items-center justify-center">
<span className="text-xl tracking-wide">thread</span>
<span className="text-xl tracking-wide text-gray-400">Select a note to view thread</span>
</div>
)}
</div>