Browse Source

more bug-fixes

imwald
Silberengel 5 months ago
parent
commit
58ce59f2bf
  1. 3
      src/components/Content/index.tsx
  2. 24
      src/components/ImageGallery/index.tsx
  3. 6
      src/components/Note/LongFormArticle/index.tsx
  4. 4
      src/components/Note/LongFormArticlePreview.tsx
  5. 155
      src/components/UniversalContent/SimpleContent.tsx
  6. 4
      src/components/WebPreview/index.tsx
  7. 4
      src/pages/secondary/NotePage/index.tsx

3
src/components/Content/index.tsx

@ -9,6 +9,7 @@ import { @@ -9,6 +9,7 @@ import {
EmbeddedWebsocketUrlParser,
parseContent
} from '@/lib/content-parser'
import logger from '@/lib/logger'
import { getImetaInfosFromEvent } from '@/lib/event'
import { getEmojiInfosFromEmojiTags, getImetaInfoFromImetaTag } from '@/lib/tag'
import { cn } from '@/lib/utils'
@ -119,6 +120,7 @@ export default function Content({ @@ -119,6 +120,7 @@ export default function Content({
}
let imageIndex = 0
logger.debug('[Content] Parsed content:', { nodeCount: nodes.length, allImages: allImages.length, nodes: nodes.map(n => ({ type: n.type, data: Array.isArray(n.data) ? n.data.length : n.data })) })
return (
<div className={cn('text-wrap break-words whitespace-pre-wrap', className)}>
{nodes.map((node, index) => {
@ -129,6 +131,7 @@ export default function Content({ @@ -129,6 +131,7 @@ export default function Content({
const start = imageIndex
const end = imageIndex + (Array.isArray(node.data) ? node.data.length : 1)
imageIndex = end
logger.debug('[Content] Creating ImageGallery:', { nodeType: node.type, start, end, totalImages: allImages.length, nodeData: Array.isArray(node.data) ? node.data.length : node.data })
return (
<ImageGallery
className="mt-2"

24
src/components/ImageGallery/index.tsx

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
import { randomString } from '@/lib/random'
import { cn } from '@/lib/utils'
import logger from '@/lib/logger'
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
import modalManager from '@/services/modal-manager.service'
import { TImetaInfo } from '@/types'
@ -39,7 +40,9 @@ export default function ImageGallery({ @@ -39,7 +40,9 @@ export default function ImageGallery({
const handlePhotoClick = (event: React.MouseEvent, current: number) => {
event.stopPropagation()
event.preventDefault()
setIndex(start + current)
const newIndex = start + current
logger.debug('[ImageGallery] Click:', { start, current, newIndex, totalImages: images.length, displayImages: displayImages.length })
setIndex(newIndex)
}
const displayImages = images.slice(start, end)
@ -62,7 +65,7 @@ export default function ImageGallery({ @@ -62,7 +65,7 @@ export default function ImageGallery({
imageContent = (
<Image
key={0}
className="max-h-[80vh] sm:max-h-[50vh] cursor-zoom-in object-contain"
className="max-h-[80vh] sm:max-h-[50vh] cursor-zoom-in object-contain max-w-[400px]"
classNames={{
errorPlaceholder: 'aspect-square h-[30vh]'
}}
@ -72,7 +75,7 @@ export default function ImageGallery({ @@ -72,7 +75,7 @@ export default function ImageGallery({
)
} else if (displayImages.length === 2 || displayImages.length === 4) {
imageContent = (
<div className="grid grid-cols-2 gap-2 w-full">
<div className="grid grid-cols-2 gap-2 w-full max-w-[400px]">
{displayImages.map((image, i) => (
<Image
key={i}
@ -85,7 +88,7 @@ export default function ImageGallery({ @@ -85,7 +88,7 @@ export default function ImageGallery({
)
} else {
imageContent = (
<div className="grid grid-cols-3 gap-2 w-full">
<div className="grid grid-cols-3 gap-2 w-full max-w-[400px]">
{displayImages.map((image, i) => (
<Image
key={i}
@ -99,17 +102,21 @@ export default function ImageGallery({ @@ -99,17 +102,21 @@ export default function ImageGallery({
}
return (
<div className={cn(displayImages.length === 1 ? 'w-fit max-w-full' : 'w-full', className)}>
<div className={cn(displayImages.length === 1 ? 'w-fit max-w-[400px]' : 'w-full', className)}>
{imageContent}
{index >= 0 &&
createPortal(
<div onClick={(e) => e.stopPropagation()}>
<Lightbox
index={index}
slides={images.map(({ url, alt }) => ({
slides={(() => {
const slides = images.map(({ url, alt }) => ({
src: url,
alt: alt || url
}))}
}))
logger.debug('[ImageGallery] Lightbox slides:', { index, slidesCount: slides.length, slides })
return slides
})()}
plugins={[Zoom]}
open={index >= 0}
close={() => setIndex(-1)}
@ -121,6 +128,9 @@ export default function ImageGallery({ @@ -121,6 +128,9 @@ export default function ImageGallery({
styles={{
toolbar: { paddingTop: '2.25rem' }
}}
carousel={{
finite: false
}}
/>
</div>,
document.body

6
src/components/Note/LongFormArticle/index.tsx

@ -78,9 +78,9 @@ export default function LongFormArticle({ @@ -78,9 +78,9 @@ export default function LongFormArticle({
img: (props) => (
<ImageWithLightbox
image={{ url: props.src || '', pubkey: event.pubkey }}
className="max-h-[80vh] sm:max-h-[50vh] object-contain my-0"
className="max-h-[80vh] sm:max-h-[50vh] object-contain my-0 max-w-[400px]"
classNames={{
wrapper: 'w-fit max-w-full'
wrapper: 'w-fit max-w-[400px]'
}}
/>
)
@ -101,7 +101,7 @@ export default function LongFormArticle({ @@ -101,7 +101,7 @@ export default function LongFormArticle({
{metadata.image && (
<ImageWithLightbox
image={{ url: metadata.image, pubkey: event.pubkey }}
className="w-full aspect-[3/1] object-cover my-0"
className="w-full max-w-[400px] aspect-[3/1] object-cover my-0"
/>
)}
<Markdown

4
src/components/Note/LongFormArticlePreview.tsx

@ -48,7 +48,7 @@ export default function LongFormArticlePreview({ @@ -48,7 +48,7 @@ export default function LongFormArticlePreview({
{metadata.image && autoLoadMedia && (
<Image
image={{ url: metadata.image, pubkey: event.pubkey }}
className="w-full aspect-video"
className="w-full max-w-[400px] aspect-video"
hideIfError
/>
)}
@ -67,7 +67,7 @@ export default function LongFormArticlePreview({ @@ -67,7 +67,7 @@ export default function LongFormArticlePreview({
{metadata.image && autoLoadMedia && (
<Image
image={{ url: metadata.image, pubkey: event.pubkey }}
className="rounded-lg aspect-[4/3] xl:aspect-video object-cover bg-foreground h-44"
className="rounded-lg aspect-[4/3] xl:aspect-video object-cover bg-foreground h-44 max-w-[400px]"
hideIfError
/>
)}

155
src/components/UniversalContent/SimpleContent.tsx

@ -1,13 +1,14 @@ @@ -1,13 +1,14 @@
import { useMemo } from 'react'
import { cleanUrl } from '@/lib/url'
import { getImetaInfosFromEvent } from '@/lib/event'
import logger from '@/lib/logger'
import { Event } from 'nostr-tools'
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { remarkNostr } from '../Note/LongFormArticle/remarkNostr'
import NostrNode from '../Note/LongFormArticle/NostrNode'
import { cn } from '@/lib/utils'
import ImageWithLightbox from '../ImageWithLightbox'
import ImageGallery from '../ImageGallery'
import MediaPlayer from '../MediaPlayer'
interface SimpleContentProps {
@ -75,90 +76,108 @@ export default function SimpleContent({ @@ -75,90 +76,108 @@ export default function SimpleContent({
const markdownLines: string[] = []
let key = 0
// Extract all image URLs from content
const imageUrls: string[] = []
lines.forEach((line) => {
// Check if line contains an image URL
const imageMatch = line.match(/(https?:\/\/[^\s]+\.(jpg|jpeg|png|gif|webp|heic|svg))/i)
if (imageMatch) {
const imageUrl = imageMatch[1]
const imageInfo = imetaInfos.find((info) => info.url === imageUrl)
const imageData = imageInfo || { url: imageUrl, pubkey: event?.pubkey }
imageUrls.push(imageMatch[1])
}
})
elements.push(
<div key={key++} className="my-4">
<ImageWithLightbox
image={imageData}
className="max-w-full h-auto rounded-lg cursor-zoom-in"
/>
</div>
)
// Extract all video URLs from content
const videoUrls: string[] = []
lines.forEach((line) => {
const videoMatch = line.match(/(https?:\/\/[^\s]+\.(mp4|webm|ogg|avi|mov|wmv|flv|mkv|m4v))/i)
if (videoMatch) {
videoUrls.push(videoMatch[1])
}
})
// Add the rest of the line as text if there's anything else
const beforeImage = line.substring(0, imageMatch.index).trim()
const afterImage = line.substring(imageMatch.index! + imageUrl.length).trim()
// Get all unique images - prioritize imeta tags, then add content images that aren't in imeta
const allImageInfos = [...imetaInfos] // Start with imeta images
const processedUrls = new Set(imetaInfos.map(info => info.url))
if (beforeImage || afterImage) {
markdownLines.push(beforeImage + afterImage)
// Add content images that aren't already in imeta
imageUrls.forEach(url => {
if (!processedUrls.has(url)) {
allImageInfos.push({ url: url, pubkey: event?.pubkey })
processedUrls.add(url)
}
} else {
// Check if line contains a video URL
const videoMatch = line.match(/(https?:\/\/[^\s]+\.(mp4|webm|ogg|avi|mov|wmv|flv|mkv|m4v))/i)
})
if (videoMatch) {
const originalVideoUrl = videoMatch[1]
// Clean the video URL to remove tracking parameters
const cleanedVideoUrl = (() => {
// Get all unique videos - prioritize imeta tags, then add content videos that aren't in imeta
const allVideoInfos = imetaInfos.filter(info => {
// Check if the imeta info is a video by looking at the URL extension
const url = info.url
const extension = url.split('.').pop()?.toLowerCase()
return extension && ['mp4', 'webm', 'ogg', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'm4v'].includes(extension)
})
const processedVideoUrls = new Set(allVideoInfos.map(info => {
try {
return new URL(cleanUrl(info.url)).href
} catch {
return cleanUrl(info.url)
}
}))
// Add content videos that aren't already in imeta
videoUrls.forEach(url => {
const cleanedUrl = (() => {
try {
return cleanUrl(originalVideoUrl)
return cleanUrl(url)
} catch {
return originalVideoUrl
return url
}
})()
// Check if this video URL is already handled by imeta tags
const normalizedVideoUrl = (() => {
const normalizedUrl = (() => {
try {
return new URL(cleanedVideoUrl).href
return new URL(cleanedUrl).href
} catch {
return cleanedVideoUrl
return cleanedUrl
}
})()
if (!imetaVideoUrls.includes(normalizedVideoUrl)) {
if (!processedVideoUrls.has(normalizedUrl)) {
allVideoInfos.push({ url: url, pubkey: event?.pubkey })
processedVideoUrls.add(normalizedUrl)
}
})
logger.debug('[SimpleContent] Processing content:', {
totalLines: lines.length,
imetaImages: imetaInfos.length,
contentImages: imageUrls.length,
totalUniqueImages: allImageInfos.length,
imetaVideos: imetaInfos.filter(info => {
const extension = info.url.split('.').pop()?.toLowerCase()
return extension && ['mp4', 'webm', 'ogg', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'm4v'].includes(extension)
}).length,
contentVideos: videoUrls.length,
totalUniqueVideos: allVideoInfos.length
})
// If we have images, create a single ImageGallery for all of them
if (allImageInfos.length > 0) {
logger.debug('[SimpleContent] Creating ImageGallery with all unique images:', {
count: allImageInfos.length,
urls: allImageInfos.map(i => i.url)
})
elements.push(
<div key={key++} className="my-4">
<MediaPlayer
src={cleanedVideoUrl}
className="max-w-full h-auto rounded-lg"
<ImageGallery
images={allImageInfos}
className="max-w-[400px]"
/>
</div>
)
}
// Add the rest of the line as text if there's anything else
const beforeVideo = line.substring(0, videoMatch.index).trim()
const afterVideo = line.substring(videoMatch.index! + originalVideoUrl.length).trim()
if (beforeVideo || afterVideo) {
markdownLines.push(beforeVideo + afterVideo)
}
} else {
// Regular text line - add to markdown processing
markdownLines.push(line)
}
}
})
// Add imeta videos to the elements
imetaInfos
.filter(info => {
// Check if the imeta info is a video by looking at the URL extension
const url = info.url
const extension = url.split('.').pop()?.toLowerCase()
return extension && ['mp4', 'webm', 'ogg', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'm4v'].includes(extension)
})
.forEach(videoInfo => {
// Clean the imeta video URL to remove tracking parameters
// Add all unique videos to elements
allVideoInfos.forEach(videoInfo => {
const cleanedVideoUrl = (() => {
try {
return cleanUrl(videoInfo.url)
@ -171,12 +190,26 @@ export default function SimpleContent({ @@ -171,12 +190,26 @@ export default function SimpleContent({
<div key={key++} className="my-4">
<MediaPlayer
src={cleanedVideoUrl}
className="max-w-full h-auto rounded-lg"
className="max-w-[400px] h-auto rounded-lg"
/>
</div>
)
})
// Process lines for text content (excluding images and videos)
lines.forEach((line) => {
// Skip lines that contain images or videos (already processed above)
const hasImage = line.match(/(https?:\/\/[^\s]+\.(jpg|jpeg|png|gif|webp|heic|svg))/i)
const hasVideo = line.match(/(https?:\/\/[^\s]+\.(mp4|webm|ogg|avi|mov|wmv|flv|mkv|m4v))/i)
if (hasImage || hasVideo) {
return // Skip this line as it's already processed
}
// Regular text line - add to markdown processing
markdownLines.push(line)
})
return {
markdownContent: markdownLines.join('\n'),
mediaElements: elements

4
src/components/WebPreview/index.tsx

@ -35,7 +35,7 @@ export default function WebPreview({ url, className }: { url: string; className? @@ -35,7 +35,7 @@ export default function WebPreview({ url, className }: { url: string; className?
window.open(url, '_blank')
}}
>
<Image image={{ url: image }} className="w-full h-44 rounded-none" hideIfError />
<Image image={{ url: image }} className="w-full max-w-[400px] h-44 rounded-none" hideIfError />
<div className="bg-muted p-2 w-full">
<div className="text-xs text-muted-foreground">{hostname}</div>
<div className="font-semibold line-clamp-1">{title}</div>
@ -55,7 +55,7 @@ export default function WebPreview({ url, className }: { url: string; className? @@ -55,7 +55,7 @@ export default function WebPreview({ url, className }: { url: string; className?
{image && (
<Image
image={{ url: image }}
className="aspect-[4/3] xl:aspect-video bg-foreground h-44 rounded-none"
className="aspect-[4/3] xl:aspect-video bg-foreground h-44 max-w-[400px] rounded-none"
hideIfError
/>
)}

4
src/pages/secondary/NotePage/index.tsx

@ -109,7 +109,7 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false }: { id?: string; @@ -109,7 +109,7 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false }: { id?: string;
return (
<SecondaryPageLayout ref={ref} index={index} title={hideTitlebar ? undefined : getNoteTypeTitle(finalEvent.kind)} displayScrollToTopButton>
<div className="px-4 pt-3">
<div className="px-4 pt-3 max-w-4xl mx-auto">
{rootITag && <ExternalRoot value={rootITag[1]} />}
{rootEventId && rootEventId !== parentEventId && (
<ParentNote
@ -139,7 +139,7 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false }: { id?: string; @@ -139,7 +139,7 @@ const NotePage = forwardRef(({ id, index, hideTitlebar = false }: { id?: string;
<NoteStats className="mt-3" event={finalEvent} fetchIfNotExisting displayTopZapsAndLikes />
</div>
<Separator className="mt-4" />
<div className="px-4 pb-4">
<div className="px-4 pb-4 max-w-4xl mx-auto">
<NoteInteractions key={`note-interactions-${finalEvent.id}`} pageIndex={index} event={finalEvent} />
</div>
</SecondaryPageLayout>

Loading…
Cancel
Save