Implement threaded post structure in NostrFeed module

- Introduced a new ThreadedPost component to display posts and their replies in a nested format, enhancing the readability of discussions.
- Updated the useFeed composable to include a computed property for building threaded posts from flat post data.
- Modified FeedService to support the extraction of reply information and build a hierarchical structure for posts, allowing for better organization of replies.
- Removed deprecated rideshare-related functions from NostrFeed.vue, streamlining the component and focusing on the threaded view.

These changes improve the user experience by facilitating clearer interactions within post discussions, promoting engagement and organization in the NostrFeed module.
This commit is contained in:
padreug 2025-09-20 11:44:22 +02:00
parent c027b9ac45
commit ebc7885f04
4 changed files with 374 additions and 150 deletions

View file

@ -13,7 +13,10 @@ export interface FeedPost {
tags: string[][]
mentions: string[]
isReply: boolean
replyTo?: string
replyTo?: string // Direct parent ID
rootId?: string // Thread root ID
replies?: FeedPost[] // Child replies
depth?: number // Depth in reply tree (0 for root posts)
}
export interface ContentFilter {
@ -234,6 +237,36 @@ export class FeedService extends BaseService {
return
}
// Extract reply information according to NIP-10
let rootId: string | undefined
let replyTo: string | undefined
let isReply = false
// Look for marked e-tags first (preferred method)
const markedRootTag = event.tags?.find((tag: string[]) => tag[0] === 'e' && tag[3] === 'root')
const markedReplyTag = event.tags?.find((tag: string[]) => tag[0] === 'e' && tag[3] === 'reply')
if (markedRootTag || markedReplyTag) {
// Using marked tags (NIP-10 preferred method)
rootId = markedRootTag?.[1]
replyTo = markedReplyTag?.[1] || markedRootTag?.[1] // Direct reply to root if no reply tag
isReply = true
} else {
// Fallback to positional tags (deprecated but still in use)
const eTags = event.tags?.filter((tag: string[]) => tag[0] === 'e') || []
if (eTags.length === 1) {
// Single e-tag means this is a direct reply
replyTo = eTags[0][1]
rootId = eTags[0][1]
isReply = true
} else if (eTags.length >= 2) {
// Multiple e-tags: first is root, last is direct reply
rootId = eTags[0][1]
replyTo = eTags[eTags.length - 1][1]
isReply = true
}
}
// Transform to FeedPost
const post: FeedPost = {
id: event.id,
@ -243,8 +276,11 @@ export class FeedService extends BaseService {
kind: event.kind,
tags: event.tags || [],
mentions: event.tags?.filter((tag: string[]) => tag[0] === 'p').map((tag: string[]) => tag[1]) || [],
isReply: event.tags?.some((tag: string[]) => tag[0] === 'e' && tag[3] === 'reply') || false,
replyTo: event.tags?.find((tag: string[]) => tag[0] === 'e' && tag[3] === 'reply')?.[1]
isReply,
replyTo,
rootId,
replies: [],
depth: 0
}
// Add to posts (newest first)
@ -378,6 +414,47 @@ export class FeedService extends BaseService {
await this.subscribeFeed(this.currentConfig)
}
/**
* Build threaded reply structure from flat posts
*/
buildThreadedPosts(posts: FeedPost[]): FeedPost[] {
// Create a map for quick lookup
const postMap = new Map<string, FeedPost>()
posts.forEach(post => {
postMap.set(post.id, { ...post, replies: [], depth: 0 })
})
// Build the tree structure
const rootPosts: FeedPost[] = []
posts.forEach(post => {
const currentPost = postMap.get(post.id)!
if (post.isReply && post.replyTo) {
// This is a reply, attach it to its parent if parent exists
const parentPost = postMap.get(post.replyTo)
if (parentPost) {
currentPost.depth = (parentPost.depth || 0) + 1
parentPost.replies = parentPost.replies || []
parentPost.replies.push(currentPost)
// Sort replies by timestamp (oldest first for better reading flow)
parentPost.replies.sort((a, b) => a.created_at - b.created_at)
} else {
// Parent not found, treat as root post
rootPosts.push(currentPost)
}
} else {
// This is a root post
rootPosts.push(currentPost)
}
})
// Sort root posts by newest first
rootPosts.sort((a, b) => b.created_at - a.created_at)
return rootPosts
}
/**
* Get filtered posts for specific feed type
*/