From d09cd7e5086f7b483bae94e7962c16e9fc13ede1 Mon Sep 17 00:00:00 2001 From: zenn Date: Fri, 3 Apr 2026 15:34:13 +0900 Subject: [PATCH] =?UTF-8?q?=EC=88=98=EC=A0=95:=20=EC=97=90=EB=94=94?= =?UTF-8?q?=ED=84=B0=20=EC=95=84=EC=9D=B4=ED=85=9C=20=EC=9A=B0=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=20=EB=B3=B5=EC=A0=9C=20=EB=A9=94=EB=89=B4=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EB=A9=94=EB=89=B4=20=EC=B0=A8=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/history.md | 4 +++ docs/todo.md | 1 + docs/update.md | 5 ++++ frontend/src/views/TierEditorView.vue | 37 +++++++++++++++++++++------ 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/docs/history.md b/docs/history.md index 932fb42..04ce7fa 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,9 @@ # 의사결정 이력 +## 2026-04-03 v1.4.68 +- 우클릭 복제 UX는 카드 영역과 썸네일 이미지 중 어디를 눌러도 같은 동작이어야 하므로, 개별 카드의 버블링 이벤트만 믿기보다 전역 캡처 단계에서 아이템 우클릭을 먼저 가로채는 방식이 더 안전하다고 판단했다. +- 편집기에서는 아이템 이미지를 브라우저 기본 이미지처럼 드래그하거나 저장 메뉴로 여는 것보다 보드 조작이 우선이므로, 썸네일 이미지의 기본 드래그도 명시적으로 꺼두는 편이 맞다고 정리했다. + ## 2026-04-03 v1.4.67 - 이미지 최적화는 해시 기반 중복 재사용을 하기 때문에, 프로필 아바타로 올린 이미지가 우연히 템플릿/사용자 아이템과 같은 `src`를 공유할 수 있다. 이때 자산 카드 쪽을 무조건 숨기면 “실제로는 프로필 이미지로 쓰이는데 관리자 필터에서 안 보이는 상태”가 생기므로, 아바타/썸네일 참조가 있는 `src`는 자산 카드도 유지하는 편이 맞다고 판단했다. diff --git a/docs/todo.md b/docs/todo.md index 91ac68b..297e525 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -1,6 +1,7 @@ # 할 일 및 이슈 ## 단기 확인 +- `v1.4.68`에서 아이템 우클릭 처리를 `window` 캡처 단계로 보강했으므로, 보드에 배치된 아이템/미사용 풀 아이템/아이템 썸네일 이미지 위에서 각각 우클릭했을 때 브라우저 기본 메뉴 대신 `아이템 복제` 메뉴가 바로 뜨는지 QA한다. - `v1.4.67`에서 같은 `src`가 프로필 아바타와 템플릿/사용자 아이템으로 동시에 쓰여도 자산 카드를 유지하도록 바꿨으므로, 운영 관리자 화면의 `전체 이미지`와 `프로필 이미지` 필터에서 실제 아바타가 보이고 상세 모달의 공유 참조 목록도 자연스럽게 읽히는지 QA한다. - 아이템 우클릭 복제 기능을 추가했으므로, 템플릿 아이템 복제/커스텀 아이템 복제/이미 보드에 배치된 아이템 복제 각각에서 복제본이 미사용 풀 맨 앞에 생기고 원본과 복제본을 서로 다른 칸에 동시에 둘 수 있는지 QA한다. - 복제본은 `dup-...` 새 ID로 저장되므로, 저장 후 재진입/티어표 복사본 생성/뷰어 모드 열람에서도 복제본이 그대로 유지되는지와, 템플릿 업데이트 요청에 복제된 커스텀 아이템이 포함될 때 운영상 이상이 없는지 확인한다. diff --git a/docs/update.md b/docs/update.md index 18fd945..f9fb84b 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,10 @@ # 업데이트 로그 +## 2026-04-03 v1.4.68 +- 티어표 편집 화면에서 아이템을 우클릭해도 브라우저 기본 컨텍스트 메뉴가 먼저 떠서 `아이템 복제` 메뉴를 누르기 어려울 수 있던 부분을 보강했다. +- 기존에는 각 아이템 카드의 `@contextmenu.prevent`에 주로 의존했지만, 이제는 `window` 캡처 단계에서 `[data-item-id]` 대상 우클릭을 먼저 잡아 기본 메뉴를 막고 커스텀 복제 메뉴를 열도록 바꿨다. +- 아이템 썸네일 이미지에도 `draggable="false"`를 명시해, 이미지 자체의 기본 드래그/컨텍스트 동작이 편집 조작보다 앞서는 상황을 줄였다. + ## 2026-04-03 v1.4.67 - 관리자 아이템 관리에서 프로필 아바타가 `전체 이미지`와 `프로필 이미지` 필터에 보이지 않을 수 있던 문제를 수정했다. - 원인은 `image_assets`의 같은 `src`가 템플릿 아이템이나 사용자 아이템에서도 쓰이는 경우, 자산 카드 생성 단계에서 해당 `src`를 무조건 제외하던 필터였다. 이제는 `users.avatar_src`나 각종 썸네일 참조로 실제 사용 중인 자산이면 같은 이미지가 다른 아이템에 재사용되더라도 자산 카드도 함께 유지한다. diff --git a/frontend/src/views/TierEditorView.vue b/frontend/src/views/TierEditorView.vue index d828bcf..662684b 100644 --- a/frontend/src/views/TierEditorView.vue +++ b/frontend/src/views/TierEditorView.vue @@ -460,9 +460,22 @@ function duplicateItemToPool() { } function handleGlobalContextMenu(event) { - if (!itemContextMenu.value.open) return const target = event?.target - if (target?.closest?.('[data-item-context-menu]') || target?.closest?.('[data-item-id]')) return + if (target?.closest?.('[data-item-context-menu]')) { + event?.preventDefault?.() + event?.stopPropagation?.() + return + } + + const itemEl = target?.closest?.('[data-item-id]') + if (canEdit.value && itemEl?.dataset?.itemId) { + event?.preventDefault?.() + event?.stopPropagation?.() + openItemContextMenu(itemEl.dataset.itemId, event) + return + } + + if (!itemContextMenu.value.open) return closeItemContextMenu() } @@ -1316,7 +1329,7 @@ watch( onMounted(() => { if (typeof window === 'undefined') return window.addEventListener('pointerdown', handleGlobalPointerDown) - window.addEventListener('contextmenu', handleGlobalContextMenu) + window.addEventListener('contextmenu', handleGlobalContextMenu, true) window.addEventListener('blur', closeItemContextMenu) window.addEventListener('scroll', closeItemContextMenu, true) }) @@ -1324,7 +1337,7 @@ onMounted(() => { onUnmounted(() => { if (typeof window !== 'undefined') { window.removeEventListener('pointerdown', handleGlobalPointerDown) - window.removeEventListener('contextmenu', handleGlobalContextMenu) + window.removeEventListener('contextmenu', handleGlobalContextMenu, true) window.removeEventListener('blur', closeItemContextMenu) window.removeEventListener('scroll', closeItemContextMenu, true) } @@ -1643,9 +1656,13 @@ onUnmounted(() => { :class="{ 'cell--selected': selectedItemId === id }" :data-item-id="id" @click.stop="selectItemByClick(id)" - @contextmenu.prevent.stop="openItemContextMenu(id, $event)" > - +
{{ itemsById[id]?.label || id }}