From e89e19f3650c3d48adf4783e9bedc59c65e7d06b Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Tue, 14 Apr 2026 15:53:09 +0900 Subject: [PATCH] =?UTF-8?q?feat(library):=20=EC=9E=90=EB=A3=8C=EC=8B=A4=20?= =?UTF-8?q?=EB=93=9C=EB=9E=98=EA=B7=B8=20=EC=97=85=EB=A1=9C=EB=93=9C=20+?= =?UTF-8?q?=20=EC=98=A4=EB=B2=84=EB=A0=88=EC=9D=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 자료실 페이지에서 드래그 앤 드롭 업로드 지원. 업로드 후 자료실 내에서 트리+목록 새로고침 (문서 페이지로 이동하지 않음). Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/routes/library/+page.svelte | 50 ++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/frontend/src/routes/library/+page.svelte b/frontend/src/routes/library/+page.svelte index ef0353d..bdb6063 100644 --- a/frontend/src/routes/library/+page.svelte +++ b/frontend/src/routes/library/+page.svelte @@ -152,13 +152,43 @@ window.open(`/api/documents/${doc.id}/preview?token=${getAccessToken()}&download=true`); } - // ─── 업로드 ─── + // ─── 업로드 (버튼 + 드래그) ─── + const MAX_UPLOAD_BYTES = 100 * 1024 * 1024; let fileInput; let uploadingCount = $state(0); + let dragging = $state(false); + let dragCounter = 0; - async function handleUpload(e) { - const files = Array.from(e.target.files || []); + onMount(() => { + function onDragEnter(e) { e.preventDefault(); dragCounter++; dragging = true; } + function onDragOver(e) { e.preventDefault(); } + function onDragLeave(e) { e.preventDefault(); dragCounter--; if (dragCounter <= 0) { dragging = false; dragCounter = 0; } } + function onDrop(e) { e.preventDefault(); dragging = false; dragCounter = 0; uploadFiles(e.dataTransfer?.files); } + window.addEventListener('dragenter', onDragEnter); + window.addEventListener('dragover', onDragOver); + window.addEventListener('dragleave', onDragLeave); + window.addEventListener('drop', onDrop); + return () => { + window.removeEventListener('dragenter', onDragEnter); + window.removeEventListener('dragover', onDragOver); + window.removeEventListener('dragleave', onDragLeave); + window.removeEventListener('drop', onDrop); + }; + }); + + function handleUpload(e) { + uploadFiles(e.target.files); + fileInput.value = ''; + } + + async function uploadFiles(fileList) { + const allFiles = Array.from(fileList || []); + const files = allFiles.filter(f => f.size <= MAX_UPLOAD_BYTES); + const tooLarge = allFiles.filter(f => f.size > MAX_UPLOAD_BYTES); + if (tooLarge.length > 0) { + addToast('error', `100MB 초과 파일 ${tooLarge.length}건 제외됨`); + } if (files.length === 0) return; uploadingCount = files.length; @@ -178,7 +208,6 @@ } uploadingCount = 0; - fileInput.value = ''; if (success > 0) { addToast('success', `${success}건 업로드 완료`); @@ -460,3 +489,16 @@ + + +{#if dragging} +
+
+ +

자료실에 파일 업로드

+ {#if activePath} +

경로: {activePath}

+ {/if} +
+
+{/if}