mirror of
https://github.com/layer-systems/website.git
synced 2026-09-13 06:07:29 +02:00
Address review feedback on reposts and quote posts
- Stop following a repost chain past MAX_REPOST_DEPTH: previously it fell through to rendering the repost event's own JSON content as if it were a note. Now it shows a "chain too deep" message instead. - Validate that each embedded repost tag is itself an array of strings, not just that `tags` is an array, so a malformed embed (e.g. tags: [1]) can't crash rendering. - Propagate the quoted note's relay hints when opening it from the preview, so "Open" can still resolve notes that only exist on a hinted relay. - Give useCreateQuotePost() the same friendly "sign in" error as useToggleRepost() instead of relying on useNostrPublish()'s generic one. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BYiUtZMQeA5RHggQw73wto
This commit is contained in:
@@ -51,7 +51,7 @@ export function NoteCard({ event, compact, className, depth = 0 }: NoteCardProps
|
||||
// until this note is actually looked at instead of firing on every mount.
|
||||
const [revealed, setRevealed] = useState(false);
|
||||
|
||||
if ((event.kind === REPOST_KIND || event.kind === GENERIC_REPOST_KIND) && depth < MAX_REPOST_DEPTH) {
|
||||
if (event.kind === REPOST_KIND || event.kind === GENERIC_REPOST_KIND) {
|
||||
return <RepostedNote event={event} compact={compact} className={className} depth={depth} />;
|
||||
}
|
||||
|
||||
@@ -130,12 +130,15 @@ function RepostedNote({ event, compact, className, depth }: RepostedNoteProps) {
|
||||
const { data: author } = useAuthor(event.pubkey);
|
||||
const name = displayName(event.pubkey, author?.metadata);
|
||||
|
||||
const embedded = useMemo(() => parseEmbeddedRepost(event), [event]);
|
||||
const tooDeep = depth >= MAX_REPOST_DEPTH;
|
||||
const embedded = useMemo(() => (tooDeep ? null : parseEmbeddedRepost(event)), [event, tooDeep]);
|
||||
const reference = repostReference(event);
|
||||
// A malformed, self-referential repost (its `e` tag points at itself) must
|
||||
// not be followed, or fetching "the original" would just re-render this
|
||||
// same repost forever.
|
||||
const fetchId = !embedded && reference && reference.id !== event.id ? reference.id : undefined;
|
||||
// same repost forever. Once MAX_REPOST_DEPTH is hit, stop following
|
||||
// reposts altogether rather than falling through to rendering the repost
|
||||
// event's own (JSON) content as if it were a note.
|
||||
const fetchId = !tooDeep && !embedded && reference && reference.id !== event.id ? reference.id : undefined;
|
||||
const fetched = useNote(fetchId, reference?.relay ? [reference.relay] : undefined);
|
||||
const original = embedded ?? fetched.data;
|
||||
|
||||
@@ -153,7 +156,11 @@ function RepostedNote({ event, compact, className, depth }: RepostedNoteProps) {
|
||||
<span>reposted</span>
|
||||
</div>
|
||||
|
||||
{original ? (
|
||||
{tooDeep ? (
|
||||
<p className="px-4 pb-3 pl-11 text-xs text-muted-foreground">
|
||||
Repost chain is too deep to display.
|
||||
</p>
|
||||
) : original ? (
|
||||
<NoteCard event={original} compact={compact} className="border-b-0" depth={depth + 1} />
|
||||
) : fetched.isLoading ? (
|
||||
<div className="space-y-2 px-4 py-3 pl-11">
|
||||
|
||||
@@ -3,6 +3,7 @@ import { NoteContent } from './NoteContent';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { useNote } from '@/hooks/useNote';
|
||||
import { useWindowManager } from '@/os/useWindowManager';
|
||||
import { encodeRelayHints } from '@/lib/nostrUtils';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface QuotedNotePreviewProps {
|
||||
@@ -24,7 +25,8 @@ export function QuotedNotePreview({ id, relays, className }: QuotedNotePreviewPr
|
||||
const { openApp } = useWindowManager();
|
||||
const note = useNote(id, relays);
|
||||
|
||||
const open = () => openApp('notes', { id });
|
||||
const relayHints = encodeRelayHints(relays);
|
||||
const open = () => openApp('notes', relayHints ? { id, relays: relayHints } : { id });
|
||||
|
||||
return (
|
||||
<div className={cn('mt-2 rounded-lg border border-border p-3', className)}>
|
||||
|
||||
@@ -73,7 +73,8 @@ export function parseEmbeddedRepost(event: NostrEvent): NostrEvent | null {
|
||||
typeof parsed.content === 'string' &&
|
||||
typeof parsed.created_at === 'number' &&
|
||||
typeof parsed.kind === 'number' &&
|
||||
Array.isArray(parsed.tags)
|
||||
Array.isArray(parsed.tags) &&
|
||||
parsed.tags.every((tag) => Array.isArray(tag) && tag.every((value) => typeof value === 'string'))
|
||||
) {
|
||||
return parsed as NostrEvent;
|
||||
}
|
||||
@@ -176,11 +177,13 @@ interface CreateQuotePostInput {
|
||||
* embedded `nostr:` reference in the content for wider compatibility.
|
||||
*/
|
||||
export function useCreateQuotePost() {
|
||||
const { user } = useCurrentUser();
|
||||
const publish = useNostrPublish();
|
||||
const hints = useRelayHints();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ target, content }: CreateQuotePostInput) => {
|
||||
if (!user) throw new Error('Sign in to quote this note');
|
||||
const trimmed = content.trim();
|
||||
const reference = buildQuoteReference(target, hints);
|
||||
return publish.mutateAsync({
|
||||
|
||||
Reference in New Issue
Block a user