From 6886c57d04060528b6879d7e183368ba6d5459ac Mon Sep 17 00:00:00 2001 From: zenn Date: Mon, 28 Sep 2026 16:52:14 +0900 Subject: [PATCH] =?UTF-8?q?v1.12.21=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EB=AF=B8=EB=94=94=EC=96=B4=20=EC=A1=B0=EC=9E=91=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20=EB=B0=8F=20CSP=20=EA=B4=80=EC=B0=B0=20=EC=95=88?= =?UTF-8?q?=EB=82=B4=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/admin/AdminMediaFolderPanel.vue | 97 +++++++-- components/admin/AdminMediaGrid.vue | 21 +- components/admin/AdminMediaVideoThumbnail.vue | 1 + docs/changelog.md | 5 + docs/deploy.md | 2 + docs/history.md | 6 + docs/map.md | 9 +- docs/spec.md | 10 +- docs/todo.md | 6 +- docs/update.md | 11 + package-lock.json | 4 +- package.json | 2 +- pages/admin/index.vue | 28 ++- pages/admin/media/index.vue | 140 ++++++++++--- .../e2e/admin-guard-and-settings.spec.js | 3 +- .../e2e/admin-media-interactions.spec.js | 197 ++++++++++++++++++ tests/playwright/e2e/admin-publish.spec.js | 3 +- .../playwright/e2e/admin-regressions.spec.js | 3 +- 18 files changed, 488 insertions(+), 60 deletions(-) create mode 100644 tests/playwright/e2e/admin-media-interactions.spec.js diff --git a/components/admin/AdminMediaFolderPanel.vue b/components/admin/AdminMediaFolderPanel.vue index 672f012..57a203f 100644 --- a/components/admin/AdminMediaFolderPanel.vue +++ b/components/admin/AdminMediaFolderPanel.vue @@ -43,6 +43,14 @@ const props = defineProps({ deletingFolder: { type: String, default: '' + }, + draggingCount: { + type: Number, + default: 0 + }, + busy: { + type: Boolean, + default: false } }) @@ -53,6 +61,8 @@ const emit = defineEmits([ 'select-folder' ]) +const hoveredFolder = ref(null) + /** * 논리 폴더 경로가 썸네일 전용 루트인지 확인한다. * @param {string} folder - 폴더 경로 @@ -80,6 +90,54 @@ const getFolderName = (folder) => String(folder || '').split('/').filter(Boolean * @returns {number} 폴더 깊이 */ const getFolderDepth = (folder) => Math.max(String(folder || '').split('/').filter(Boolean).length - 1, 0) + +/** + * 내부 미디어 드래그만 폴더 이동 대상으로 허용한다. + * @param {DragEvent} event - 드래그 이벤트 + * @param {string} folder - 대상 폴더 + * @returns {void} + */ +const onFolderDragOver = (event, folder) => { + if (!props.draggingCount || props.busy || isThumbnailFolderPath(folder) || (props.deletingFolder && props.deletingFolder === folder)) { + return + } + event.preventDefault() + if (event.dataTransfer) { + event.dataTransfer.dropEffect = 'move' + } + hoveredFolder.value = folder +} + +/** + * 폴더 내부 요소 간 이동을 제외하고 드롭 강조를 해제한다. + * @param {DragEvent} event - 드래그 이탈 이벤트 + * @returns {void} + */ +const onFolderDragLeave = (event) => { + if (!event.currentTarget.contains(event.relatedTarget)) { + hoveredFolder.value = null + } +} + +/** + * 유효한 내부 드래그에 한해 폴더 이동을 요청한다. + * @param {DragEvent} event - 드롭 이벤트 + * @param {string} folder - 대상 폴더 + * @returns {void} + */ +const onFolderDrop = (event, folder) => { + event.preventDefault() + hoveredFolder.value = null + if (props.draggingCount && !props.busy && !isThumbnailFolderPath(folder) && props.deletingFolder !== folder) { + emit('drop-folder', folder) + } +} + +watch(() => props.draggingCount, (count) => { + if (!count) { + hoveredFolder.value = null + } +})