diff --git a/src/components/nostr/NoteCard.tsx b/src/components/nostr/NoteCard.tsx
index 5db8052..442a10e 100644
--- a/src/components/nostr/NoteCard.tsx
+++ b/src/components/nostr/NoteCard.tsx
@@ -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 ;
}
@@ -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) {
reposted
- {original ? (
+ {tooDeep ? (
+
+ Repost chain is too deep to display.
+
+ ) : original ? (
) : fetched.isLoading ? (
diff --git a/src/components/nostr/QuotedNotePreview.tsx b/src/components/nostr/QuotedNotePreview.tsx
index ef70ab6..d258fce 100644
--- a/src/components/nostr/QuotedNotePreview.tsx
+++ b/src/components/nostr/QuotedNotePreview.tsx
@@ -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 (
diff --git a/src/hooks/useReposts.ts b/src/hooks/useReposts.ts
index 1c6605d..5dc36c0 100644
--- a/src/hooks/useReposts.ts
+++ b/src/hooks/useReposts.ts
@@ -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({