From dffd414a5eaf60342875f4d670b449e31c889eec Mon Sep 17 00:00:00 2001 From: Silberengel Date: Mon, 9 Feb 2026 23:20:43 +0100 Subject: [PATCH] bug-fixes --- src/lib/components/layout/ProfileBadge.svelte | 113 ++++++++++-- src/lib/modules/feed/FeedPost.svelte | 167 +++++++++++++++++- src/lib/modules/feed/HighlightCard.svelte | 135 ++++++++++++++ src/lib/modules/profiles/ProfilePage.svelte | 154 +--------------- src/routes/topics/[name]/+page.svelte | 45 ++++- 5 files changed, 435 insertions(+), 179 deletions(-) diff --git a/src/lib/components/layout/ProfileBadge.svelte b/src/lib/components/layout/ProfileBadge.svelte index 407452a..1a40567 100644 --- a/src/lib/components/layout/ProfileBadge.svelte +++ b/src/lib/components/layout/ProfileBadge.svelte @@ -191,13 +191,13 @@ {/if} {/if} {#if !pictureOnly} -
-
+
+
{profile?.name || shortenedNpub} {#if profile?.nip05 && profile.nip05.length > 0} - + {profile.nip05[0]} {/if} @@ -210,10 +210,30 @@ .profile-badge { text-decoration: none; color: inherit; - max-width: 100%; - width: 100%; + max-width: 100% !important; + width: auto !important; + min-width: 0 !important; transition: opacity 0.2s; - overflow: hidden; + overflow: visible !important; + word-break: break-word !important; + overflow-wrap: anywhere !important; + box-sizing: border-box !important; + display: inline-flex !important; + flex-shrink: 1 !important; + } + + .profile-badge-content { + min-width: 0 !important; + max-width: 100% !important; + width: 100% !important; + box-sizing: border-box !important; + } + + .nip05-container { + width: 100% !important; + max-width: 100% !important; + box-sizing: border-box !important; + min-width: 0 !important; } .profile-badge:hover { @@ -263,23 +283,86 @@ min-height: 1.5rem; } + /* Apply break-all only on wider screens (not in narrow screen media query) */ + @media (min-width: 641px) { + .nip05-text { + font-size: 0.875em; + word-break: break-all !important; + overflow-wrap: anywhere !important; + word-wrap: break-word !important; + max-width: 100% !important; + white-space: normal !important; + flex-shrink: 1 !important; + min-width: 0 !important; + display: inline-block !important; + width: auto !important; + max-width: 100% !important; + box-sizing: border-box !important; + hyphens: auto; + overflow: visible !important; + } + + .break-nip05 { + word-break: break-all !important; + overflow-wrap: anywhere !important; + word-wrap: break-word !important; + white-space: normal !important; + max-width: 100% !important; + display: inline-block !important; + } + } + + /* Base styles for NIP-05 text that apply on all screen sizes */ .nip05-text { font-size: 0.875em; - word-break: break-word; - overflow-wrap: break-word; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; + max-width: 100% !important; + flex-shrink: 1 !important; + min-width: 0 !important; + display: inline-block !important; + width: auto !important; + box-sizing: border-box !important; + hyphens: auto; + } + + .break-nip05 { + max-width: 100% !important; + display: inline-block !important; } @media (max-width: 640px) { .profile-badge { - max-width: 100%; + max-width: 100% !important; + width: 100% !important; } - .nip05-text { - max-width: 100%; - word-break: break-all; + .nip05-container { + flex-direction: column !important; + align-items: flex-start !important; + width: 100% !important; + } + + /* Truncate handle/name to 10 characters on narrow screens */ + .profile-badge-content .truncate { + max-width: 10ch !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + display: inline-block !important; + } + + /* Truncate NIP-05 to 10 characters on narrow screens */ + /* Override both the class selectors and Tailwind's break-all utility */ + .nip05-text, + .break-nip05 { + max-width: 10ch !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + display: inline-block !important; + word-break: normal !important; + overflow-wrap: normal !important; + word-wrap: normal !important; + box-sizing: border-box !important; } } diff --git a/src/lib/modules/feed/FeedPost.svelte b/src/lib/modules/feed/FeedPost.svelte index e8481bc..ebe13ed 100644 --- a/src/lib/modules/feed/FeedPost.svelte +++ b/src/lib/modules/feed/FeedPost.svelte @@ -840,12 +840,12 @@
-
+
- {getRelativeTime()} + {getRelativeTime()} {#if getClientName()} - via {getClientName()} + via {getClientName()} {/if} {#if post.kind === KIND.DISCUSSION_THREAD} {@const topics = getTopics()} @@ -903,9 +903,9 @@
- {getRelativeTime()} + {getRelativeTime()} {#if getClientName()} - via {getClientName()} + via {getClientName()} {/if} {#if post.kind === KIND.DISCUSSION_THREAD} {@const topics = getTopics()} @@ -1110,11 +1110,18 @@ border-radius: 0.25rem; position: relative; overflow: hidden; + width: 100%; + max-width: 100%; + box-sizing: border-box; + word-break: break-word; + overflow-wrap: anywhere; } @media (max-width: 640px) { .Feed-post { padding: 0.75rem; + width: 100%; + max-width: 100%; } } @@ -1172,17 +1179,42 @@ .post-content { line-height: 1.6; word-wrap: break-word; - overflow-wrap: break-word; + overflow-wrap: anywhere; word-break: break-word; padding-top: 1rem; + width: 100%; + max-width: 100%; + box-sizing: border-box; + overflow: hidden; + } + + .post-content :global(.nostr-event-link), + .post-content :global(a[href^="nostr:"]) { + word-break: break-all !important; + overflow-wrap: anywhere !important; + word-wrap: break-word !important; + white-space: normal !important; + max-width: 100% !important; + display: inline-block !important; + box-sizing: border-box !important; + } + + .post-content :global(.profile-badge) { + max-width: 100% !important; + min-width: 0 !important; + flex-shrink: 1 !important; + word-break: break-word !important; + overflow-wrap: anywhere !important; + flex-wrap: wrap !important; } .word-wrap { word-wrap: break-word; - overflow-wrap: break-word; + overflow-wrap: anywhere; word-break: break-word; } + .media-urls { display: flex; flex-direction: column; @@ -1192,12 +1224,20 @@ .media-url-link { word-break: break-all; + overflow-wrap: anywhere; text-align: left; /* Ensure left alignment */ + max-width: 100%; } .nostr-event-link { - word-break: break-all; + word-break: break-all !important; + overflow-wrap: anywhere !important; + word-wrap: break-word !important; + white-space: normal !important; text-decoration: underline; + max-width: 100% !important; + display: inline-block !important; + box-sizing: border-box !important; } .nostr-event-link:hover { @@ -1289,11 +1329,62 @@ gap: 0.5rem; min-width: 0; flex-wrap: wrap; + width: 100%; + max-width: 100%; + box-sizing: border-box; + overflow: hidden; + word-break: break-word; + overflow-wrap: anywhere; } .post-header-left { min-width: 0; overflow: hidden; + width: 100%; + max-width: 100%; + box-sizing: border-box; + word-break: break-word; + overflow-wrap: anywhere; + } + + .post-header-left > span { + word-break: break-word !important; + overflow-wrap: anywhere !important; + white-space: normal !important; + max-width: 100%; + } + + .profile-badge-wrapper { + min-width: 0 !important; + max-width: 100% !important; + flex-shrink: 1 !important; + overflow: visible !important; + } + + .post-header-left :global(.profile-badge) { + max-width: 100% !important; + width: auto !important; + min-width: 0 !important; + word-break: break-word !important; + overflow-wrap: anywhere !important; + box-sizing: border-box !important; + flex-shrink: 1 !important; + flex-wrap: wrap !important; + } + + /* Apply break-all only on wider screens (not in narrow screen media query) */ + @media (min-width: 641px) { + .post-header-left :global(.nip05-text), + .post-header-left :global(.break-nip05) { + word-break: break-all !important; + overflow-wrap: anywhere !important; + word-wrap: break-word !important; + white-space: normal !important; + max-width: 100% !important; + width: 100% !important; + display: block !important; + box-sizing: border-box !important; + } } @media (max-width: 640px) { @@ -1309,11 +1400,68 @@ gap: 0.5rem; } + .post-header-left > span { + white-space: normal !important; + word-break: break-word !important; + overflow-wrap: anywhere !important; + max-width: 100%; + flex-shrink: 1; + min-width: 0; + } + + .post-header-left { + width: 100%; + flex-wrap: wrap; + gap: 0.5rem; + } + .post-header-actions { width: 100%; justify-content: flex-start; flex-wrap: wrap; } + + .profile-badge-wrapper { + max-width: 100% !important; + width: 100% !important; + flex-shrink: 1 !important; + min-width: 0 !important; + } + + .post-header-left :global(.profile-badge) { + max-width: 100% !important; + width: 100% !important; + min-width: 0 !important; + flex-shrink: 1 !important; + } + + .post-header-left :global(.nip05-container) { + flex-direction: column !important; + align-items: flex-start !important; + width: 100% !important; + } + + /* Truncate NIP-05 to 10 characters on narrow screens */ + /* Override both the class selectors and Tailwind's break-all utility */ + .post-header-left :global(.nip05-text), + .post-header-left :global(.break-nip05), + .post-header-left :global(.nip05-text.break-all), + .post-header-left :global(.break-nip05.break-all), + .post-header-left :global(span.nip05-text), + .post-header-left :global(span.break-nip05), + .post-header-left :global(span.nip05-text.break-all), + .post-header-left :global(span.break-nip05.break-all) { + max-width: 10ch !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + word-break: normal !important; + overflow-wrap: normal !important; + word-wrap: normal !important; + display: inline-block !important; + width: auto !important; + box-sizing: border-box !important; + } } .post-header-divider { @@ -1352,7 +1500,8 @@ .post-title { word-break: break-word; - overflow-wrap: break-word; + overflow-wrap: anywhere; + max-width: 100%; } } diff --git a/src/lib/modules/feed/HighlightCard.svelte b/src/lib/modules/feed/HighlightCard.svelte index 9f5f584..530e2ee 100644 --- a/src/lib/modules/feed/HighlightCard.svelte +++ b/src/lib/modules/feed/HighlightCard.svelte @@ -395,6 +395,12 @@ border: 1px solid var(--fog-border, #e5e7eb); border-radius: 0.25rem; position: relative; + overflow: hidden; + width: 100%; + max-width: 100%; + box-sizing: border-box; + word-break: break-word; + overflow-wrap: anywhere; } :global(.dark) .highlight-card { @@ -411,6 +417,31 @@ align-items: center; gap: 0.5rem; flex-wrap: wrap; + width: 100%; + max-width: 100%; + box-sizing: border-box; + word-break: break-word; + overflow-wrap: anywhere; + } + + .highlight-meta :global(.profile-badge) { + max-width: 100% !important; + min-width: 0 !important; + flex-shrink: 1 !important; + word-break: break-word !important; + overflow-wrap: anywhere !important; + } + + /* Apply break-all only on wider screens (not in narrow screen media query) */ + @media (min-width: 641px) { + .highlight-meta :global(.nip05-text), + .highlight-meta :global(.break-nip05) { + word-break: break-all !important; + overflow-wrap: anywhere !important; + word-wrap: break-word !important; + white-space: normal !important; + max-width: 100% !important; + } } .highlight-content { @@ -419,6 +450,12 @@ background: var(--fog-highlight, #f3f4f6); border-radius: 0.25rem; border-left: 3px solid #fbbf24; + width: 100%; + max-width: 100%; + box-sizing: border-box; + overflow: hidden; + word-break: break-word; + overflow-wrap: anywhere; } :global(.dark) .highlight-content { @@ -430,6 +467,12 @@ padding-top: 0.5rem; border-top: 1px solid var(--fog-border, #e5e7eb); margin-top: 0.5rem; + width: 100%; + max-width: 100%; + box-sizing: border-box; + overflow: hidden; + word-break: break-word; + overflow-wrap: anywhere; } :global(.dark) .source-event-link { @@ -451,8 +494,29 @@ color: var(--fog-accent, #64748b); text-decoration: none; font-size: 0.875rem; + word-break: break-all !important; + overflow-wrap: anywhere !important; + word-wrap: break-word !important; + white-space: normal !important; + max-width: 100%; + display: inline-block; + box-sizing: border-box; + } + + /* Ensure nostr: addresses break properly */ + .highlight-content :global(.nostr-event-link), + .highlight-content :global(a[href^="nostr:"]), + .source-event-link :global(.nostr-event-link), + .source-event-link :global(a[href^="nostr:"]) { + word-break: break-all !important; + overflow-wrap: anywhere !important; + word-wrap: break-word !important; + white-space: normal !important; + max-width: 100% !important; + display: inline-block !important; } + .source-link:hover { text-decoration: underline; } @@ -520,4 +584,75 @@ .bookmark-indicator.bookmarked { filter: grayscale(0%); } + + @media (max-width: 640px) { + .highlight-card { + padding: 0.75rem; + width: 100%; + max-width: 100%; + } + + .highlight-content { + padding: 0.5rem; + width: 100%; + max-width: 100%; + } + + .source-event-link { + width: 100%; + max-width: 100%; + } + + .source-link { + word-break: break-all !important; + overflow-wrap: anywhere !important; + word-wrap: break-word !important; + white-space: normal !important; + max-width: 100% !important; + display: block !important; + width: 100% !important; + } + + .source-event-link a, + .source-event-link span { + word-break: break-all !important; + overflow-wrap: anywhere !important; + word-wrap: break-word !important; + white-space: normal !important; + max-width: 100% !important; + } + + .highlight-meta { + flex-wrap: wrap; + width: 100%; + max-width: 100%; + } + + .highlight-meta > span { + word-break: break-word; + overflow-wrap: anywhere; + white-space: normal; + } + + /* Truncate NIP-05 to 10 characters on narrow screens */ + /* Override both the class selectors and Tailwind's break-all utility */ + .highlight-meta :global(.nip05-text), + .highlight-meta :global(.break-nip05), + .highlight-meta :global(.nip05-text.break-all), + .highlight-meta :global(.break-nip05.break-all), + .highlight-meta :global(span.nip05-text), + .highlight-meta :global(span.break-nip05), + .highlight-meta :global(span.nip05-text.break-all), + .highlight-meta :global(span.break-nip05.break-all) { + max-width: 10ch !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + word-break: normal !important; + overflow-wrap: normal !important; + word-wrap: normal !important; + display: inline-block !important; + box-sizing: border-box !important; + } + } diff --git a/src/lib/modules/profiles/ProfilePage.svelte b/src/lib/modules/profiles/ProfilePage.svelte index 542bed3..a67688c 100644 --- a/src/lib/modules/profiles/ProfilePage.svelte +++ b/src/lib/modules/profiles/ProfilePage.svelte @@ -28,7 +28,7 @@ let wallComments = $state([]); // Kind 1111 comments on the wall let loading = $state(true); let loadingWall = $state(false); - let activeTab = $state<'pins' | 'notifications' | 'interactions' | 'wall' | 'bookmarks'>('pins'); + let activeTab = $state<'pins' | 'notifications' | 'interactions' | 'wall'>('pins'); let nip05Validations = $state>({}); // null = checking, true = valid, false = invalid // Compute pubkey from route params let profilePubkey = $derived.by(() => decodePubkey($page.params.pubkey)); @@ -43,10 +43,6 @@ // Pins state let pins = $state([]); - // Bookmarks state - let bookmarks = $state([]); - let loadingBookmarks = $state(false); - // Cleanup tracking let isMounted = $state(true); let activeFetchPromises = $state>>(new Set()); @@ -272,133 +268,6 @@ } } - async function loadBookmarks(pubkey: string) { - if (!isMounted) return; - loadingBookmarks = true; - try { - // Fetch the user's bookmark list (kind 10003) - const profileRelays = relayManager.getProfileReadRelays(); - const bookmarkLists = await nostrClient.fetchEvents( - [{ kinds: [KIND.BOOKMARKS], authors: [pubkey], limit: 400 }], - profileRelays, - { useCache: 'cache-first', cacheResults: true, timeout: config.mediumTimeout } - ); - - if (!isMounted || bookmarkLists.length === 0) { - if (isMounted) { - bookmarks = []; - loadingBookmarks = false; - } - return; - } - - // Extract event IDs from bookmark lists - const bookmarkedIds = new Set(); - for (const bookmarkList of bookmarkLists) { - for (const tag of bookmarkList.tags) { - if (tag[0] === 'e' && tag[1]) { - bookmarkedIds.add(tag[1]); - } - } - } - - if (bookmarkedIds.size === 0) { - if (isMounted) { - bookmarks = []; - loadingBookmarks = false; - } - return; - } - - // Load from cache first (fast - instant display) - try { - const { getEvent } = await import('../../services/cache/event-cache.js'); - const cachedBookmarks: NostrEvent[] = []; - for (const id of bookmarkedIds) { - const cached = await getEvent(id); - if (cached) { - cachedBookmarks.push(cached); - } - } - - if (cachedBookmarks.length > 0 && isMounted) { - bookmarks = cachedBookmarks.sort((a, b) => b.created_at - a.created_at); - loadingBookmarks = false; // Show cached content immediately - } - } catch (error) { - // Cache error is non-critical - } - - // Stream fresh data from relays (progressive enhancement) - // Optimized: Fetch all bookmarked events in parallel batches - const batchSize = 100; - const bookmarkedIdsArray = Array.from(bookmarkedIds); - const batches: string[][] = []; - - for (let i = 0; i < bookmarkedIdsArray.length; i += batchSize) { - batches.push(bookmarkedIdsArray.slice(i, i + batchSize)); - } - - // Fetch all batches in parallel - const batchPromises = batches.map(batch => - nostrClient.fetchEvents( - [{ ids: batch, limit: batch.length }], - profileRelays, - { - useCache: 'cache-first', // Already shown cache above, now stream updates - cacheResults: true, - timeout: config.mediumTimeout, - onUpdate: (newBookmarks) => { - if (!isMounted) return; - // Merge with existing bookmarks - const bookmarkMap = new Map(bookmarks.map(b => [b.id, b])); - for (const bookmark of newBookmarks) { - bookmarkMap.set(bookmark.id, bookmark); - } - bookmarks = Array.from(bookmarkMap.values()).sort((a, b) => b.created_at - a.created_at); - loadingBookmarks = false; - } - } - ) - ); - - // Track all batch promises for cleanup - for (const promise of batchPromises) { - activeFetchPromises.add(promise); - } - - try { - // Wait for all batches to complete - const allBatchResults = await Promise.all(batchPromises); - - if (!isMounted) return; - - // Merge final results with existing bookmarks (onUpdate may have already updated some) - // This ensures we don't lose any intermediate updates from streaming - const bookmarkMap = new Map(bookmarks.map(b => [b.id, b])); - const allBookmarkedEvents = allBatchResults.flat(); - for (const bookmark of allBookmarkedEvents) { - bookmarkMap.set(bookmark.id, bookmark); - } - bookmarks = Array.from(bookmarkMap.values()).sort((a, b) => b.created_at - a.created_at); - } finally { - // Clean up all batch promises - for (const promise of batchPromises) { - activeFetchPromises.delete(promise); - } - } - } catch (error) { - // Failed to load bookmarks - if (isMounted) { - bookmarks = []; - } - } finally { - if (isMounted) { - loadingBookmarks = false; - } - } - } - async function loadNotifications(pubkey: string) { if (!isMounted) return; try { @@ -758,9 +627,6 @@ // Step 2: Load pins for the profile being viewed await loadPins(pubkey); - // Step 2.5: Load bookmarks for the profile being viewed - await loadBookmarks(pubkey); - // Step 3: Load notifications or interactions if (isOwnProfile) { await loadNotifications(pubkey); @@ -917,12 +783,6 @@ Interactions ({interactionsWithMe.length}) {/if} -
{#if activeTab === 'pins'} @@ -997,18 +857,6 @@ {/each}
{/if} - {:else if activeTab === 'bookmarks'} - {#if loadingBookmarks} -

Loading bookmarks...

- {:else if bookmarks.length === 0} -

No bookmarks yet.

- {:else} -
- {#each bookmarks as bookmark (bookmark.id)} - - {/each} -
- {/if} {/if}
{:else} diff --git a/src/routes/topics/[name]/+page.svelte b/src/routes/topics/[name]/+page.svelte index e462689..41e8df8 100644 --- a/src/routes/topics/[name]/+page.svelte +++ b/src/routes/topics/[name]/+page.svelte @@ -155,7 +155,7 @@
-
+

@@ -227,15 +227,27 @@