diff --git a/src/components/editor/ImageUploadInput.tsx b/src/components/editor/ImageUploadInput.tsx index 897c4c7..065b526 100644 --- a/src/components/editor/ImageUploadInput.tsx +++ b/src/components/editor/ImageUploadInput.tsx @@ -2,7 +2,7 @@ import React, { useRef, useState } from 'react'; import { Upload, Loader2, Image as ImageIcon } from 'lucide-react'; -import { shrinkImage, uploadFile } from '@/lib/upload'; +import { shrinkImage, shrinkRemoteImage, uploadFile } from '@/lib/upload'; interface ImageUploadInputProps { label?: string; @@ -27,16 +27,13 @@ export const ImageUploadInput: React.FC = ({ const fileInputRef = useRef(null); const [isUploading, setIsUploading] = useState(false); - const handleFileChange = async (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; - + const uploadImage = async (file: File) => { setIsUploading(true); try { const small = await shrinkImage(file); onChange(await uploadFile(small, small.name)); - } catch (err: any) { - alert(`Chyba pri nahrávaní obrázka: ${err.message}`); + } catch (err) { + alert(`Chyba pri nahrávaní obrázka: ${(err as Error).message}`); } finally { setIsUploading(false); if (fileInputRef.current) { @@ -45,6 +42,43 @@ export const ImageUploadInput: React.FC = ({ } }; + const handleFileChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) uploadImage(file); + }; + + // Vložená URL: príliš veľký obrázok sa zmenší a uloží k nám (posledná spracovaná adresa, aby sa nerobilo dvakrát) + const lastCheckedUrl = useRef(''); + const checkUrl = async (url: string) => { + url = url.trim(); + if (!url || url === lastCheckedUrl.current) return; + lastCheckedUrl.current = url; + setIsUploading(true); + try { + const smaller = await shrinkRemoteImage(url); + if (smaller) { + lastCheckedUrl.current = smaller; + onChange(smaller); + } + } catch (err) { + console.warn('Obrázok z URL sa nepodarilo zmenšiť:', err); + } finally { + setIsUploading(false); + } + }; + + const handlePaste = (e: React.ClipboardEvent) => { + // obrázok zo schránky (Ctrl+V) → nahrá sa ako súbor + const file = [...e.clipboardData.files].find((f) => f.type.startsWith('image/')); + if (file) { + e.preventDefault(); + uploadImage(file); + return; + } + const text = e.clipboardData.getData('text'); + if (/^https?:\/\//i.test(text.trim())) setTimeout(() => checkUrl(text), 0); + }; + return (
{label && } @@ -71,6 +105,8 @@ export const ImageUploadInput: React.FC = ({ type="text" value={value || ''} onChange={(e) => onChange(e.target.value)} + onPaste={handlePaste} + onBlur={(e) => checkUrl(e.target.value)} placeholder={placeholder} className="flex-1 min-w-0 text-xs font-mono border border-gray-300 rounded-lg p-2 bg-white" /> diff --git a/src/lib/upload.ts b/src/lib/upload.ts index ed2118e..d9e35e6 100644 --- a/src/lib/upload.ts +++ b/src/lib/upload.ts @@ -21,10 +21,10 @@ export async function uploadFile(file: Blob, name: string): Promise { return data.url; } -// Zmenšenie veľkej fotky pred nahratím: v e-maile má obrázok najviac 600 px (×2 pre ostré displeje), -// tlačové podklady s tisíckami px a desiatkami MB sú zbytočné (a bucket berie najviac 10 MB). +// Zmenšenie veľkej fotky pred nahratím: v e-maile má obrázok najviac 600 px, dlhšia strana 1000 px stačí +// aj na ostré displeje – tlačové podklady s tisíckami px a desiatkami MB sú zbytočné (bucket berie najviac 10 MB). // PNG ostáva PNG kvôli priehľadnosti; SVG a GIF (animácie) sa nechávajú tak. -const MAX_SIDE = 2000; +const MAX_SIDE = 1000; const MAX_BYTES = 9 * 1024 * 1024; export async function shrinkImage(file: File): Promise { @@ -54,3 +54,31 @@ export async function shrinkImage(file: File): Promise { const name = type === 'image/jpeg' ? file.name.replace(/\.\w+$/, '.jpg') : file.name; return new File([blob], name, { type }); } + +// Je adresa už v našom úložisku? +export function isOwnStorageUrl(url: string): boolean { + return url.includes('/storage/v1/object/public/newsletter-assets/'); +} + +// Obrázok vložený cez URL: stiahne sa (priamo, ak to cudzí server dovolí, inak cez /api/img), +// a ak je väčší ako 1000 px, zmenší sa a nahrá do úložiska. Vráti novú adresu, alebo null, ak netreba nič meniť. +export async function shrinkRemoteImage(url: string): Promise { + if (!/^https?:\/\//i.test(url) || isOwnStorageUrl(url)) return null; + let blob: Blob | null = null; + try { + const res = await fetch(url); + if (res.ok) blob = await res.blob(); + } catch { + // CORS – skúsime cez náš proxy + } + if (!blob || !blob.type.startsWith('image/')) { + const res = await fetch(`/api/img?url=${encodeURIComponent(url)}`); + if (!res.ok) return null; + blob = await res.blob(); + } + const name = decodeURIComponent(new URL(url).pathname.split('/').pop() || '') || 'obrazok.png'; + const file = new File([blob], name, { type: blob.type }); + const small = await shrinkImage(file); + if (small === file) return null; + return uploadFile(small, small.name); +}