/| / | .
- // Standalone image paragraphs are handled separately in renderParagraph().
- const label = String(token.text ?? src)
+ const label = String(token.text ?? '')
if (isVideo(cleaned) || isAudio(cleaned)) {
out.push(
- {label}
+ {label || src}
)
break
}
if (!isImage(cleaned) || !isSafeMediaUrl(cleaned)) {
- out.push({label})
+ out.push(
+
+ {label || src}
+
+ )
break
}
+ // `` has empty alt — a plain {label} was invisible. Use Image like block paragraphs.
+ const baseImeta = imetaInfoForStandaloneImageUrl(cleaned)
+ const identifier = getImageIdentifier?.(cleaned)
+ const thumbnail =
+ imageThumbnailMap?.get(cleaned) ??
+ (identifier ? imageThumbnailMap?.get(`__img_id:${identifier}`) : undefined)
+ const imageUrl = thumbnail || src
+ let imageIdx = imageIndexMap.get(cleaned)
+ if (imageIdx === undefined && getImageIdentifier) {
+ const id = getImageIdentifier(cleaned)
+ if (id) imageIdx = imageIndexMap.get(`__img_id:${id}`)
+ }
out.push(
-
- {label}
-
+ {
+ e.stopPropagation()
+ if (typeof imageIdx === 'number') openLightbox(imageIdx)
+ }}
+ />
)
break
}
diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts
index 8f6c41e1..bcce83a4 100644
--- a/src/i18n/locales/de.ts
+++ b/src/i18n/locales/de.ts
@@ -341,6 +341,9 @@ export default {
'Picture note requires images': 'Bildnotiz erfordert Bilder',
Relays: 'Relays',
Image: 'Bild',
+ 'This image could not be loaded.': 'Dieses Bild konnte nicht geladen werden.',
+ 'Invalid or unsupported image address.': 'Ungültige oder nicht unterstützte Bildadresse.',
+ 'Open image link': 'Bildlink öffnen',
'Upload Image': 'Bild hochladen',
'Insert emoji': 'Emoji einfügen',
'Insert GIF': 'GIF einfügen',
diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts
index ebffb132..74fb85ec 100644
--- a/src/i18n/locales/en.ts
+++ b/src/i18n/locales/en.ts
@@ -340,6 +340,9 @@ export default {
'Picture note requires images': 'Picture note requires images',
Relays: 'Relays',
Image: 'Image',
+ 'This image could not be loaded.': 'This image could not be loaded.',
+ 'Invalid or unsupported image address.': 'Invalid or unsupported image address.',
+ 'Open image link': 'Open image link',
'Upload Image': 'Upload Image',
'Insert emoji': 'Insert emoji',
'Insert GIF': 'Insert GIF',
diff --git a/src/lib/url.ts b/src/lib/url.ts
index a324acb1..f8498c9c 100644
--- a/src/lib/url.ts
+++ b/src/lib/url.ts
@@ -367,11 +367,12 @@ export function primalR2aMirrorForBlossomPrimalUrl(url: string | URL): string |
}
/**
- * Prefer Primal’s CDN URL for `img src` when the note points at `blossom.primal.net/…`.
- * Same file as the blossom URL; avoids browsers that block or hang on the blossom host (Primal/Wisp-style delivery).
+ * Display URL for note/imeta image `src`. Keep `https://blossom.primal.net/{sha256}.ext` as-is: it is the
+ * canonical URL in events and usually loads reliably. Use {@link primalR2aMirrorForBlossomPrimalUrl} only
+ * as a fallback in {@link Image} `onError` when the blossom host fails.
*/
export function preferBlossomPrimalDisplayUrl(url: string): string {
- return primalR2aMirrorForBlossomPrimalUrl(url) ?? url
+ return url
}
/**
diff --git a/src/services/media-extraction.service.ts b/src/services/media-extraction.service.ts
index 286a716b..38f9ee76 100644
--- a/src/services/media-extraction.service.ts
+++ b/src/services/media-extraction.service.ts
@@ -1,6 +1,5 @@
import { Event } from 'nostr-tools'
import { getImetaInfosFromEvent } from '@/lib/event'
-import { tagNameEquals } from '@/lib/tag'
import { cleanUrl, isImage, isMedia, isAudio, isVideo } from '@/lib/url'
import { TImetaInfo } from '@/types'
import mediaUpload from './media-upload.service'
@@ -15,7 +14,7 @@ export interface ExtractedMedia {
/**
* Unified service for extracting all media (images, videos, audio) from an event
- * Sources: imeta tags, r tags, image tags, and content field
+ * Sources: imeta tags, image tags, and content field (not `r` tags — those are references, not media embeds)
*/
export function extractAllMediaFromEvent(
event: Event,
@@ -73,20 +72,13 @@ export function extractAllMediaFromEvent(
}
})
- // 2. Extract from r tags (reference/URL tags)
- event.tags.filter(tagNameEquals('r')).forEach(([, url]) => {
- if (url && (isImage(url) || isMedia(url))) {
- addMedia(url)
- }
- })
-
- // 3. Extract from image tag
+ // 2. Extract from image tag
const imageTag = event.tags.find((tag) => tag[0] === 'image' && tag[1])
if (imageTag?.[1]) {
addMedia(imageTag[1])
}
- // 4. Extract from content (if provided)
+ // 3. Extract from content (if provided)
if (content) {
// First, extract from markdown image syntax:  or [](link)
// This handles images inside links
|