From 6a579ce1d15b4bab5e8bb88243f4396d001fae0d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Jan 2026 20:16:17 +0000 Subject: [PATCH] feat(editor): show usernames when pasting npub/nprofile mentions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When pasting npub or nprofile URIs, the editor now looks up the profile from the event store cache and uses the display name if available. Changes: - Add getDisplayNameForPubkey helper function - Import eventStore and profile helpers - Synchronously check if profile is cached via .value property - Use getDisplayName from nostr-utils for consistent formatting - Falls back to short pubkey (first 8 chars) if profile not cached Result: - Paste npub1abc... → shows "@alice" if profile is cached - Paste npub1abc... → shows "@abc12345" if profile not cached - Consistent with typed @mentions behavior - No async delays or loading states needed --- src/components/editor/MentionEditor.tsx | 36 +++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/components/editor/MentionEditor.tsx b/src/components/editor/MentionEditor.tsx index f701d9a..534ac0e 100644 --- a/src/components/editor/MentionEditor.tsx +++ b/src/components/editor/MentionEditor.tsx @@ -32,6 +32,9 @@ import type { ProfileSearchResult } from "@/services/profile-search"; import type { EmojiSearchResult } from "@/services/emoji-search"; import type { ChatAction } from "@/types/chat-actions"; import { nip19 } from "nostr-tools"; +import eventStore from "@/services/event-store"; +import { getProfileContent } from "applesauce-core/helpers"; +import { getDisplayName } from "@/lib/nostr-utils"; /** * Represents an emoji tag for NIP-30 @@ -361,6 +364,33 @@ const NostrEventPreview = Node.create({ }, }); +// Helper to get display name for a pubkey (synchronous lookup from cache) +function getDisplayNameForPubkey(pubkey: string): string { + try { + // Try to get profile from event store (check if it's a BehaviorSubject with .value) + const profile$ = eventStore.replaceable(0, pubkey) as any; + if (profile$ && profile$.value) { + const profileEvent = profile$.value; + if (profileEvent) { + const content = getProfileContent(profileEvent); + if (content) { + // Use the Grimoire helper which handles fallbacks + return getDisplayName(pubkey, content); + } + } + } + } catch (err) { + // Ignore errors, fall through to default + console.debug( + "[NostrPasteHandler] Could not get profile for", + pubkey.slice(0, 8), + err, + ); + } + // Fallback to short pubkey + return pubkey.slice(0, 8); +} + // Paste handler extension to transform bech32 strings into preview nodes const NostrPasteHandler = Extension.create({ name: "nostrPasteHandler", @@ -406,18 +436,20 @@ const NostrPasteHandler = Extension.create({ // For npub/nprofile, create regular mention nodes (reuse existing infrastructure) if (decoded.type === "npub") { const pubkey = decoded.data as string; + const displayName = getDisplayNameForPubkey(pubkey); nodes.push( view.state.schema.nodes.mention.create({ id: pubkey, - label: pubkey.slice(0, 8), // Will be updated with profile name if available + label: displayName, }), ); } else if (decoded.type === "nprofile") { const pubkey = (decoded.data as any).pubkey; + const displayName = getDisplayNameForPubkey(pubkey); nodes.push( view.state.schema.nodes.mention.create({ id: pubkey, - label: pubkey.slice(0, 8), // Will be updated with profile name if available + label: displayName, }), ); } else if (decoded.type === "note") {