From b46d3832003e5689f61b2f6b4181e60e4e99bf66 Mon Sep 17 00:00:00 2001 From: Jure <44338+hoornet@users.noreply.github.com> Date: Fri, 27 Mar 2026 18:20:00 +0100 Subject: [PATCH] Grouped emoji reactions, npub search, notification fix, dev logger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Emoji reactions now display as grouped pills (❤️5 🤙3 🔥2) instead of a single aggregated count. Multi-reaction per note supported. - Throttled reaction fetch queue (max 4 concurrent) prevents relay overload. - Searching a bare npub/nprofile navigates directly to that profile. - Notification poller waits for relay connection before first fetch, fixing empty results on startup. - Dev-only debug logger (src/lib/debug.ts) — silent in production builds. --- src/components/feed/NoteActions.tsx | 97 +++++++++++++++++----------- src/components/search/SearchView.tsx | 15 ++++- src/hooks/useReactions.ts | 89 +++++++++++++++++++++++++ src/lib/debug.ts | 8 +++ src/lib/nostr/engagement.ts | 67 +++++++++++++++++-- src/lib/nostr/index.ts | 3 +- src/lib/notificationPoller.ts | 27 ++++++-- src/stores/feed.test.ts | 12 ++-- src/stores/notifications.ts | 6 +- 9 files changed, 266 insertions(+), 58 deletions(-) create mode 100644 src/hooks/useReactions.ts create mode 100644 src/lib/debug.ts diff --git a/src/components/feed/NoteActions.tsx b/src/components/feed/NoteActions.tsx index f32dfeb..3307e15 100644 --- a/src/components/feed/NoteActions.tsx +++ b/src/components/feed/NoteActions.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { NDKEvent, nip19 } from "@nostr-dev-kit/ndk"; import { useProfile } from "../../hooks/useProfile"; -import { useReactionCount } from "../../hooks/useReactionCount"; +import { useReactions } from "../../hooks/useReactions"; import { useReplyCount } from "../../hooks/useReplyCount"; import { useZapCount } from "../../hooks/useZapCount"; import { useUserStore } from "../../stores/user"; @@ -26,14 +26,8 @@ export function NoteActions({ event, onReplyToggle, showReply }: NoteActionsProp const { bookmarkedIds, addBookmark, removeBookmark } = useBookmarkStore(); const isBookmarked = bookmarkedIds.includes(event.id!); - const likedKey = "wrystr_liked"; - const getLiked = () => { - try { return new Set(JSON.parse(localStorage.getItem(likedKey) || "[]")); } - catch { return new Set(); } - }; - const [liked, setLiked] = useState(() => getLiked().has(event.id)); - const [liking, setLiking] = useState(false); - const [reactionCount, adjustReactionCount] = useReactionCount(event.id); + const [reactionsData, addReaction] = useReactions(event.id); + const [reacting, setReacting] = useState(false); const [showEmojiPicker, setShowEmojiPicker] = useState(false); const [replyCount] = useReplyCount(event.id); const [copied, setCopied] = useState(false); @@ -43,19 +37,17 @@ export function NoteActions({ event, onReplyToggle, showReply }: NoteActionsProp const [reposting, setReposting] = useState(false); const [reposted, setReposted] = useState(false); - const handleReact = async (emoji?: string) => { - if (!loggedIn || liked || liking) return; - setLiking(true); + const myReactions = reactionsData?.myReactions ?? new Set(); + + const handleReact = async (emoji: string) => { + if (!loggedIn || reacting || myReactions.has(emoji)) return; + setReacting(true); setShowEmojiPicker(false); try { - await publishReaction(event.id, event.pubkey, emoji || "+"); - const likedSet = getLiked(); - likedSet.add(event.id); - localStorage.setItem(likedKey, JSON.stringify(Array.from(likedSet))); - setLiked(true); - adjustReactionCount(1); + await publishReaction(event.id, event.pubkey, emoji); + addReaction(emoji); } finally { - setLiking(false); + setReacting(false); } }; @@ -77,9 +69,14 @@ export function NoteActions({ event, onReplyToggle, showReply }: NoteActionsProp } }; + // Sort emoji groups: most popular first + const sortedGroups = reactionsData + ? Array.from(reactionsData.groups.entries()).sort((a, b) => b[1] - a[1]) + : []; + return ( <> -
+
-
- - {!liked && !liking && ( + + {/* Emoji reaction pills */} +
+ {sortedGroups.map(([emoji, count]) => ( + + ))} + + {/* Add reaction button */} + {loggedIn && ( )} + + {/* Emoji picker popover */} {showEmojiPicker && ( <>
setShowEmojiPicker(false)} /> @@ -115,7 +125,10 @@ export function NoteActions({ event, onReplyToggle, showReply }: NoteActionsProp @@ -124,6 +137,7 @@ export function NoteActions({ event, onReplyToggle, showReply }: NoteActionsProp )}
+