diff --git a/src/components/NoteContent.test.tsx b/src/components/NoteContent.test.tsx
index 9feaaf3..b5e8257 100644
--- a/src/components/NoteContent.test.tsx
+++ b/src/components/NoteContent.test.tsx
@@ -101,4 +101,36 @@ describe('NoteContent', () => {
expect(nostrHashtag).toHaveAttribute('href', '/t/nostr');
expect(bitcoinHashtag).toHaveAttribute('href', '/t/bitcoin');
});
+
+ it('generates deterministic names for users without metadata and styles them differently', () => {
+ // Use a valid npub for testing
+ const event: NostrEvent = {
+ id: 'test-id',
+ pubkey: 'test-pubkey',
+ created_at: Math.floor(Date.now() / 1000),
+ kind: 1,
+ tags: [],
+ content: `Mentioning nostr:npub1zg69v7ys40x77y352eufp27daufrg4ncjz4ummcjx3t83y9tehhsqepuh0`,
+ sig: 'test-sig',
+ };
+
+ render(
+
+
+
+ );
+
+ // The mention should be rendered with a deterministic name
+ const mention = screen.getByRole('link');
+ expect(mention).toBeInTheDocument();
+
+ // Should have muted styling for generated names (gray instead of blue)
+ expect(mention).toHaveClass('text-gray-500');
+ expect(mention).not.toHaveClass('text-blue-500');
+
+ // The text should start with @ and contain a generated name (not a truncated npub)
+ const linkText = mention.textContent;
+ expect(linkText).not.toMatch(/^@npub1/); // Should not be a truncated npub
+ expect(linkText).toEqual("@SwiftFalcon");
+ });
});
\ No newline at end of file
diff --git a/src/components/NoteContent.tsx b/src/components/NoteContent.tsx
index 2a19343..2572463 100644
--- a/src/components/NoteContent.tsx
+++ b/src/components/NoteContent.tsx
@@ -20,7 +20,7 @@ export function NoteContent({
const text = event.content;
// Regex to find URLs, Nostr references, and hashtags
- const regex = /(https?:\/\/[^\s]+)|nostr:(npub1|note1|nprofile1|nevent1)([a-z0-9]+)|(#\w+)/g;
+ const regex = /(https?:\/\/[^\s]+)|nostr:(npub1|note1|nprofile1|nevent1)([023456789acdefghjklmnpqrstuvwxyz]+)|(#\w+)/g;
const parts: React.ReactNode[] = [];
let lastIndex = 0;
@@ -117,14 +117,50 @@ export function NoteContent({
function NostrMention({ pubkey }: { pubkey: string }) {
const author = useAuthor(pubkey);
const npub = nip19.npubEncode(pubkey);
- const displayName = author.data?.metadata?.name ?? npub.slice(0, 8);
+ const hasRealName = !!author.data?.metadata?.name;
+ const displayName = author.data?.metadata?.name ?? generateDeterministicName(pubkey);
return (
@{displayName}
);
+}
+
+// Generate a deterministic name based on pubkey
+function generateDeterministicName(pubkey: string): string {
+ // Use a simple hash of the pubkey to generate consistent adjective + noun combinations
+ const adjectives = [
+ 'Swift', 'Bright', 'Calm', 'Bold', 'Wise', 'Kind', 'Quick', 'Brave',
+ 'Cool', 'Sharp', 'Clear', 'Strong', 'Smart', 'Fast', 'Keen', 'Pure',
+ 'Noble', 'Gentle', 'Fierce', 'Steady', 'Clever', 'Proud', 'Silent', 'Wild'
+ ];
+
+ const nouns = [
+ 'Fox', 'Eagle', 'Wolf', 'Bear', 'Lion', 'Tiger', 'Hawk', 'Owl',
+ 'Deer', 'Raven', 'Falcon', 'Lynx', 'Otter', 'Whale', 'Shark', 'Dolphin',
+ 'Phoenix', 'Dragon', 'Panther', 'Jaguar', 'Cheetah', 'Leopard', 'Puma', 'Cobra'
+ ];
+
+ // Create a simple hash from the pubkey
+ let hash = 0;
+ for (let i = 0; i < pubkey.length; i++) {
+ const char = pubkey.charCodeAt(i);
+ hash = ((hash << 5) - hash) + char;
+ hash = hash & hash; // Convert to 32-bit integer
+ }
+
+ // Use absolute value to ensure positive index
+ const adjIndex = Math.abs(hash) % adjectives.length;
+ const nounIndex = Math.abs(hash >> 8) % nouns.length;
+
+ return `${adjectives[adjIndex]}${nouns[nounIndex]}`;
}
\ No newline at end of file