From 23402b93099b5fb401402c91418625f52e801cd1 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Fri, 31 Oct 2025 16:04:36 +0100 Subject: [PATCH] clean up articles --- src/components/Embedded/EmbeddedNote.tsx | 37 ++++++++++++--- src/components/Note/Article/index.tsx | 38 +-------------- .../Note/AsciidocArticle/AsciidocArticle.tsx | 39 +-------------- .../Note/MarkdownArticle/MarkdownArticle.tsx | 23 +-------- src/pages/secondary/NotePage/NotFound.tsx | 47 ++++++++++++++----- 5 files changed, 69 insertions(+), 115 deletions(-) diff --git a/src/components/Embedded/EmbeddedNote.tsx b/src/components/Embedded/EmbeddedNote.tsx index 528611c..ce2cd98 100644 --- a/src/components/Embedded/EmbeddedNote.tsx +++ b/src/components/Embedded/EmbeddedNote.tsx @@ -1,4 +1,5 @@ import { Skeleton } from '@/components/ui/skeleton' +import { BIG_RELAY_URLS, SEARCHABLE_RELAY_URLS } from '@/constants' import { useFetchEvent } from '@/hooks' import { normalizeUrl } from '@/lib/url' import { cn } from '@/lib/utils' @@ -94,17 +95,20 @@ function EmbeddedNoteNotFound({ const [isSearchingExternal, setIsSearchingExternal] = useState(false) const [triedExternal, setTriedExternal] = useState(false) const [externalRelays, setExternalRelays] = useState([]) + const [hexEventId, setHexEventId] = useState(null) // Calculate which external relays would be tried useEffect(() => { const getExternalRelays = async () => { let relays: string[] = [] + let extractedHexEventId: string | null = null if (!/^[0-9a-f]{64}$/.test(noteId)) { try { const { type, data } = nip19.decode(noteId) if (type === 'nevent') { + extractedHexEventId = data.id if (data.relays) relays.push(...data.relays) if (data.author) { const authorRelayList = await client.fetchRelayList(data.author) @@ -114,6 +118,8 @@ function EmbeddedNoteNotFound({ if (data.relays) relays.push(...data.relays) const authorRelayList = await client.fetchRelayList(data.pubkey) relays.push(...authorRelayList.write.slice(0, 6)) + } else if (type === 'note') { + extractedHexEventId = data } // Normalize and deduplicate relays relays = relays.map(url => normalizeUrl(url) || url) @@ -121,13 +127,30 @@ function EmbeddedNoteNotFound({ } catch (err) { console.error('Failed to parse external relays:', err) } + } else { + extractedHexEventId = noteId } - const seenOn = client.getSeenEventRelayUrls(noteId) + setHexEventId(extractedHexEventId) + + const seenOn = extractedHexEventId ? client.getSeenEventRelayUrls(extractedHexEventId) : [] relays.push(...seenOn) - // Normalize and deduplicate final relay list - const normalizedRelays = relays.map(url => normalizeUrl(url) || url) + // Normalize all relays first + let normalizedRelays = relays.map(url => normalizeUrl(url) || url).filter(Boolean) + normalizedRelays = Array.from(new Set(normalizedRelays)) + + // If no external relays from hints, try SEARCHABLE_RELAY_URLS as fallback + // Filter out relays that overlap with BIG_RELAY_URLS (already tried first) + if (normalizedRelays.length === 0) { + const searchableRelays = SEARCHABLE_RELAY_URLS + .map(url => normalizeUrl(url) || url) + .filter((url): url is string => Boolean(url)) + .filter(relay => !BIG_RELAY_URLS.includes(relay)) + normalizedRelays.push(...searchableRelays) + } + + // Deduplicate final relay list setExternalRelays(Array.from(new Set(normalizedRelays))) } @@ -135,11 +158,11 @@ function EmbeddedNoteNotFound({ }, [noteId]) const handleTryExternalRelays = async () => { - if (isSearchingExternal) return + if (!hexEventId || isSearchingExternal) return setIsSearchingExternal(true) try { - const event = await client.fetchEventWithExternalRelays(noteId, []) + const event = await client.fetchEventWithExternalRelays(hexEventId, externalRelays) if (event && onEventFound) { onEventFound(event) } @@ -195,11 +218,11 @@ function EmbeddedNoteNotFound({ )} {!triedExternal && !hasExternalRelays && ( -

{t('No external relay hints available')}

+
{t('No external relay hints available')}
)} {triedExternal && ( -

{t('Note could not be found anywhere')}

+
{t('Note could not be found anywhere')}
)} diff --git a/src/components/Note/Article/index.tsx b/src/components/Note/Article/index.tsx index ed023ab..1c11141 100644 --- a/src/components/Note/Article/index.tsx +++ b/src/components/Note/Article/index.tsx @@ -6,7 +6,6 @@ import { ChevronDown, ChevronRight } from 'lucide-react' import { Event, kinds } from 'nostr-tools' import { useMemo, useState, useEffect, useRef } from 'react' import { useEventFieldParser } from '@/hooks/useContentParser' -import WebPreview from '../../WebPreview' import HighlightSourcePreview from '../../UniversalContent/HighlightSourcePreview' import { Button } from '@/components/ui/button' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' @@ -249,7 +248,7 @@ export default function Article({ /> {/* Collapsible Article Info - only for article-type events */} - {isArticleType && (parsedContent?.media?.length > 0 || parsedContent?.links?.length > 0 || parsedContent?.nostrLinks?.length > 0 || parsedContent?.highlightSources?.length > 0 || parsedContent?.hashtags?.length > 0) && ( + {isArticleType && (parsedContent?.nostrLinks?.length > 0 || parsedContent?.highlightSources?.length > 0 || parsedContent?.hashtags?.length > 0) && ( - {/* Media thumbnails */} - {parsedContent?.media?.length > 0 && ( -
-

Images in this article:

-
- {parsedContent?.media?.map((media, index) => ( -
- -
- ))} -
-
- )} - - {/* Links summary with OpenGraph previews */} - {parsedContent?.links?.length > 0 && ( -
-

Links in this article:

-
- {parsedContent?.links?.map((link, index) => ( - - ))} -
-
- )} {/* Nostr links summary */} {parsedContent?.nostrLinks?.length > 0 && ( diff --git a/src/components/Note/AsciidocArticle/AsciidocArticle.tsx b/src/components/Note/AsciidocArticle/AsciidocArticle.tsx index ec20774..d1374a3 100644 --- a/src/components/Note/AsciidocArticle/AsciidocArticle.tsx +++ b/src/components/Note/AsciidocArticle/AsciidocArticle.tsx @@ -1,14 +1,11 @@ import { useSecondaryPage } from '@/PageManager' import ImageWithLightbox from '@/components/ImageWithLightbox' -import ImageCarousel from '@/components/ImageCarousel/ImageCarousel' import { getLongFormArticleMetadataFromEvent } from '@/lib/event-metadata' import { toNoteList } from '@/lib/link' -import { extractAllImagesFromEvent } from '@/lib/image-extraction' import { ChevronDown, ChevronRight } from 'lucide-react' import { Event, kinds } from 'nostr-tools' import { useMemo, useState, useEffect, useRef } from 'react' import { useEventFieldParser } from '@/hooks/useContentParser' -import WebPreview from '../../WebPreview' import HighlightSourcePreview from '../../UniversalContent/HighlightSourcePreview' import { Button } from '@/components/ui/button' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' @@ -26,10 +23,6 @@ export default function AsciidocArticle({ const { push } = useSecondaryPage() const metadata = useMemo(() => getLongFormArticleMetadataFromEvent(event), [event]) const [isInfoOpen, setIsInfoOpen] = useState(false) - const [isImagesOpen, setIsImagesOpen] = useState(false) - - // Extract all images from the event - const allImages = useMemo(() => extractAllImagesFromEvent(event), [event]) // Determine if this is an article-type event that should show ToC and Article Info const isArticleType = useMemo(() => { @@ -310,23 +303,9 @@ export default function AsciidocArticle({ dangerouslySetInnerHTML={{ __html: parsedContent?.html || '' }} /> - {/* Image Carousel - Collapsible */} - {!hideImagesAndInfo && allImages.length > 0 && ( - - - - - - - - - )} {/* Collapsible Article Info - only for article-type events */} - {!hideImagesAndInfo && isArticleType && (parsedContent?.links?.length > 0 || parsedContent?.nostrLinks?.length > 0 || parsedContent?.highlightSources?.length > 0 || parsedContent?.hashtags?.length > 0) && ( + {!hideImagesAndInfo && isArticleType && (parsedContent?.nostrLinks?.length > 0 || parsedContent?.highlightSources?.length > 0 || parsedContent?.hashtags?.length > 0) && ( - - - - - - )} {metadata.tags.filter(tag => !contentHashtags.has(tag.toLowerCase())).length > 0 && (
{metadata.tags diff --git a/src/pages/secondary/NotePage/NotFound.tsx b/src/pages/secondary/NotePage/NotFound.tsx index 64795a3..d8cd039 100644 --- a/src/pages/secondary/NotePage/NotFound.tsx +++ b/src/pages/secondary/NotePage/NotFound.tsx @@ -1,5 +1,6 @@ import ClientSelect from '@/components/ClientSelect' import { Button } from '@/components/ui/button' +import { BIG_RELAY_URLS, SEARCHABLE_RELAY_URLS } from '@/constants' import { normalizeUrl } from '@/lib/url' import client from '@/services/client.service' import { AlertCircle, Search } from 'lucide-react' @@ -18,6 +19,7 @@ export default function NotFound({ const [isSearchingExternal, setIsSearchingExternal] = useState(false) const [triedExternal, setTriedExternal] = useState(false) const [externalRelays, setExternalRelays] = useState([]) + const [hexEventId, setHexEventId] = useState(null) // Calculate which external relays would be tried (excluding already-tried relays) useEffect(() => { @@ -28,6 +30,7 @@ export default function NotFound({ const alreadyTriedRelays: string[] = await client.getAlreadyTriedRelays() let externalRelays: string[] = [] + let extractedHexEventId: string | null = null // Parse relay hints and author from bech32 ID if (!/^[0-9a-f]{64}$/.test(bech32Id)) { @@ -35,6 +38,7 @@ export default function NotFound({ const { type, data } = nip19.decode(bech32Id) if (type === 'nevent') { + extractedHexEventId = data.id if (data.relays) externalRelays.push(...data.relays) if (data.author) { const authorRelayList = await client.fetchRelayList(data.author) @@ -44,6 +48,8 @@ export default function NotFound({ if (data.relays) externalRelays.push(...data.relays) const authorRelayList = await client.fetchRelayList(data.pubkey) externalRelays.push(...authorRelayList.write.slice(0, 6)) + } else if (type === 'note') { + extractedHexEventId = data } // Normalize and deduplicate external relays externalRelays = externalRelays.map(url => normalizeUrl(url) || url) @@ -51,28 +57,45 @@ export default function NotFound({ } catch (err) { console.error('Failed to parse external relays:', err) } + } else { + extractedHexEventId = bech32Id } - const seenOn = client.getSeenEventRelayUrls(bech32Id) + setHexEventId(extractedHexEventId) + + const seenOn = extractedHexEventId ? client.getSeenEventRelayUrls(extractedHexEventId) : [] externalRelays.push(...seenOn) + // Normalize all relays first + let normalizedRelays = externalRelays.map(url => normalizeUrl(url) || url).filter(Boolean) + normalizedRelays = Array.from(new Set(normalizedRelays)) + + // If no external relays from hints, try SEARCHABLE_RELAY_URLS as fallback + // Filter out relays that overlap with BIG_RELAY_URLS (already tried first) + if (normalizedRelays.length === 0) { + const searchableRelays = SEARCHABLE_RELAY_URLS + .map(url => normalizeUrl(url) || url) + .filter((url): url is string => Boolean(url)) + .filter(relay => !BIG_RELAY_URLS.includes(relay)) + normalizedRelays.push(...searchableRelays) + } + // Filter out relays that were already tried in tiers 1-3 - const newRelays = externalRelays.filter(relay => !alreadyTriedRelays.includes(relay)) + const newRelays = normalizedRelays.filter(relay => !alreadyTriedRelays.includes(relay)) - // Normalize and deduplicate final relay list - const normalizedRelays = newRelays.map(url => normalizeUrl(url) || url) - setExternalRelays(Array.from(new Set(normalizedRelays))) + // Deduplicate final relay list + setExternalRelays(Array.from(new Set(newRelays))) } getExternalRelays() }, [bech32Id]) const handleTryExternalRelays = async () => { - if (!bech32Id || isSearchingExternal) return + if (!hexEventId || isSearchingExternal) return setIsSearchingExternal(true) try { - const event = await client.fetchEventWithExternalRelays(bech32Id, externalRelays) + const event = await client.fetchEventWithExternalRelays(hexEventId, externalRelays) if (event && onEventFound) { onEventFound(event) } @@ -93,9 +116,9 @@ export default function NotFound({ {bech32Id && !triedExternal && hasExternalRelays && (
-

+

{t('The note was not found on your relays or default relays.')} -

+