Enhance comment seeding and search functionality

- Updated the `seedComments` function to return an array of published comment event IDs for tracking.
- Introduced `seedCommentReactions` to seed upvotes and downvotes on comments, improving interaction visibility.
- Enhanced the `App.vue` and `MobileNav.vue` components to support a mobile search overlay, allowing users to search films seamlessly.
- Added a new `MobileSearch` component for better search experience on mobile devices.
- Implemented a search feature in `AppHeader.vue` with dropdown results for improved content discovery.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Dorian
2026-02-12 14:57:16 +00:00
parent 53a88b012a
commit f19fd6feef
10 changed files with 898 additions and 74 deletions

View File

@@ -31,14 +31,41 @@ export interface CommentNode {
replies: CommentNode[]
}
/**
* Compute the net vote score (positive - negative) for a comment event
* from a reactions map.
*/
function getNetVotes(eventId: string, reactionsMap: Map<string, NostrEvent[]>): number {
const reactions = reactionsMap.get(eventId) || []
// Deduplicate: one vote per pubkey, keep latest
const byPubkey = new Map<string, NostrEvent>()
for (const r of reactions) {
const existing = byPubkey.get(r.pubkey)
if (!existing || r.created_at > existing.created_at) {
byPubkey.set(r.pubkey, r)
}
}
let net = 0
for (const r of byPubkey.values()) {
if (r.content === '+') net++
else if (r.content === '-') net--
}
return net
}
/**
* Build a threaded comment tree from flat event arrays.
* Top-level comments have #i = externalId.
* Replies reference parent via #e tag.
*
* Top-level comments are sorted by net votes (most upvoted first),
* with created_at descending as tiebreaker.
* Replies are sorted chronologically (oldest first).
*/
function buildCommentTree(
topLevel: NostrEvent[],
allInThread: NostrEvent[],
reactionsMap: Map<string, NostrEvent[]>,
): CommentNode[] {
// Group replies by parent event ID
const childrenMap = new Map<string, NostrEvent[]>()
@@ -66,8 +93,13 @@ function buildCommentTree(
}
}
// Sort top-level by net votes descending, then newest first as tiebreaker
return [...topLevel]
.sort((a, b) => b.created_at - a.created_at)
.sort((a, b) => {
const voteDiff = getNetVotes(b.id, reactionsMap) - getNetVotes(a.id, reactionsMap)
if (voteDiff !== 0) return voteDiff
return b.created_at - a.created_at
})
.map(buildNode)
}
@@ -171,7 +203,7 @@ export function useNostr(contentId?: string) {
}
allComments.value = allEvents
commentTree.value = buildCommentTree(topLevelEvents, allEvents)
commentTree.value = buildCommentTree(topLevelEvents, allEvents, commentReactions.value)
// Fetch profiles for all comment authors
const pubkeys = [...new Set(allEvents.map((e) => e.pubkey))]
@@ -218,6 +250,7 @@ export function useNostr(contentId?: string) {
/**
* Refresh reactions for a single comment event from the store.
* Also re-sorts the comment tree so most-voted comments float to top.
*/
function refreshSingleCommentReactions(eventId: string) {
const events = eventStore.getByFilters([
@@ -227,6 +260,25 @@ export function useNostr(contentId?: string) {
const newMap = new Map(commentReactions.value)
newMap.set(eventId, [...events])
commentReactions.value = newMap
// Re-sort the comment tree with updated vote counts
if (currentExternalId) {
const topLevel = eventStore.getByFilters([
{ kinds: [COMMENT_KIND], '#i': [currentExternalId] },
])
const allInThread = eventStore.getByFilters([
{ kinds: [COMMENT_KIND], '#I': [currentExternalId] },
])
if (topLevel && allInThread) {
const topLevelEvents = [...topLevel]
const allEvents = [...allInThread]
const allIds = new Set(allEvents.map((e) => e.id))
for (const e of topLevelEvents) {
if (!allIds.has(e.id)) allEvents.push(e)
}
commentTree.value = buildCommentTree(topLevelEvents, allEvents, newMap)
}
}
}
}