From 21f18ef1e15ab956e03878893bbf2008771428b8 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Thu, 5 Feb 2026 23:56:46 +0100 Subject: [PATCH] fix profile mentions --- src/app.css | 24 ++++++- .../content/MarkdownRenderer.svelte | 15 ++++ .../content/MentionsAutocomplete.svelte | 72 ++++++++++++++----- src/lib/components/layout/ProfileBadge.svelte | 26 +++++-- src/lib/services/profile-search.ts | 6 +- 5 files changed, 118 insertions(+), 25 deletions(-) diff --git a/src/app.css b/src/app.css index 5b2e466..de520b5 100644 --- a/src/app.css +++ b/src/app.css @@ -472,7 +472,8 @@ img.emoji-inline { } /* Responsive images and media - max 600px, scale down on smaller screens */ -img:not(.profile-picture):not([alt*="profile" i]):not([alt*="avatar" i]):not([src*="avatar" i]):not([src*="profile" i]):not(.relay-icon):not(.relay-favorite-pic), +/* Exclude profile pictures, avatars, and emoji images from responsive sizing */ +img:not(.profile-picture):not([alt*="profile" i]):not([alt*="avatar" i]):not([src*="avatar" i]):not([src*="profile" i]):not(.relay-icon):not(.relay-favorite-pic):not(.emoji-inline):not(.avatar), video, audio { max-width: 600px !important; @@ -482,10 +483,11 @@ audio { } /* Ensure media in markdown content is responsive */ -.markdown-content img, +/* Exclude profile pictures, avatars, and emoji images from responsive sizing - they should maintain their fixed size */ +.markdown-content img:not(.profile-picture):not([class*="profile"]):not(.emoji-inline):not(.avatar), .markdown-content video, .markdown-content audio, -.post-content img, +.post-content img:not(.profile-picture):not([class*="profile"]):not(.emoji-inline):not(.avatar), .post-content video, .post-content audio { max-width: 600px; @@ -494,6 +496,22 @@ audio { display: block; } +/* Ensure avatars in mentions autocomplete are not affected by responsive image styles */ +.mentions-autocomplete img.avatar, +.mentions-autocomplete .avatar { + width: 1.25rem !important; + height: 1.25rem !important; + min-width: 1.25rem !important; + min-height: 1.25rem !important; + max-width: 1.25rem !important; + max-height: 1.25rem !important; + aspect-ratio: 1 / 1 !important; + border-radius: 50% !important; + object-fit: cover !important; + flex-shrink: 0 !important; + display: block !important; +} + /* Media gallery items should also be responsive */ .media-gallery img, .media-gallery video { diff --git a/src/lib/components/content/MarkdownRenderer.svelte b/src/lib/components/content/MarkdownRenderer.svelte index 472d2dc..07c5760 100644 --- a/src/lib/components/content/MarkdownRenderer.svelte +++ b/src/lib/components/content/MarkdownRenderer.svelte @@ -1037,6 +1037,21 @@ align-items: center; } + /* Ensure profile pictures in markdown content maintain circular shape and don't stretch */ + :global(.markdown-content .profile-badge .profile-picture), + :global(.markdown-content .profile-badge .profile-placeholder) { + width: 1.5rem !important; + height: 1.5rem !important; + max-width: 1.5rem !important; + max-height: 1.5rem !important; + min-width: 1.5rem !important; + min-height: 1.5rem !important; + aspect-ratio: 1 / 1 !important; + border-radius: 50% !important; + object-fit: cover !important; + flex-shrink: 0 !important; + } + /* Embedded events should be block-level */ :global(.markdown-content [data-nostr-event]) { display: block; diff --git a/src/lib/components/content/MentionsAutocomplete.svelte b/src/lib/components/content/MentionsAutocomplete.svelte index 8c15bef..90c24b9 100644 --- a/src/lib/components/content/MentionsAutocomplete.svelte +++ b/src/lib/components/content/MentionsAutocomplete.svelte @@ -26,29 +26,36 @@ const cursorPos = target.selectionStart || 0; // Find @ mention starting from cursor backwards - // Allow word chars, @, dots, and hyphens to support NIP-05 format (user@domain.com) + // We need to find the leftmost @ that could start a mention let start = cursorPos - 1; + let foundAt = -1; + + // First, find the leftmost @ going backwards while characters are valid mention chars + // This handles cases like @user@domain.com - we want the first @ while (start >= 0 && /[\w@.-]/.test(text[start])) { + if (text[start] === '@') { + foundAt = start; + } start--; } - if (start >= 0 && text[start] === '@') { - // Found @ mention - mentionStart = start; - const mentionText = text.substring(start + 1, cursorPos); + // If we found an @ and there's valid mention text after it, show suggestions + if (foundAt >= 0) { + mentionStart = foundAt; + const mentionText = text.substring(foundAt + 1, cursorPos); // Check if we're still in a valid mention (word chars, @, dots, hyphens) // Support both simple handles (@user) and NIP-05 format (@user@domain.com) if (mentionText.length > 0 && /^[\w.-]+(@[\w.-]+)?$/.test(mentionText)) { query = mentionText; updateSuggestions(mentionText); - updatePosition(target, start); + updatePosition(target, foundAt); showSuggestions = true; } else if (mentionText.length === 0) { // Just @, show all suggestions query = ''; updateSuggestions(''); - updatePosition(target, start); + updatePosition(target, foundAt); showSuggestions = true; } else { showSuggestions = false; @@ -132,9 +139,21 @@ const suggestion = suggestions[index]; const handle = suggestion.handle || suggestion.name || suggestion.pubkey.slice(0, 8); - const mentionText = `@${handle} `; - // Replace @mention with selected handle + // Convert pubkey to npub format (NIP-19) + let npub: string; + try { + npub = nip19.npubEncode(suggestion.pubkey); + } catch (error) { + console.error('Error encoding pubkey to npub:', error); + // Fallback to @handle if encoding fails + npub = suggestion.pubkey; + } + + // Insert as nostr:npub... format instead of @handle + const mentionText = `nostr:${npub} `; + + // Replace @mention with nostr:npub format const text = textarea.value; const cursorPos = textarea.selectionStart || 0; const textBefore = text.substring(0, mentionStart); @@ -186,6 +205,7 @@ src={suggestion.picture} alt="" class="avatar" + style="width: 1.25rem; height: 1.25rem; min-width: 1.25rem; min-height: 1.25rem; max-width: 1.25rem; max-height: 1.25rem; aspect-ratio: 1 / 1; border-radius: 50%; object-fit: cover; flex-shrink: 0; display: block;" onerror={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }} @@ -197,7 +217,9 @@
{suggestion.name || suggestion.handle || suggestion.pubkey.slice(0, 8)}
- {#if suggestion.handle && suggestion.name} + {#if suggestion.nip05} +
{suggestion.nip05}
+ {:else if suggestion.handle}
@{suggestion.handle}
{/if} @@ -253,13 +275,29 @@ background: var(--fog-dark-highlight, #374151); } - .avatar, - .avatar-placeholder { - width: 2rem; - height: 2rem; - border-radius: 50%; - flex-shrink: 0; - object-fit: cover; + .suggestion-item .avatar, + .suggestion-item .avatar-placeholder { + width: 1.25rem !important; + height: 1.25rem !important; + min-width: 1.25rem !important; + min-height: 1.25rem !important; + max-width: 1.25rem !important; + max-height: 1.25rem !important; + aspect-ratio: 1 / 1 !important; + border-radius: 50% !important; + flex-shrink: 0 !important; + flex-grow: 0 !important; + display: block !important; + box-sizing: border-box !important; + } + + .suggestion-item .avatar { + object-fit: cover !important; + image-rendering: auto; + } + + .suggestion-item .avatar-placeholder { + background: var(--fog-border, #e5e7eb); } .avatar-placeholder { diff --git a/src/lib/components/layout/ProfileBadge.svelte b/src/lib/components/layout/ProfileBadge.svelte index 5d4e945..87b52a9 100644 --- a/src/lib/components/layout/ProfileBadge.svelte +++ b/src/lib/components/layout/ProfileBadge.svelte @@ -143,7 +143,7 @@ {profile.name { @@ -152,7 +152,7 @@ /> {:else}