Browse Source

bug-fixes

master
Silberengel 1 month ago
parent
commit
5385766a5e
  1. 8
      src/lib/components/layout/CardHeader.svelte
  2. 8
      src/lib/components/layout/ProfileBadge.svelte
  3. 44
      src/lib/modules/comments/Comment.svelte
  4. 80
      src/lib/modules/discussions/DiscussionCard.svelte
  5. 128
      src/lib/modules/feed/FeedPost.svelte
  6. 56
      src/lib/modules/feed/HighlightCard.svelte
  7. 112
      src/lib/modules/profiles/ProfilePage.svelte
  8. 11
      src/lib/services/user-data.ts

8
src/lib/components/layout/CardHeader.svelte

@ -32,13 +32,9 @@ @@ -32,13 +32,9 @@
let profile = $state<{ name?: string; nip05?: string[] } | null>(null);
let lastLoadedPubkey = $state<string | null>(null);
// Check if nip05 handle matches the name to avoid duplicate display
// Always show NIP-05 if it exists (it's a verified identifier, different from display name)
let shouldShowNip05 = $derived.by(() => {
if (!profile?.nip05 || profile.nip05.length === 0) return false;
if (!profile?.name) return true; // Show nip05 if no name
const nip05Handle = profile.nip05[0].split('@')[0]; // Extract handle part before @
return nip05Handle.toLowerCase() !== profile.name.toLowerCase();
return !!(profile?.nip05 && profile.nip05.length > 0);
});
$effect(() => {

8
src/lib/components/layout/ProfileBadge.svelte

@ -137,13 +137,9 @@ @@ -137,13 +137,9 @@
}
});
// Check if nip05 handle matches the name to avoid duplicate display
// Always show NIP-05 if it exists (it's a verified identifier, different from display name)
let shouldShowNip05 = $derived.by(() => {
if (!profile?.nip05 || profile.nip05.length === 0) return false;
if (!profile?.name) return true; // Show nip05 if no name
const nip05Handle = profile.nip05[0].split('@')[0]; // Extract handle part before @
return nip05Handle.toLowerCase() !== profile.name.toLowerCase();
return !!(profile?.nip05 && profile.nip05.length > 0);
});
</script>

44
src/lib/modules/comments/Comment.svelte

@ -140,6 +140,17 @@ @@ -140,6 +140,17 @@
{/snippet}
</CardHeader>
{#if getTitle()}
<div class="event-title-row">
<h2 class="event-title">{getTitle()}</h2>
</div>
{/if}
{#if getSummary()}
<div class="event-summary-row">
<p class="event-summary">{getSummary()}</p>
</div>
{/if}
<div class="comment-content mb-2">
{#if shouldAutoRenderMedia}
<MediaAttachments event={comment} forceRender={isMediaKind} />
@ -263,6 +274,39 @@ @@ -263,6 +274,39 @@
cursor: pointer;
}
.event-title-row {
margin-top: 0.75rem;
margin-bottom: 0.5rem;
}
.event-title {
font-size: 1.5em;
font-weight: bold;
color: var(--fog-text, #1f2937);
margin: 0;
line-height: 1.3;
}
:global(.dark) .event-title {
color: var(--fog-dark-text, #f9fafb);
}
.event-summary-row {
margin-top: 0.5rem;
margin-bottom: 0.75rem;
}
.event-summary {
font-size: 0.9375em;
color: var(--fog-text-light, #52667a);
margin: 0;
line-height: 1.5;
}
:global(.dark) .event-summary {
color: var(--fog-dark-text-light, #a8b8d0);
}
.kind-badge {
display: flex;
flex-direction: row;

80
src/lib/modules/discussions/DiscussionCard.svelte

@ -141,9 +141,16 @@ @@ -141,9 +141,16 @@
}
}
function getTitle(): string {
function getTitle(): string | null {
const titleTag = thread.tags.find((t) => t[0] === 'title');
return titleTag?.[1] || 'Untitled';
const title = titleTag?.[1];
return title && title.trim() ? title.trim() : null;
}
function getSummary(): string | null {
const summaryTag = thread.tags.find((t) => t[0] === 'summary');
const summary = summaryTag?.[1];
return summary && summary.trim() ? summary.trim() : null;
}
function getTopics(): string[] {
@ -191,20 +198,23 @@ @@ -191,20 +198,23 @@
{#if !fullView}
<a href="/event/{thread.id}" class="card-link">
<div class="card-content" class:expanded={expanded} bind:this={contentElement}>
{#if getTitle() && getTitle() !== 'Untitled'}
<h2 class="post-title font-bold mb-4 text-fog-text dark:text-fog-dark-text overflow-hidden" style="font-size: 1.5em;">
{getTitle()}
</h2>
{/if}
<CardHeader
pubkey={thread.pubkey}
relativeTime={getRelativeTime()}
clientName={getClientName()}
topics={getTopics().length > 0 ? getTopics() : undefined}
showDivider={true}
/>
{#if getTitle()}
<div class="event-title-row">
<h2 class="event-title">{getTitle()}</h2>
</div>
{/if}
{#if getSummary()}
<div class="event-summary-row">
<p class="event-summary">{getSummary()}</p>
</div>
{/if}
<!-- Display metadata (title, author, summary, description, image) -->
<MetadataCard event={thread} hideTitle={true} hideImageIfInMedia={true} />
@ -242,9 +252,21 @@ @@ -242,9 +252,21 @@
<CardHeader
pubkey={thread.pubkey}
relativeTime={getRelativeTime()}
clientName={getClientName()}
/>
{#if getTitle()}
<div class="event-title-row">
<h2 class="event-title">{getTitle()}</h2>
</div>
{/if}
{#if getSummary()}
<div class="event-summary-row">
<p class="event-summary">{getSummary()}</p>
</div>
{/if}
<!-- Display metadata (title, author, summary, description, image) -->
<MetadataCard event={thread} hideTitle={true} hideImageIfInMedia={true} />
@ -394,6 +416,39 @@ @@ -394,6 +416,39 @@
cursor: pointer;
}
.event-title-row {
margin-top: 0.75rem;
margin-bottom: 0.5rem;
}
.event-title {
font-size: 1.5em;
font-weight: bold;
color: var(--fog-text, #1f2937);
margin: 0;
line-height: 1.3;
}
:global(.dark) .event-title {
color: var(--fog-dark-text, #f9fafb);
}
.event-summary-row {
margin-top: 0.5rem;
margin-bottom: 0.75rem;
}
.event-summary {
font-size: 0.9375em;
color: var(--fog-text-light, #52667a);
margin: 0;
line-height: 1.5;
}
:global(.dark) .event-summary {
color: var(--fog-dark-text-light, #a8b8d0);
}
.kind-badge {
display: flex;
flex-direction: row;
@ -407,9 +462,10 @@ @@ -407,9 +462,10 @@
}
@media (max-width: 640px) {
.post-title {
.event-title {
word-break: break-word;
overflow-wrap: break-word;
overflow-wrap: anywhere;
max-width: 100%;
}
}

128
src/lib/modules/feed/FeedPost.svelte

@ -161,9 +161,16 @@ @@ -161,9 +161,16 @@
return clientTag?.[1] || null;
}
function getTitle(): string {
function getTitle(): string | null {
const titleTag = post.tags.find((t) => t[0] === 'title');
return titleTag?.[1] || 'Untitled';
const title = titleTag?.[1];
return title && title.trim() ? title.trim() : null;
}
function getSummary(): string | null {
const summaryTag = post.tags.find((t) => t[0] === 'summary');
const summary = summaryTag?.[1];
return summary && summary.trim() ? summary.trim() : null;
}
function getPlaintextContent(): string {
@ -833,19 +840,10 @@ @@ -833,19 +840,10 @@
<MetadataCard event={post} hideTitle={true} hideImageIfInMedia={true} />
{@const title = getTitle()}
{#if !hideTitle && title && title !== 'Untitled'}
<h2 class="post-title font-bold mb-4 text-fog-text dark:text-fog-dark-text overflow-hidden" style="font-size: 1.5em;">
{title}
</h2>
{/if}
<CardHeader
pubkey={post.pubkey}
relativeTime={getRelativeTime()}
clientName={getClientName()}
topics={post.kind === KIND.DISCUSSION_THREAD && !isOnEventPage ? (getTopics().length === 0 ? ['General'] : getTopics()) : undefined}
showDivider={post.kind === KIND.DISCUSSION_THREAD && !isOnEventPage}
>
{#snippet actions()}
{#if isLoggedIn && bookmarked}
@ -869,6 +867,17 @@ @@ -869,6 +867,17 @@
{/snippet}
</CardHeader>
{#if getTitle()}
<div class="event-title-row">
<h2 class="event-title">{getTitle()}</h2>
</div>
{/if}
{#if getSummary()}
<div class="event-summary-row">
<p class="event-summary">{getSummary()}</p>
</div>
{/if}
<div class="post-content mb-2">
{#if (shouldAutoRenderMedia || fullView) && (post.content && post.content.trim())}
<MediaAttachments event={post} forceRender={isMediaKind} />
@ -896,14 +905,9 @@ @@ -896,14 +905,9 @@
pubkey={post.pubkey}
relativeTime={getRelativeTime()}
clientName={getClientName()}
topics={post.kind === KIND.DISCUSSION_THREAD ? (getTopics().length === 0 ? ['General'] : getTopics()) : undefined}
inline={true}
showDivider={true}
>
{#snippet actions()}
{#if isLoggedIn && bookmarked}
<span class="bookmark-indicator bookmarked" title="Bookmarked">🔖</span>
{/if}
<IconButton
icon="eye"
label="View"
@ -922,11 +926,15 @@ @@ -922,11 +926,15 @@
{/snippet}
</CardHeader>
{@const title = getTitle()}
{#if title && title !== 'Untitled'}
<h2 class="post-title font-bold mb-2 text-fog-text dark:text-fog-dark-text overflow-hidden" style="font-size: 1.5em;">
{title}
</h2>
{#if getTitle()}
<div class="event-title-row">
<h2 class="event-title">{getTitle()}</h2>
</div>
{/if}
{#if getSummary()}
<div class="event-summary-row">
<p class="event-summary">{getSummary()}</p>
</div>
{/if}
<!-- Show referenced event preview in feed view -->
@ -1040,32 +1048,10 @@ @@ -1040,32 +1048,10 @@
{/if}
{#if !fullView}
<div class="feed-card-actions-section">
<div class="feed-card-footer flex items-center justify-between flex-wrap gap-2">
<div class="feed-card-reactions">
<FeedReactionButtons event={post} preloadedReactions={preloadedReactions} />
</div>
</div>
<div class="feed-card-footer flex items-center justify-between flex-wrap gap-2">
<div class="feed-card-actions flex items-center gap-2">
{#if isLoggedIn && bookmarked}
<span class="bookmark-indicator bookmarked" title="Bookmarked">🔖</span>
{/if}
<IconButton
icon="eye"
label="View"
size={16}
onclick={() => goto(getEventLink(post))}
/>
{#if isLoggedIn}
<IconButton
icon="message-square"
label="Reply"
size={16}
onclick={() => showReplyForm = !showReplyForm}
/>
{/if}
<EventMenu event={post} showContentActions={true} onReply={() => showReplyForm = !showReplyForm} />
</div>
<div class="kind-badge feed-card-kind-badge">
<span class="kind-number">{getKindInfo(post.kind).number}</span>
<span class="kind-description">{getKindInfo(post.kind).description}</span>
@ -1254,35 +1240,53 @@ @@ -1254,35 +1240,53 @@
border-top-color: var(--fog-dark-border, #374151);
}
.feed-card-actions-section {
.feed-card-footer {
margin-top: 0.5rem;
padding-top: 0.5rem;
border-top: 1px solid var(--fog-border, #e5e7eb);
flex-shrink: 0;
}
:global(.dark) .feed-card-actions-section {
:global(.dark) .feed-card-footer {
border-top-color: var(--fog-dark-border, #374151);
}
.feed-card-reactions {
flex: 1;
min-width: 0;
}
.event-title-row {
margin-top: 0.75rem;
margin-bottom: 0.5rem;
}
.feed-card-footer {
.event-title {
font-size: 1.5em;
font-weight: bold;
color: var(--fog-text, #1f2937);
margin: 0;
line-height: 1.3;
}
:global(.dark) .event-title {
color: var(--fog-dark-text, #f9fafb);
}
.event-summary-row {
margin-top: 0.5rem;
padding-top: 0.5rem;
border-top: 1px solid var(--fog-border, #e5e7eb);
flex-shrink: 0;
margin-bottom: 0.75rem;
}
:global(.dark) .feed-card-footer {
border-top-color: var(--fog-dark-border, #374151);
.event-summary {
font-size: 0.9375em;
color: var(--fog-text-light, #52667a);
margin: 0;
line-height: 1.5;
}
.feed-card-actions {
display: flex;
align-items: center;
gap: 0.5rem;
:global(.dark) .event-summary {
color: var(--fog-dark-text-light, #a8b8d0);
}
.kind-badge {
@ -1320,18 +1324,8 @@ @@ -1320,18 +1324,8 @@
}
.feed-card-actions {
display: flex !important;
visibility: visible !important;
}
@media (max-width: 640px) {
.feed-card-actions {
gap: 0.375rem;
flex-wrap: wrap;
}
.post-title {
.event-title {
word-break: break-word;
overflow-wrap: anywhere;
max-width: 100%;

56
src/lib/modules/feed/HighlightCard.svelte

@ -115,6 +115,18 @@ @@ -115,6 +115,18 @@
return urlTag?.[1] || null;
}
function getTitle(): string | null {
const titleTag = highlight.tags.find((t) => t[0] === 'title');
const title = titleTag?.[1];
return title && title.trim() ? title.trim() : null;
}
function getSummary(): string | null {
const summaryTag = highlight.tags.find((t) => t[0] === 'summary');
const summary = summaryTag?.[1];
return summary && summary.trim() ? summary.trim() : null;
}
// Normalize text for matching (remove extra whitespace, normalize line breaks)
function normalizeText(text: string): string {
return text.replace(/\s+/g, ' ').trim();
@ -349,6 +361,17 @@ @@ -349,6 +361,17 @@
{/snippet}
</CardHeader>
{#if getTitle()}
<div class="event-title-row">
<h2 class="event-title">{getTitle()}</h2>
</div>
{/if}
{#if getSummary()}
<div class="event-summary-row">
<p class="event-summary">{getSummary()}</p>
</div>
{/if}
<div class="highlight-content">
{#if shouldShowContext}
{@html getHighlightedContext()}
@ -502,6 +525,39 @@ @@ -502,6 +525,39 @@
background: var(--fog-dark-highlight, #374151);
}
.event-title-row {
margin-top: 0.75rem;
margin-bottom: 0.5rem;
}
.event-title {
font-size: 1.5em;
font-weight: bold;
color: var(--fog-text, #1f2937);
margin: 0;
line-height: 1.3;
}
:global(.dark) .event-title {
color: var(--fog-dark-text, #f9fafb);
}
.event-summary-row {
margin-top: 0.5rem;
margin-bottom: 0.75rem;
}
.event-summary {
font-size: 0.9375em;
color: var(--fog-text-light, #52667a);
margin: 0;
line-height: 1.5;
}
:global(.dark) .event-summary {
color: var(--fog-dark-text-light, #a8b8d0);
}
.kind-badge {
position: absolute;
bottom: 0.5rem;

112
src/lib/modules/profiles/ProfilePage.svelte

@ -96,28 +96,24 @@ @@ -96,28 +96,24 @@
const cached = await getProfile(pubkey);
if (cached) {
profileEvent = cached.event;
// Load wall comments if we have the profile event
if (profileEvent) {
await loadWallComments(profileEvent.id);
}
// Don't load wall comments here - load them when Wall tab is clicked
return;
}
// Fetch from relays if not in cache
// Fetch from relays if not in cache - shorter timeout
const relays = relayManager.getProfileReadRelays();
const events = await nostrClient.fetchEvents(
[{ kinds: [KIND.METADATA], authors: [pubkey], limit: 1 }],
relays,
{ useCache: 'cache-first', cacheResults: true }
{ useCache: 'cache-first', cacheResults: true, timeout: config.shortTimeout }
);
if (events.length > 0 && isMounted) {
profileEvent = events[0];
// Load wall comments
await loadWallComments(profileEvent.id);
// Don't load wall comments here - load them when Wall tab is clicked
}
} catch (error) {
// Failed to load profile
// Failed to load profile event - non-critical
}
}
@ -209,12 +205,12 @@ @@ -209,12 +205,12 @@
async function loadPins(pubkey: string) {
if (!isMounted) return;
try {
// Fetch the user's pin list (kind 10001)
// Fetch the user's pin list (kind 10001) - cache first, short timeout
const profileRelays = relayManager.getProfileReadRelays();
const pinLists = await nostrClient.fetchEvents(
[{ kinds: [KIND.PIN_LIST], authors: [pubkey], limit: 1 }],
profileRelays,
{ useCache: 'cache-first', cacheResults: true, timeout: config.mediumTimeout }
{ useCache: 'cache-first', cacheResults: true, timeout: config.shortTimeout }
);
if (!isMounted || pinLists.length === 0) {
@ -236,13 +232,14 @@ @@ -236,13 +232,14 @@
}
// Fetch the actual pinned events with cache-first and streaming
// Use shorter timeout since cache should be fast
const fetchPromise = nostrClient.fetchEvents(
[{ ids: Array.from(pinnedIds), limit: config.feedLimit }],
profileRelays,
{
useCache: 'cache-first', // Load from cache first
cacheResults: true,
timeout: config.mediumTimeout,
timeout: config.shortTimeout, // Shorter timeout for cache-first
onUpdate: (newPins) => {
if (!isMounted) return;
// Merge with existing pins
@ -264,7 +261,7 @@ @@ -264,7 +261,7 @@
pins = pinnedEvents.sort((a, b) => b.created_at - a.created_at);
} catch (error) {
// Failed to load pins
pins = [];
if (isMounted) pins = [];
}
}
@ -273,18 +270,25 @@ @@ -273,18 +270,25 @@
try {
const notificationRelays = relayManager.getFeedReadRelays();
// Fetch user's posts to find replies
// Fetch user's posts to find replies - cache first, shorter timeout
const userPosts = await nostrClient.fetchEvents(
[{ kinds: [KIND.SHORT_TEXT_NOTE], authors: [pubkey], limit: 100 }],
notificationRelays,
{ useCache: 'cache-first', cacheResults: true, timeout: config.mediumTimeout }
{ useCache: 'cache-first', cacheResults: true, timeout: config.shortTimeout }
);
if (!isMounted) return;
const userPostIds = new Set(userPosts.map(p => p.id));
// Only fetch notifications if we have posts to check
if (userPostIds.size === 0) {
if (isMounted) notifications = [];
return;
}
// Fetch notifications: replies, mentions, reactions with cache-first and streaming
// Use shorter timeout since cache should be fast
const notificationEvents = await nostrClient.fetchEvents(
[
{ kinds: [KIND.SHORT_TEXT_NOTE], '#e': Array.from(userPostIds).slice(0, 50), limit: 100 }, // Replies to user's posts
@ -295,7 +299,7 @@ @@ -295,7 +299,7 @@
{
useCache: 'cache-first', // Load from cache first
cacheResults: true,
timeout: config.mediumTimeout,
timeout: config.shortTimeout, // Shorter timeout for cache-first
onUpdate: (newNotifications) => {
if (!isMounted) return;
// Merge with existing notifications
@ -323,7 +327,7 @@ @@ -323,7 +327,7 @@
.slice(0, 100); // Limit to 100 most recent
} catch (error) {
// Failed to load notifications
notifications = [];
if (isMounted) notifications = [];
}
}
@ -336,7 +340,7 @@ @@ -336,7 +340,7 @@
try {
const interactionRelays = relayManager.getFeedResponseReadRelays();
// Fetch current user's posts from cache first (fast)
// Fetch current user's posts from cache first (fast) - shorter timeout
const fetchPromise1 = nostrClient.fetchEvents(
[{ kinds: [KIND.SHORT_TEXT_NOTE], authors: [currentUserPubkey], limit: config.mediumBatchLimit }],
interactionRelays,
@ -356,20 +360,17 @@ @@ -356,20 +360,17 @@
return;
}
// Fetch interactions with cache-first and streaming
// Fetch interactions with cache-first and streaming - shorter timeout
const fetchPromise2 = nostrClient.fetchEvents(
[
{ kinds: [KIND.SHORT_TEXT_NOTE], authors: [profilePubkey], '#e': Array.from(currentUserPostIds).slice(0, config.smallBatchLimit), limit: config.smallBatchLimit }, // Limit IDs to avoid huge queries
{ kinds: [KIND.SHORT_TEXT_NOTE], authors: [profilePubkey], '#p': [currentUserPubkey], limit: config.smallBatchLimit }
],
interactionRelays,
{ useCache: 'cache-first', cacheResults: true, timeout: config.mediumTimeout }
{ useCache: 'cache-first', cacheResults: true, timeout: config.shortTimeout } // Shorter timeout for cache-first
);
activeFetchPromises.add(fetchPromise2);
const interactionEvents = await Promise.race([
fetchPromise2,
new Promise<NostrEvent[]>((resolve) => setTimeout(() => resolve([]), 5000)) // 5s timeout
]);
const interactionEvents = await fetchPromise2;
activeFetchPromises.delete(fetchPromise2);
if (!isMounted) return;
@ -587,6 +588,7 @@ @@ -587,6 +588,7 @@
loading = true;
try {
// Step 1: Load profile and status first (fast from cache) - display immediately
// fetchProfile uses parseProfile which prioritizes tags over JSON content
const profilePromise = fetchProfile(pubkey);
const statusPromise = fetchUserStatus(pubkey);
const statusEventPromise = fetchUserStatusEvent(pubkey);
@ -608,9 +610,6 @@ @@ -608,9 +610,6 @@
userStatusEvent = statusEvent;
loading = false; // Show profile immediately, even if posts are still loading
// Load the kind 0 profile event for the wall
await loadProfileEvent(pubkey);
// Validate NIP-05 addresses in background (non-blocking)
if (profileData?.nip05 && profileData.nip05.length > 0) {
for (const nip05 of profileData.nip05) {
@ -624,32 +623,57 @@ @@ -624,32 +623,57 @@
}
}
// Step 2: Load pins for the profile being viewed
await loadPins(pubkey);
// Load pins and notifications/interactions in parallel (non-blocking)
// Don't wait for these - they'll update the UI as they load
const loadPromises: Promise<void>[] = [];
// Always load pins (for both own and other profiles)
loadPromises.push(loadPins(pubkey).catch(() => {
// Failed to load pins - non-critical
}));
// Step 3: Load notifications or interactions
// Load notifications or interactions based on profile type
if (isOwnProfile) {
await loadNotifications(pubkey);
loadPromises.push(loadNotifications(pubkey).catch(() => {
// Failed to load notifications - non-critical
}));
interactionsWithMe = [];
// Set default tab: if no pins, default to notifications
if (pins.length === 0 && notifications.length > 0) {
activeTab = 'notifications';
} else if (pins.length > 0) {
activeTab = 'pins';
}
} else {
notifications = [];
// Load interactions if logged in and viewing another user's profile
if (currentUserPubkey) {
await loadInteractionsWithMe(pubkey, currentUserPubkey);
loadPromises.push(loadInteractionsWithMe(pubkey, currentUserPubkey).catch(() => {
// Failed to load interactions - non-critical
}));
} else {
interactionsWithMe = [];
}
// Set default tab to pins if available
if (pins.length > 0) {
activeTab = 'pins';
}
}
// Wait for initial loads to complete to set default tab
Promise.all(loadPromises).then(() => {
if (!isMounted || abortController.signal.aborted || currentLoadPubkey !== pubkey) return;
if (isOwnProfile) {
// Set default tab: if no pins, default to notifications
if (pins.length === 0 && notifications.length > 0) {
activeTab = 'notifications';
} else if (pins.length > 0) {
activeTab = 'pins';
}
} else {
// Set default tab to pins if available
if (pins.length > 0) {
activeTab = 'pins';
}
}
});
// Load profile event in background (only needed for wall tab)
// Don't await - load it when user clicks Wall tab
loadProfileEvent(pubkey).catch(() => {
// Failed to load profile event - non-critical
});
} catch (error) {
// Only update state if this load wasn't aborted
if (!abortController.signal.aborted && currentLoadPubkey === pubkey) {
@ -759,6 +783,10 @@ @@ -759,6 +783,10 @@
<button
onclick={async () => {
activeTab = 'wall';
// Load profile event if not loaded yet
if (!profileEvent && profilePubkey) {
await loadProfileEvent(profilePubkey);
}
// Load wall comments if profile event is available and not already loaded
if (profileEvent && wallComments.length === 0 && !loadingWall) {
await loadWallComments(profileEvent.id);

11
src/lib/services/user-data.ts

@ -22,6 +22,7 @@ export interface ProfileData { @@ -22,6 +22,7 @@ export interface ProfileData {
/**
* Parse profile from kind 0 event
* Prioritizes tags over JSON content field
*/
export function parseProfile(event: NostrEvent): ProfileData {
const profile: ProfileData = {};
@ -41,10 +42,16 @@ export function parseProfile(event: NostrEvent): ProfileData { @@ -41,10 +42,16 @@ export function parseProfile(event: NostrEvent): ProfileData {
profile.nip05 = event.tags.filter((t) => t[0] === 'nip05').map((t) => t[1]).filter(Boolean);
profile.lud16 = event.tags.filter((t) => t[0] === 'lud16').map((t) => t[1]).filter(Boolean);
// Fallback to JSON content if tags not found
if (!profile.name || !profile.about) {
// Fallback to JSON content only for fields not found in tags
// Check if any field is missing to determine if we should parse JSON
const hasMissingFields = !profile.name || !profile.about || !profile.picture ||
profile.website.length === 0 || profile.nip05.length === 0 ||
profile.lud16.length === 0;
if (hasMissingFields) {
try {
const json = JSON.parse(event.content);
// Only use JSON values if tag values are missing
if (json.name && !profile.name) profile.name = json.name;
if (json.about && !profile.about) profile.about = json.about;
if (json.picture && !profile.picture) profile.picture = json.picture;

Loading…
Cancel
Save