|
|
|
|
@ -52,50 +52,65 @@
@@ -52,50 +52,65 @@
|
|
|
|
|
.replace(/'/g, '''); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Convert plain image URLs to img tags |
|
|
|
|
function convertImageUrls(text: string): string { |
|
|
|
|
// Match image URLs (http/https URLs ending in image extensions) |
|
|
|
|
// Pattern: http(s)://... followed by image extension, optionally with query params |
|
|
|
|
// Don't match URLs that are already in markdown  or HTML <img> tags |
|
|
|
|
// Convert plain media URLs (images, videos, audio) to HTML tags |
|
|
|
|
function convertMediaUrls(text: string): string { |
|
|
|
|
// Match media URLs (http/https URLs ending in media extensions) |
|
|
|
|
const imageExtensions = /\.(png|jpg|jpeg|gif|webp|svg|bmp|ico)(\?[^\s<>"']*)?$/i; |
|
|
|
|
const videoExtensions = /\.(mp4|webm|ogg|mov|avi|mkv)(\?[^\s<>"']*)?$/i; |
|
|
|
|
const audioExtensions = /\.(mp3|wav|ogg|flac|aac|m4a)(\?[^\s<>"']*)?$/i; |
|
|
|
|
const urlPattern = /https?:\/\/[^\s<>"']+/g; |
|
|
|
|
|
|
|
|
|
let result = text; |
|
|
|
|
const matches: Array<{ url: string; index: number; endIndex: number }> = []; |
|
|
|
|
const matches: Array<{ url: string; index: number; endIndex: number; type: 'image' | 'video' | 'audio' }> = []; |
|
|
|
|
|
|
|
|
|
// Find all URLs |
|
|
|
|
let match; |
|
|
|
|
while ((match = urlPattern.exec(text)) !== null) { |
|
|
|
|
const url = match[0]; |
|
|
|
|
if (imageExtensions.test(url)) { |
|
|
|
|
const index = match.index; |
|
|
|
|
const endIndex = index + url.length; |
|
|
|
|
const index = match.index; |
|
|
|
|
const endIndex = index + url.length; |
|
|
|
|
|
|
|
|
|
// Check if this URL is already in markdown or HTML |
|
|
|
|
const before = text.substring(Math.max(0, index - 10), index); |
|
|
|
|
const after = text.substring(endIndex, Math.min(text.length, endIndex + 10)); |
|
|
|
|
// Check if this URL is already in markdown or HTML |
|
|
|
|
const before = text.substring(Math.max(0, index - 10), index); |
|
|
|
|
const after = text.substring(endIndex, Math.min(text.length, endIndex + 10)); |
|
|
|
|
|
|
|
|
|
// Skip if it's in markdown image syntax  or already in <img> tag |
|
|
|
|
if (!before.includes(' && !after.startsWith('</img>')) { |
|
|
|
|
matches.push({ url, index, endIndex }); |
|
|
|
|
} |
|
|
|
|
// Skip if it's already in markdown or HTML tags |
|
|
|
|
if (before.includes(' || after.startsWith('</img>') || after.startsWith('</video>') || after.startsWith('</audio>')) { |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Determine media type |
|
|
|
|
if (imageExtensions.test(url)) { |
|
|
|
|
matches.push({ url, index, endIndex, type: 'image' }); |
|
|
|
|
} else if (videoExtensions.test(url)) { |
|
|
|
|
matches.push({ url, index, endIndex, type: 'video' }); |
|
|
|
|
} else if (audioExtensions.test(url)) { |
|
|
|
|
matches.push({ url, index, endIndex, type: 'audio' }); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Replace from end to start to preserve indices |
|
|
|
|
for (let i = matches.length - 1; i >= 0; i--) { |
|
|
|
|
const { url, index, endIndex } = matches[i]; |
|
|
|
|
const { url, index, endIndex, type } = matches[i]; |
|
|
|
|
const escapedUrl = escapeHtml(url); |
|
|
|
|
result = result.substring(0, index) + `<img src="${escapedUrl}" alt="" loading="lazy" />` + result.substring(endIndex); |
|
|
|
|
|
|
|
|
|
if (type === 'image') { |
|
|
|
|
result = result.substring(0, index) + `<img src="${escapedUrl}" alt="" loading="lazy" />` + result.substring(endIndex); |
|
|
|
|
} else if (type === 'video') { |
|
|
|
|
result = result.substring(0, index) + `<video src="${escapedUrl}" controls preload="none" style="max-width: 100%; max-height: 500px;"></video>` + result.substring(endIndex); |
|
|
|
|
} else if (type === 'audio') { |
|
|
|
|
result = result.substring(0, index) + `<audio src="${escapedUrl}" controls preload="none" style="width: 100%;"></audio>` + result.substring(endIndex); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Process content: replace nostr URIs with HTML span elements and convert image URLs |
|
|
|
|
// Process content: replace nostr URIs with HTML span elements and convert media URLs |
|
|
|
|
function processContent(text: string): string { |
|
|
|
|
// First, convert plain image URLs to img tags |
|
|
|
|
let processed = convertImageUrls(text); |
|
|
|
|
// First, convert plain media URLs (images, videos, audio) to HTML tags |
|
|
|
|
let processed = convertMediaUrls(text); |
|
|
|
|
|
|
|
|
|
// Find all NIP-21 links (nostr:npub, nostr:nprofile, etc.) |
|
|
|
|
const links = findNIP21Links(processed); |
|
|
|
|
@ -203,6 +218,8 @@
@@ -203,6 +218,8 @@
|
|
|
|
|
display: block; |
|
|
|
|
visibility: visible !important; |
|
|
|
|
opacity: 1 !important; |
|
|
|
|
/* Content images should be prominent - no grayscale filters */ |
|
|
|
|
filter: none !important; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
:global(.markdown-content video) { |
|
|
|
|
@ -210,11 +227,15 @@
@@ -210,11 +227,15 @@
|
|
|
|
|
height: auto; |
|
|
|
|
border-radius: 0.25rem; |
|
|
|
|
margin: 0.5rem 0; |
|
|
|
|
/* Content videos should be prominent - no grayscale filters */ |
|
|
|
|
filter: none !important; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
:global(.markdown-content audio) { |
|
|
|
|
width: 100%; |
|
|
|
|
margin: 0.5rem 0; |
|
|
|
|
/* Content audio should be prominent - no grayscale filters */ |
|
|
|
|
filter: none !important; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
:global(.markdown-content a) { |
|
|
|
|
|