Add reply context to notifications and make it DRY with feed
- Created shared ReplyContext component for consistent reply rendering - Updated NoteCard to use shared ReplyContext component - Added reply detection logic to ReferencedPostCard in notifications - Added reply context rendering for liked/reposted/zapped posts in notifications - Removed duplicate ReplyContext implementations - Ensures "Replying to @username" appears consistently across feed and notifications Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
@@ -13,6 +13,7 @@ import { FoundLogContent } from '@/components/FoundLogContent';
|
||||
import { ColorMomentContent } from '@/components/ColorMomentContent';
|
||||
import { FollowPackContent } from '@/components/FollowPackContent';
|
||||
import { ChestIcon } from '@/components/icons/ChestIcon';
|
||||
import { ReplyContext } from '@/components/ReplyContext';
|
||||
import { useAuthor } from '@/hooks/useAuthor';
|
||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
import { useEventStats } from '@/hooks/useTrending';
|
||||
@@ -508,20 +509,4 @@ function TreasureHeader({ pubkey, variant }: { pubkey: string; variant: 'hid' |
|
||||
);
|
||||
}
|
||||
|
||||
function ReplyContext({ pubkey }: { pubkey: string }) {
|
||||
const author = useAuthor(pubkey);
|
||||
const name = author.data?.metadata?.name || genUserName(pubkey);
|
||||
|
||||
return (
|
||||
<div className="flex items-center text-sm text-muted-foreground mt-2 mb-1">
|
||||
<span className="mr-1">Replying to</span>
|
||||
{author.isLoading ? (
|
||||
<Skeleton className="h-3.5 w-20 inline-block" />
|
||||
) : (
|
||||
<Link to={`/${nip19.npubEncode(pubkey)}`} className="text-primary hover:underline" onClick={(e) => e.stopPropagation()}>
|
||||
@{name}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { useAuthor } from '@/hooks/useAuthor';
|
||||
import { genUserName } from '@/lib/genUserName';
|
||||
|
||||
interface ReplyContextProps {
|
||||
pubkey: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays "Replying to @username" context for reply posts.
|
||||
* Used consistently across NoteCard and notification views.
|
||||
*/
|
||||
export function ReplyContext({ pubkey, className }: ReplyContextProps) {
|
||||
const author = useAuthor(pubkey);
|
||||
const name = author.data?.metadata?.name || genUserName(pubkey);
|
||||
|
||||
return (
|
||||
<div className={className || 'flex items-center text-sm text-muted-foreground mt-2 mb-1'}>
|
||||
<span className="mr-1">Replying to</span>
|
||||
{author.isLoading ? (
|
||||
<Skeleton className="h-3.5 w-20 inline-block" />
|
||||
) : (
|
||||
<Link to={`/${nip19.npubEncode(pubkey)}`} className="text-primary hover:underline" onClick={(e) => e.stopPropagation()}>
|
||||
@{name}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { RepostMenu } from '@/components/RepostMenu';
|
||||
import { NoteMoreMenu } from '@/components/NoteMoreMenu';
|
||||
import { ReplyComposeModal } from '@/components/ReplyComposeModal';
|
||||
import { ZapDialog } from '@/components/ZapDialog';
|
||||
import { ReplyContext } from '@/components/ReplyContext';
|
||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
import { useAuthor } from '@/hooks/useAuthor';
|
||||
import { useEvent } from '@/hooks/useEvent';
|
||||
@@ -321,6 +322,27 @@ function ReferencedPostCard({ event }: { event: NostrEvent }) {
|
||||
const [moreMenuOpen, setMoreMenuOpen] = useState(false);
|
||||
const [replyOpen, setReplyOpen] = useState(false);
|
||||
|
||||
// Check if this is a reply and find the person being replied to
|
||||
const isReply = event.tags.some(([name]) => name === 'e');
|
||||
const replyTo = isReply ? (() => {
|
||||
// First try to find a p tag that's not a mention
|
||||
const directReply = event.tags.find(([name, , , marker]) => name === 'p' && marker !== 'mention');
|
||||
if (directReply) return directReply;
|
||||
|
||||
// If no direct reply p tag, check if there's a root or reply e tag with a pubkey
|
||||
const rootOrReply = event.tags.find(([name, , , marker]) =>
|
||||
name === 'e' && (marker === 'root' || marker === 'reply')
|
||||
);
|
||||
if (rootOrReply?.[4]) {
|
||||
// Return a p tag format with the pubkey from the e tag
|
||||
return ['p', rootOrReply[4]];
|
||||
}
|
||||
|
||||
// Fallback: if this is a reply but all p tags are mentions, use the first p tag anyway
|
||||
const firstP = event.tags.find(([name]) => name === 'p');
|
||||
return firstP;
|
||||
})() : undefined;
|
||||
|
||||
const handleNavigate = useCallback(() => {
|
||||
navigate(`/${encodedId}`);
|
||||
}, [navigate, encodedId]);
|
||||
@@ -330,6 +352,11 @@ function ReferencedPostCard({ event }: { event: NostrEvent }) {
|
||||
className="cursor-pointer"
|
||||
onClick={handleNavigate}
|
||||
>
|
||||
{/* Reply context */}
|
||||
{isReply && replyTo?.[1] && (
|
||||
<ReplyContext pubkey={replyTo[1]} />
|
||||
)}
|
||||
|
||||
{/* Author row */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Link to={`/${npub}`} className="shrink-0" onClick={(e) => e.stopPropagation()}>
|
||||
@@ -584,27 +611,6 @@ function ActionButtons({
|
||||
);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────
|
||||
// Reply Context: "Replying to @{name} and @{name}"
|
||||
// ──────────────────────────────────────
|
||||
function ReplyContext({ pubkey }: { pubkey: string }) {
|
||||
const author = useAuthor(pubkey);
|
||||
const name = author.data?.metadata?.name || genUserName(pubkey);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1.5 text-sm text-muted-foreground mb-1">
|
||||
<span>Replying to</span>
|
||||
<Link
|
||||
to={`/${nip19.npubEncode(pubkey)}`}
|
||||
className="text-primary hover:underline"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
@{name}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────
|
||||
// Skeleton Loader
|
||||
// ──────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user