diff --git a/package-lock.json b/package-lock.json index 73559d5..c9d073e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "jumble-imwald", - "version": "10.15", + "version": "12.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "jumble-imwald", - "version": "10.15", + "version": "12.2", "license": "MIT", "dependencies": { "@asciidoctor/core": "^3.0.4", diff --git a/package.json b/package.json index dd3b1ba..d73aba8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jumble-imwald", - "version": "12.1", + "version": "12.2", "description": "A user-friendly Nostr client focused on relay feed browsing and relay discovery, forked from Jumble", "private": true, "type": "module", diff --git a/src/components/Note/MarkdownArticle/MarkdownArticle.tsx b/src/components/Note/MarkdownArticle/MarkdownArticle.tsx index d4d0312..d2e47db 100644 --- a/src/components/Note/MarkdownArticle/MarkdownArticle.tsx +++ b/src/components/Note/MarkdownArticle/MarkdownArticle.tsx @@ -49,7 +49,7 @@ export default function MarkdownArticle({ // Convert "Read naddr... instead." patterns to markdown links for replaceable events // This is a standard format for forwarding readers to referred events (e.g., in wikis) const redirectRegex = /Read (naddr1[a-z0-9]+) instead\./gi - content = content.replace(redirectRegex, (match, naddr) => { + content = content.replace(redirectRegex, (_match, naddr) => { const href = toNote(naddr) return `Read [${naddr}](${href}) instead.` }) diff --git a/src/components/SaveRelayDropdownMenu/index.tsx b/src/components/SaveRelayDropdownMenu/index.tsx index 48c8940..5017a72 100644 --- a/src/components/SaveRelayDropdownMenu/index.tsx +++ b/src/components/SaveRelayDropdownMenu/index.tsx @@ -131,7 +131,7 @@ function RelayItem({ urls }: { urls: string[] }) { await addFavoriteRelays(urls) } } catch (error) { - logger.error('Failed to toggle favorite relay', { error, url }) + logger.error('Failed to toggle favorite relay', { error, urls }) } finally { setIsLoading(false) } @@ -256,7 +256,7 @@ function BlockRelayItem({ urls }: { urls: string[] }) { await addBlockedRelays(urls) } } catch (error) { - logger.error('Failed to toggle blocked relay', { error, url }) + logger.error('Failed to toggle blocked relay', { error, urls }) } finally { setIsLoading(false) } diff --git a/src/components/TrendingNotes/index.tsx b/src/components/TrendingNotes/index.tsx index d7f5592..e06b2f5 100644 --- a/src/components/TrendingNotes/index.tsx +++ b/src/components/TrendingNotes/index.tsx @@ -14,7 +14,7 @@ import { FAST_READ_RELAY_URLS } from '@/constants' import logger from '@/lib/logger' import { normalizeUrl } from '@/lib/url' -const SHOW_COUNT = 10 +const SHOW_COUNT = 25 const CACHE_DURATION = 30 * 60 * 1000 // 30 minutes // Unified cache for all custom trending feeds @@ -40,7 +40,7 @@ export default function TrendingNotes() { const { zapReplyThreshold } = useZap() const [nostrEvents, setNostrEvents] = useState([]) const [nostrLoading, setNostrLoading] = useState(false) - const [showCount, setShowCount] = useState(10) + const [showCount, setShowCount] = useState(SHOW_COUNT) const [activeTab, setActiveTab] = useState('nostr') const [sortOrder, setSortOrder] = useState('most-popular') const [hashtagFilter] = useState('popular') @@ -244,7 +244,7 @@ export default function TrendingNotes() { const events = await client.fetchEvents([relay], { kinds: [1, 11, 30023, 9802, 20, 21, 22], since: twentyFourHoursAgo, - limit: 100 + limit: 200 }) logger.debug('[TrendingNotes] Fetched', events.length, 'events from relay', relay) return events @@ -386,7 +386,8 @@ export default function TrendingNotes() { }, []) // Only run once on mount to prevent infinite loop - const relaysFilteredEvents = useMemo(() => { + // Compute filtered events without slicing (for pagination length check) + const relaysFilteredEventsAll = useMemo(() => { const idSet = new Set() const sourceEvents = cacheEvents.length > 0 ? cacheEvents : nostrEvents @@ -412,6 +413,9 @@ export default function TrendingNotes() { if (!allHashtags.includes(selectedHashtag.toLowerCase())) return false } } + } + + // Deduplicate events const id = isReplaceableEvent(evt.kind) ? getReplaceableCoordinateFromEvent(evt) : evt.id if (idSet.has(id)) { return false @@ -465,12 +469,11 @@ export default function TrendingNotes() { return 0 }) - return filtered.slice(0, showCount) + return filtered }, [ cacheEvents, nostrEvents, hideUntrustedNotes, - showCount, isEventDeleted, isUserTrusted, activeTab, @@ -480,6 +483,11 @@ export default function TrendingNotes() { zapReplyThreshold ]) + // Slice to showCount for display + const relaysFilteredEvents = useMemo(() => { + return relaysFilteredEventsAll.slice(0, showCount) + }, [relaysFilteredEventsAll, showCount]) + const filteredEvents = useMemo(() => { if (activeTab === 'nostr') { return nostrEvents.slice(0, showCount) @@ -491,7 +499,7 @@ export default function TrendingNotes() { // Reset showCount when tab changes useEffect(() => { - setShowCount(10) + setShowCount(SHOW_COUNT) }, [activeTab]) // Reset filters when switching tabs @@ -510,10 +518,12 @@ export default function TrendingNotes() { useEffect(() => { + // For relays/hashtags tabs, use the filtered length (before slicing) + // For nostr tab, use the raw events length const totalLength = activeTab === 'nostr' ? nostrEvents.length - : cacheEvents.length + : relaysFilteredEventsAll.length if (showCount >= totalLength) return @@ -540,7 +550,7 @@ export default function TrendingNotes() { observerInstance.unobserve(currentBottomRef) } } - }, [activeTab, cacheEvents.length, nostrEvents.length, showCount, cacheLoading, nostrLoading]) + }, [activeTab, nostrEvents.length, relaysFilteredEventsAll.length, showCount, cacheLoading, nostrLoading]) return (
@@ -682,15 +692,21 @@ export default function TrendingNotes() { ))} {(() => { - const currentDataLength = + const totalAvailableLength = activeTab === 'nostr' ? nostrEvents.length : cacheEvents.length + // For relays/hashtags tabs, we need to check the filtered length, not raw cache length + // because filtering might reduce the available items + const actualAvailableLength = activeTab === 'nostr' + ? totalAvailableLength + : relaysFilteredEventsAll.length + const shouldShowLoading = (activeTab === 'nostr' && nostrLoading) || ((activeTab === 'relays' || activeTab === 'hashtags') && cacheLoading) || - showCount < currentDataLength + showCount < actualAvailableLength if (shouldShowLoading) { return ( diff --git a/src/hooks/useProfileTimeline.tsx b/src/hooks/useProfileTimeline.tsx index 5935010..73dad95 100644 --- a/src/hooks/useProfileTimeline.tsx +++ b/src/hooks/useProfileTimeline.tsx @@ -112,7 +112,6 @@ export function useProfileTimeline({ useEffect(() => { let cancelled = false - const refreshIndex = refreshToken const subscribe = async () => { setIsLoading(!timelineCache.has(cacheKey)) @@ -134,7 +133,11 @@ export function useProfileTimeline({ .filter((request) => request.urls.length) if (!subRequests.length) { - updateCache([]) + timelineCache.set(cacheKey, { + events: [], + lastUpdated: Date.now() + }) + setEvents([]) setIsLoading(false) return } diff --git a/src/lib/draft-event.ts b/src/lib/draft-event.ts index de9fff4..1ecc254 100644 --- a/src/lib/draft-event.ts +++ b/src/lib/draft-event.ts @@ -1034,7 +1034,7 @@ export async function createHighlightDraftEvent( } } } catch (err) { - logger.error('Failed to decode naddr', { error: err, reference: tag }) + logger.error('Failed to decode naddr', { error: err, reference: sourceValue }) } } else if (sourceValue.startsWith('nevent')) { // Handle nevent @@ -1056,7 +1056,7 @@ export async function createHighlightDraftEvent( } } } catch (err) { - logger.error('Failed to decode nevent', { error: err, reference: tag }) + logger.error('Failed to decode nevent', { error: err, reference: sourceValue }) } } else if (sourceValue.startsWith('note')) { // Handle note1... (bech32 encoded event ID) @@ -1073,7 +1073,7 @@ export async function createHighlightDraftEvent( } } } catch (err) { - logger.error('Failed to decode note', { error: err, reference: tag }) + logger.error('Failed to decode note', { error: err, reference: sourceValue }) } } else { // Regular hex event ID diff --git a/src/lib/nip05.ts b/src/lib/nip05.ts index 46db1ad..32bd4d0 100644 --- a/src/lib/nip05.ts +++ b/src/lib/nip05.ts @@ -69,7 +69,7 @@ export async function fetchPubkeysFromDomain(domain: string): Promise return true }) as string[] } catch (error) { - logger.error('Error fetching pubkeys from domain', { error, nip05Domain }) + logger.error('Error fetching pubkeys from domain', { error, domain }) return [] } } diff --git a/src/services/relay-selection.service.ts b/src/services/relay-selection.service.ts index c7297a8..da7498d 100644 --- a/src/services/relay-selection.service.ts +++ b/src/services/relay-selection.service.ts @@ -185,7 +185,7 @@ class RelaySelectionService { } } } catch (error) { - logger.error('Failed to get contextual relays', { error, relaySets }) + logger.error('Failed to get contextual relays', { error }) } return Array.from(contextualRelays) @@ -440,7 +440,7 @@ class RelaySelectionService { } } } catch (error) { - logger.error('Failed to decode nostr address', { error, tag }) + logger.error('Failed to decode nostr address', { error, match }) } } }