From 2b0207cd9558251c8a011b4ab4a6036678b38201 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sun, 5 Oct 2025 20:09:15 +0200 Subject: [PATCH] reply reactions-voting removed repost and republishing from discussion replies --- src/components/NoteOptions/useMenuActions.tsx | 27 ++++++- src/components/NoteStats/LikeButton.tsx | 70 +++++++++++++++---- src/components/NoteStats/index.tsx | 32 +++++++-- .../SuggestedEmojis/DiscussionEmojis.tsx | 23 ++++++ 4 files changed, 131 insertions(+), 21 deletions(-) create mode 100644 src/components/SuggestedEmojis/DiscussionEmojis.tsx diff --git a/src/components/NoteOptions/useMenuActions.tsx b/src/components/NoteOptions/useMenuActions.tsx index 56e3553..9760282 100644 --- a/src/components/NoteOptions/useMenuActions.tsx +++ b/src/components/NoteOptions/useMenuActions.tsx @@ -1,5 +1,5 @@ import { ExtendedKind } from '@/constants' -import { getNoteBech32Id, isProtectedEvent } from '@/lib/event' +import { getNoteBech32Id, isProtectedEvent, getRootEventHexId } from '@/lib/event' import { toNjump } from '@/lib/link' import { pubkeyToNpub } from '@/lib/pubkey' import { simplifyUrl } from '@/lib/url' @@ -10,7 +10,7 @@ import { useNostr } from '@/providers/NostrProvider' import client from '@/services/client.service' import { Bell, BellOff, Code, Copy, Link, SatelliteDish, Trash2, TriangleAlert } from 'lucide-react' import { Event } from 'nostr-tools' -import { useMemo } from 'react' +import { useMemo, useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import RelayIcon from '../RelayIcon' @@ -57,6 +57,27 @@ export function useMenuActions({ }, [currentBrowsingRelayUrls, favoriteRelays]) const { mutePubkeyPublicly, mutePubkeyPrivately, unmutePubkey, mutePubkeySet } = useMuteList() const isMuted = useMemo(() => mutePubkeySet.has(event.pubkey), [mutePubkeySet, event]) + + // Check if this is a reply to a discussion event + const [isReplyToDiscussion, setIsReplyToDiscussion] = useState(false) + + useEffect(() => { + const isDiscussion = event.kind === ExtendedKind.DISCUSSION + if (isDiscussion) return // Already a discussion event + + const rootEventId = getRootEventHexId(event) + if (rootEventId) { + // Fetch the root event to check if it's a discussion + client.fetchEvent(rootEventId).then(rootEvent => { + if (rootEvent && rootEvent.kind === ExtendedKind.DISCUSSION) { + setIsReplyToDiscussion(true) + } + }).catch(() => { + // If we can't fetch the root event, assume it's not a discussion reply + setIsReplyToDiscussion(false) + }) + } + }, [event.id, event.kind]) const broadcastSubMenu: SubMenuAction[] = useMemo(() => { const items = [] @@ -185,7 +206,7 @@ export function useMenuActions({ const isProtected = isProtectedEvent(event) const isDiscussion = event.kind === ExtendedKind.DISCUSSION - if ((!isProtected || event.pubkey === pubkey) && !isDiscussion) { + if ((!isProtected || event.pubkey === pubkey) && !isDiscussion && !isReplyToDiscussion) { actions.push({ icon: SatelliteDish, label: t('Republish to ...'), diff --git a/src/components/NoteStats/LikeButton.tsx b/src/components/NoteStats/LikeButton.tsx index 1ba0b71..9a91a79 100644 --- a/src/components/NoteStats/LikeButton.tsx +++ b/src/components/NoteStats/LikeButton.tsx @@ -4,8 +4,10 @@ import { DropdownMenuContent, DropdownMenuTrigger } from '@/components/ui/dropdown-menu' +import { ExtendedKind } from '@/constants' import { useNoteStatsById } from '@/hooks/useNoteStatsById' import { createReactionDraftEvent } from '@/lib/draft-event' +import { getRootEventHexId } from '@/lib/event' import { useNostr } from '@/providers/NostrProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useUserTrust } from '@/providers/UserTrustProvider' @@ -19,6 +21,7 @@ import { useTranslation } from 'react-i18next' import Emoji from '../Emoji' import EmojiPicker from '../EmojiPicker' import SuggestedEmojis from '../SuggestedEmojis' +import DiscussionEmojis from '../SuggestedEmojis/DiscussionEmojis' import { formatCount } from './utils' export default function LikeButton({ event }: { event: Event }) { @@ -30,14 +33,39 @@ export default function LikeButton({ event }: { event: Event }) { const [isEmojiReactionsOpen, setIsEmojiReactionsOpen] = useState(false) const [isPickerOpen, setIsPickerOpen] = useState(false) const noteStats = useNoteStatsById(event.id) - const { myLastEmoji, likeCount } = useMemo(() => { + const isDiscussion = event.kind === ExtendedKind.DISCUSSION + + // Check if this is a reply to a discussion event + const [isReplyToDiscussion, setIsReplyToDiscussion] = useState(false) + + useMemo(() => { + if (isDiscussion) return // Already a discussion event + + const rootEventId = getRootEventHexId(event) + if (rootEventId) { + // Fetch the root event to check if it's a discussion + client.fetchEvent(rootEventId).then(rootEvent => { + if (rootEvent && rootEvent.kind === ExtendedKind.DISCUSSION) { + setIsReplyToDiscussion(true) + } + }).catch(() => { + // If we can't fetch the root event, assume it's not a discussion reply + setIsReplyToDiscussion(false) + }) + } + }, [event.id, isDiscussion]) + const { myLastEmoji, likeCount, hasVoted } = useMemo(() => { const stats = noteStats || {} const myLike = stats.likes?.find((like) => like.pubkey === pubkey) const likes = hideUntrustedInteractions ? stats.likes?.filter((like) => isUserTrusted(like.pubkey)) : stats.likes - return { myLastEmoji: myLike?.emoji, likeCount: likes?.length } - }, [noteStats, pubkey, hideUntrustedInteractions]) + + // For discussion events and replies to discussions, check if user has voted (either up or down) + const hasVoted = (isDiscussion || isReplyToDiscussion) && myLike && (myLike.emoji === '⬆️' || myLike.emoji === '⬇️') + + return { myLastEmoji: myLike?.emoji, likeCount: likes?.length, hasVoted } + }, [noteStats, pubkey, hideUntrustedInteractions, isDiscussion, isReplyToDiscussion]) const like = async (emoji: string | TEmoji) => { checkLogin(async () => { @@ -68,8 +96,9 @@ export default function LikeButton({ event }: { event: Event }) {