From 9e82fad8757c7076e6d61b4d9eeadacb435758b4 Mon Sep 17 00:00:00 2001 From: Ender Date: Fri, 24 Oct 2025 15:46:16 +0200 Subject: [PATCH] feat: add clipboard paste-to-upload for images in media library --- apps/admin/src/components/MediaLibrary.tsx | 43 +++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/apps/admin/src/components/MediaLibrary.tsx b/apps/admin/src/components/MediaLibrary.tsx index 41720a7..0a60df9 100644 --- a/apps/admin/src/components/MediaLibrary.tsx +++ b/apps/admin/src/components/MediaLibrary.tsx @@ -13,6 +13,8 @@ export default function MediaLibrary({ onInsert, onSetFeature, showSetFeature }: const [error, setError] = useState(''); const [query, setQuery] = useState(''); const [sortBy, setSortBy] = useState<'date_desc' | 'date_asc' | 'name_asc' | 'size_desc'>('date_desc'); + const [uploading, setUploading] = useState(false); + const [uploadingCount, setUploadingCount] = useState(0); const load = async () => { try { @@ -33,6 +35,42 @@ export default function MediaLibrary({ onInsert, onSetFeature, showSetFeature }: load(); }, []); + // Paste-to-upload clipboard images + useEffect(() => { + const handler = async (e: ClipboardEvent) => { + try { + const items = e.clipboardData?.items; + if (!items || items.length === 0) return; + const files: File[] = []; + for (let i = 0; i < items.length; i++) { + const it = items[i]; + if (it.kind === 'file') { + const f = it.getAsFile(); + if (f && f.type.startsWith('image/')) files.push(f); + } + } + if (files.length === 0) return; + setUploading(true); + setUploadingCount(files.length); + for (const file of files) { + try { + const fd = new FormData(); + fd.append('image', file, file.name || 'pasted-image.png'); + const res = await fetch('/api/media/image', { method: 'POST', body: fd }); + if (!res.ok) throw new Error(await res.text()); + } catch (err: any) { + setError(err?.message || 'Clipboard upload failed'); + } + } + setUploading(false); + setUploadingCount(0); + await load(); + } catch {} + }; + window.addEventListener('paste', handler); + return () => window.removeEventListener('paste', handler); + }, []); + const del = async (key: string) => { try { setError(''); @@ -81,7 +119,7 @@ export default function MediaLibrary({ onInsert, onSetFeature, showSetFeature }: Media Library - + Largest + + Tip: paste an image (Cmd/Ctrl+V) to upload{uploading ? ` — uploading ${uploadingCount}…` : ''} + {error && {error}}