mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-12 08:27:27 +02:00
fix: pass full EventPointer with relay hints through embed chain
The previous commit fixed q-tag parsing in chat adapters, but the RichText/Mention chain was also discarding relay hints from nevent. EventEmbed was only extracting `pointer.id` and passing it as a string to QuotedEvent, losing the relay hints. Same issue in EmbeddedEvent. Changes: - Update QuotedEvent props: eventPointer instead of eventId string - Update EmbeddedEvent props: eventPointer instead of eventId string - Update EventEmbed to pass full pointer to QuotedEvent - Update MarkdownContent to pass full pointers for note/nevent - Update EventRefList to pass full pointer instead of just ID - Update HighlightDetailRenderer to pass full eventPointer - Update RepostRenderer to extract e-tag as EventPointer with hints Now nevent mentions in content like nostr:nevent1... correctly use the relay hints from the bech32 encoding when fetching the event.
This commit is contained in:
@@ -1,16 +1,15 @@
|
||||
import type { EventPointer, AddressPointer } from "nostr-tools/nip19";
|
||||
import { useNostrEvent } from "@/hooks/useNostrEvent";
|
||||
import { KindRenderer } from "./kinds";
|
||||
import { EventCardSkeleton } from "@/components/ui/skeleton";
|
||||
|
||||
interface EmbeddedEventProps {
|
||||
/** Event ID string for regular events */
|
||||
eventId?: string;
|
||||
/** AddressPointer for addressable/replaceable events */
|
||||
addressPointer?: { kind: number; pubkey: string; identifier: string };
|
||||
/** EventPointer with optional relay hints for regular events */
|
||||
eventPointer?: EventPointer;
|
||||
/** AddressPointer for addressable/replaceable events (includes relay hints) */
|
||||
addressPointer?: AddressPointer;
|
||||
/** Callback when user clicks to open the event in new window */
|
||||
onOpen?: (
|
||||
id: string | { kind: number; pubkey: string; identifier: string },
|
||||
) => void;
|
||||
onOpen?: (id: string | AddressPointer) => void;
|
||||
/** Optional loading fallback */
|
||||
loadingFallback?: React.ReactNode;
|
||||
/** Optional className for container */
|
||||
@@ -20,18 +19,19 @@ interface EmbeddedEventProps {
|
||||
/**
|
||||
* Reusable component for embedding Nostr events
|
||||
* Handles loading state and displays the embedded event using KindRenderer
|
||||
* Passes full pointer (including relay hints) for proper event resolution
|
||||
*/
|
||||
export function EmbeddedEvent({
|
||||
eventId,
|
||||
eventPointer,
|
||||
addressPointer,
|
||||
onOpen,
|
||||
loadingFallback,
|
||||
className = "my-4 border border-muted rounded overflow-hidden",
|
||||
}: EmbeddedEventProps) {
|
||||
// Determine pointer to use
|
||||
const pointer = eventId || addressPointer;
|
||||
// Determine pointer to use - full pointer preserves relay hints
|
||||
const pointer = eventPointer || addressPointer;
|
||||
|
||||
// Load the event
|
||||
// Load the event - passes full pointer with relay hints to useNostrEvent
|
||||
const event = useNostrEvent(pointer);
|
||||
|
||||
// If event loaded, render it
|
||||
@@ -50,19 +50,18 @@ export function EmbeddedEvent({
|
||||
|
||||
// Default loading state - show clickable link if onOpen provided
|
||||
if (onOpen && pointer) {
|
||||
const displayText =
|
||||
typeof eventId === "string"
|
||||
? `@${eventId.slice(0, 8)}...`
|
||||
: addressPointer
|
||||
? `@${addressPointer.identifier || addressPointer.kind}`
|
||||
: "@event";
|
||||
const displayText = eventPointer
|
||||
? `@${eventPointer.id.slice(0, 8)}...`
|
||||
: addressPointer
|
||||
? `@${addressPointer.identifier || addressPointer.kind}`
|
||||
: "@event";
|
||||
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onOpen(pointer);
|
||||
onOpen(eventPointer?.id || addressPointer!);
|
||||
}}
|
||||
className="inline-flex items-center gap-1 text-accent underline decoration-dotted break-all"
|
||||
>
|
||||
|
||||
@@ -57,9 +57,10 @@ function NostrMention({ href }: { href: string }) {
|
||||
</span>
|
||||
);
|
||||
case "note":
|
||||
// note is just an event ID, wrap in EventPointer
|
||||
return (
|
||||
<EmbeddedEvent
|
||||
eventId={parsed.data}
|
||||
eventPointer={{ id: parsed.data }}
|
||||
onOpen={(id) => {
|
||||
addWindow(
|
||||
"open",
|
||||
@@ -70,9 +71,10 @@ function NostrMention({ href }: { href: string }) {
|
||||
/>
|
||||
);
|
||||
case "nevent":
|
||||
// nevent includes full EventPointer with relay hints
|
||||
return (
|
||||
<EmbeddedEvent
|
||||
eventId={parsed.data.id}
|
||||
eventPointer={parsed.data}
|
||||
onOpen={(id) => {
|
||||
addWindow(
|
||||
"open",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import type { EventPointer, AddressPointer } from "nostr-tools/nip19";
|
||||
import { useNostrEvent } from "@/hooks/useNostrEvent";
|
||||
import { KindRenderer } from "./kinds";
|
||||
import { UserName } from "./UserName";
|
||||
@@ -7,14 +8,12 @@ import { cn } from "@/lib/utils";
|
||||
import { CompactQuoteSkeleton } from "@/components/ui/skeleton";
|
||||
|
||||
interface QuotedEventProps {
|
||||
/** Event ID string for regular events */
|
||||
eventId?: string;
|
||||
/** AddressPointer for addressable/replaceable events */
|
||||
addressPointer?: { kind: number; pubkey: string; identifier: string };
|
||||
/** EventPointer with optional relay hints for regular events */
|
||||
eventPointer?: EventPointer;
|
||||
/** AddressPointer for addressable/replaceable events (includes relay hints) */
|
||||
addressPointer?: AddressPointer;
|
||||
/** Callback when user clicks to open the event in new window */
|
||||
onOpen?: (
|
||||
id: string | { kind: number; pubkey: string; identifier: string },
|
||||
) => void;
|
||||
onOpen?: (id: string | AddressPointer) => void;
|
||||
/** Depth level for nesting (0 = root, 1 = first quote, 2+ = nested) */
|
||||
depth?: number;
|
||||
/** Optional className for container */
|
||||
@@ -27,7 +26,7 @@ interface QuotedEventProps {
|
||||
* - depth 2+: Show expandable preview only
|
||||
*/
|
||||
export function QuotedEvent({
|
||||
eventId,
|
||||
eventPointer,
|
||||
addressPointer,
|
||||
onOpen,
|
||||
depth = 1,
|
||||
@@ -35,21 +34,20 @@ export function QuotedEvent({
|
||||
}: QuotedEventProps) {
|
||||
const [isExpanded, setIsExpanded] = useState(depth < 2);
|
||||
|
||||
// Determine pointer to use
|
||||
const pointer = eventId || addressPointer;
|
||||
// Determine pointer to use - full pointer preserves relay hints
|
||||
const pointer = eventPointer || addressPointer;
|
||||
|
||||
// Load the event
|
||||
// Load the event - passes full pointer with relay hints to useNostrEvent
|
||||
const event = useNostrEvent(pointer);
|
||||
|
||||
// Loading state
|
||||
if (!event) {
|
||||
if (onOpen && pointer) {
|
||||
const displayText =
|
||||
typeof eventId === "string"
|
||||
? `@${eventId.slice(0, 8)}...`
|
||||
: addressPointer
|
||||
? `@${addressPointer.identifier || addressPointer.kind}`
|
||||
: "@event";
|
||||
const displayText = eventPointer
|
||||
? `@${eventPointer.id.slice(0, 8)}...`
|
||||
: addressPointer
|
||||
? `@${addressPointer.identifier || addressPointer.kind}`
|
||||
: "@event";
|
||||
|
||||
return (
|
||||
<a
|
||||
@@ -57,7 +55,7 @@ export function QuotedEvent({
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onOpen(pointer);
|
||||
onOpen(eventPointer?.id || addressPointer!);
|
||||
}}
|
||||
className="inline-flex items-center gap-1 text-accent underline decoration-dotted break-all"
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EventPointer, AddressPointer } from "nostr-tools/nip19";
|
||||
import type { EventPointer, AddressPointer } from "nostr-tools/nip19";
|
||||
import { QuotedEvent } from "../QuotedEvent";
|
||||
|
||||
interface EventEmbedNodeProps {
|
||||
@@ -11,16 +11,18 @@ interface EventEmbedNodeProps {
|
||||
/**
|
||||
* EventEmbed component for rendering quoted/embedded Nostr events
|
||||
* Uses QuotedEvent with depth tracking for smart expand/collapse behavior
|
||||
* Passes full pointer (including relay hints) for proper event resolution
|
||||
*/
|
||||
export function EventEmbed({ node, depth = 1 }: EventEmbedNodeProps) {
|
||||
const { pointer } = node;
|
||||
|
||||
// Check if it's an EventPointer (has 'id') or AddressPointer (has 'kind' + 'pubkey')
|
||||
const isEvent = "id" in pointer;
|
||||
|
||||
return (
|
||||
<QuotedEvent
|
||||
eventId={"id" in pointer ? pointer.id : undefined}
|
||||
addressPointer={
|
||||
"kind" in pointer && "pubkey" in pointer ? pointer : undefined
|
||||
}
|
||||
eventPointer={isEvent ? pointer : undefined}
|
||||
addressPointer={!isEvent ? (pointer as AddressPointer) : undefined}
|
||||
depth={depth}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -122,7 +122,7 @@ export function Kind9802DetailRenderer({ event }: { event: NostrEvent }) {
|
||||
Highlighted From
|
||||
</div>
|
||||
<EmbeddedEvent
|
||||
eventId={eventPointer?.id}
|
||||
eventPointer={eventPointer}
|
||||
addressPointer={addressPointer}
|
||||
onOpen={(pointer) => {
|
||||
if (typeof pointer === "string") {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Repeat2 } from "lucide-react";
|
||||
import { getEventPointerFromETag } from "applesauce-core/helpers/pointers";
|
||||
import { BaseEventContainer, type BaseEventProps } from "./BaseEventRenderer";
|
||||
import { EmbeddedEvent } from "../EmbeddedEvent";
|
||||
import { useGrimoire } from "@/core/state";
|
||||
@@ -13,9 +14,9 @@ import { useGrimoire } from "@/core/state";
|
||||
export function RepostRenderer({ event }: BaseEventProps) {
|
||||
const { addWindow } = useGrimoire();
|
||||
|
||||
// Get the event being reposted (e tag)
|
||||
// Get the event being reposted (e tag) with relay hints
|
||||
const eTag = event.tags.find((tag) => tag[0] === "e");
|
||||
const repostedEventId = eTag?.[1];
|
||||
const repostedEventPointer = eTag ? getEventPointerFromETag(eTag) : null;
|
||||
|
||||
return (
|
||||
<BaseEventContainer event={event}>
|
||||
@@ -24,9 +25,9 @@ export function RepostRenderer({ event }: BaseEventProps) {
|
||||
<Repeat2 className="size-4" />
|
||||
<span>reposted</span>
|
||||
</div>
|
||||
{repostedEventId && (
|
||||
{repostedEventPointer && (
|
||||
<EmbeddedEvent
|
||||
eventId={repostedEventId}
|
||||
eventPointer={repostedEventPointer}
|
||||
onOpen={(id) => {
|
||||
addWindow(
|
||||
"open",
|
||||
|
||||
@@ -137,7 +137,7 @@ export function EventRefListFull({
|
||||
{eventPointers.map((pointer) => (
|
||||
<EmbeddedEvent
|
||||
key={pointer.id}
|
||||
eventId={pointer.id}
|
||||
eventPointer={pointer}
|
||||
className="border border-muted rounded overflow-hidden"
|
||||
/>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user