diff --git a/.env.example b/.env.example index 33479bf..de165b7 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,7 @@ NEXT_PUBLIC_SHOW_GEYSER_FUND=false NEXT_PUBLIC_ENABLE_UMAMI=false NEXT_PUBLIC_UMAMI_URL=https://your-umami-url.com -NEXT_PUBLIC_UMAMI_WEBSITE_ID=your-umami-website-id \ No newline at end of file +NEXT_PUBLIC_UMAMI_WEBSITE_ID=your-umami-website-id + +NEXT_PUBLIC_ENABLE_IMGPROXY=false +NEXT_PUBLIC_IMGPROXY_URL=https://your-imgproxy-url.com \ No newline at end of file diff --git a/README.md b/README.md index 16267bc..b3a20fc 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,26 @@ docker run -p 3000:3000 lumina ## ⚙️ Configuration +### Image Proxy (imgproxy) + +LUMINA supports image proxying via imgproxy to optimize image loading, resize images on-the-fly, and enhance privacy. To enable: + +1. Edit the `.env` file in the `lumina` directory: + ``` + NEXT_PUBLIC_ENABLE_IMGPROXY=true + NEXT_PUBLIC_IMGPROXY_URL=https://your-imgproxy-instance.com/ + ``` + +2. Make sure your imgproxy instance is properly configured and accessible. + +3. Restart the application to apply changes. + +The imgproxy feature: +- Resizes images to appropriate dimensions for better performance +- Falls back to direct image URLs if the proxy fails +- Provides faster loading times for large images +- Can be disabled by setting `NEXT_PUBLIC_ENABLE_IMGPROXY=false` + ### Umami Analytics Umami analytics is disabled by default. To enable: diff --git a/components/KIND20Card.tsx b/components/KIND20Card.tsx index a1eeb13..f20d2e9 100644 --- a/components/KIND20Card.tsx +++ b/components/KIND20Card.tsx @@ -14,6 +14,7 @@ import ZapButton from "./ZapButton" import Image from "next/image" import CardOptionsDropdown from "./CardOptionsDropdown" import { renderTextWithLinkedTags } from "@/utils/textUtils" +import { getProxiedImageUrl } from "@/utils/utils" // Function to extract all images from a kind 20 event's imeta tags const extractImagesFromEvent = (tags: string[][]): string[] => { @@ -26,6 +27,8 @@ const extractImagesFromEvent = (tags: string[][]): string[] => { .filter(Boolean) as string[] } +const useImgProxy = process.env.NEXT_PUBLIC_ENABLE_IMGPROXY === "true" + interface KIND20CardProps { pubkey: string text: string @@ -50,6 +53,7 @@ const KIND20Card: React.FC = ({ }) const [currentImage, setCurrentImage] = useState(0); const [imageErrors, setImageErrors] = useState>({}); + const [imagesWithoutProxy, setImagesWithoutProxy] = useState>({}); const [api, setApi] = useState(null); // Extract all images from imeta tags @@ -61,12 +65,21 @@ const KIND20Card: React.FC = ({ // Filter out images with errors const validImages = allImages.filter(img => !imageErrors[img]); - // Handle image error by marking that specific image as having an error + // Handle image error by first trying without proxy, then marking as error if that fails too const handleImageError = (errorImage: string) => { - setImageErrors(prev => ({ - ...prev, - [errorImage]: true - })); + if (imagesWithoutProxy[errorImage]) { + // Already tried without proxy, mark as error + setImageErrors(prev => ({ + ...prev, + [errorImage]: true + })); + } else { + // Try without proxy + setImagesWithoutProxy(prev => ({ + ...prev, + [errorImage]: true + })); + } } // Update current image index when carousel slides @@ -134,25 +147,29 @@ const KIND20Card: React.FC = ({ setApi={setApi} > - {validImages.map((imageUrl, index) => ( - -
-
- {text} handleImageError(imageUrl)} - loading="lazy" - style={{ - maxHeight: "80vh", - margin: "auto" - }} - /> + {validImages.map((imageUrl, index) => { + const shouldUseProxy = useImgProxy && !imagesWithoutProxy[imageUrl]; + const image = shouldUseProxy ? getProxiedImageUrl(imageUrl, 1200, 0) : imageUrl; + return ( + +
+
+ {text} handleImageError(imageUrl)} + loading="lazy" + style={{ + maxHeight: "80vh", + margin: "auto" + }} + /> +
-
- - ))} + + ); + })} {validImages.length > 1 && ( <> diff --git a/components/QuickViewKind20NoteCard.tsx b/components/QuickViewKind20NoteCard.tsx index 6d78242..edb4fb2 100644 --- a/components/QuickViewKind20NoteCard.tsx +++ b/components/QuickViewKind20NoteCard.tsx @@ -9,7 +9,7 @@ import { } from "@/components/ui/card" import Link from 'next/link'; import Image from 'next/image'; -import { extractDimensions } from '@/utils/utils'; +import { extractDimensions, getProxiedImageUrl } from '@/utils/utils'; interface QuickViewKind20NoteCardProps { pubkey: string; @@ -26,8 +26,14 @@ const QuickViewKind20NoteCard: React.FC = ({ pubke pubkey, }); const [imageError, setImageError] = useState(false); + const [tryWithoutProxy, setTryWithoutProxy] = useState(false); - if (!image || !image.startsWith("http") || imageError) return null; + if (!image || !image.startsWith("http")) return null; + if (imageError && tryWithoutProxy) return null; + + const useImgProxy = process.env.NEXT_PUBLIC_ENABLE_IMGPROXY === "true" && !tryWithoutProxy; + + image = useImgProxy ? getProxiedImageUrl(image, 500, 0) : image; text = text.replaceAll('\n', ' '); const encodedNoteId = nip19.noteEncode(event.id) @@ -44,7 +50,13 @@ const QuickViewKind20NoteCard: React.FC = ({ pubke alt={text} className='w-full h-full rounded lg:rounded-lg object-cover' loading="lazy" - // onError={() => setImageError(true)} + onError={() => { + if (tryWithoutProxy) { + setImageError(true); + } else { + setTryWithoutProxy(true); + } + }} style={{ objectPosition: 'center' }} />
diff --git a/utils/utils.ts b/utils/utils.ts index d22ee16..1caabed 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -54,4 +54,19 @@ export async function signEvent(loginType: string | null, event: NostrEvent): Pr } console.log(eventSigned); return eventSigned; +} + +// Create proxied image URL +export const getProxiedImageUrl = (url: string, width: number, height: number) => { + if (!url.startsWith("http")) return url; + try { + // Encode the URL to be used in the proxy + const encodedUrl = encodeURIComponent(url); + const imgproxyEnv = process.env.NEXT_PUBLIC_IMGPROXY_URL; + const imgproxyUrl = new URL(imgproxyEnv || "https://imgproxy.example.com"); + return `${imgproxyUrl}_/resize:fit:${width}:${height}/plain/${encodedUrl}`; + } catch (error) { + console.error("Error creating proxied image URL:", error); + return url; + } } \ No newline at end of file