드래그 초기화와 문구 정리

This commit is contained in:
2026-04-07 16:54:14 +09:00
parent 5dbc83c79e
commit 6f8e623b56
7 changed files with 27 additions and 10 deletions

View File

@@ -1,5 +1,10 @@
# 의사결정 이력
## 2026-04-07 v1.1.29
- 프리뷰 여부는 페이지 맥락과 우측 뷰어 카드만으로 충분히 전달되므로, 헤더 아이브로우에 `Preview`를 한 줄 더 두는 것은 중복 정보라고 정리했다.
- `커스텀 티어표 만들기`는 버튼 문구로 다소 길고 반복될수록 무거워 보여, 주요 CTA 에서는 `커스텀 티어표`로 짧게 통일하는 편이 더 낫다고 판단했다.
- 스켈레톤과 드래그 라이브러리를 함께 쓸 때는 “실제 드래그 대상 DOM이 렌더링된 뒤에만 초기화”가 핵심이라고 다시 확인했다. 로딩 placeholder 상태에서 초기화한 `Sortable` 인스턴스는 비어 있는 타깃을 잡아 드래그 회귀를 만들 수 있다.
## 2026-04-07 v1.1.27
- 티어표 화면 헤더도 다른 주요 페이지와 같은 `eyebrow / title / desc` 문법을 따라야 전체 앱 톤이 정리된다고 판단했다.
- 편집 가능한 화면과 보기 전용 화면은 헤더에서 전달해야 할 정보가 다르므로, 같은 문법은 유지하되 내용 규칙은 분리하는 편이 맞다고 정리했다. 편집 화면은 “무엇을 만들고 있는가”, 보기 화면은 “무엇을 보고 있는가”가 더 중요하다.

View File

@@ -17,7 +17,7 @@
## `/editor/:topicId/new`, `/editor/:topicId/:tierListId`
- 화면 파일: `frontend/src/views/TierEditorView.vue`
- 역할: 주제 slug 기반 에디터 진입, 티어 그룹 편집, 티어 행 추가/삭제, 보드 옆 아이템 풀에서 관리자 아이템/커스텀 아이템 다중 드래그 앤 드롭 업로드, 아이템 클릭 선택 후 셀/풀 재배치, 아이템 우클릭 메뉴 기반 복제본 생성, 헤더는 공통 `pageHead` 문법을 따르며 편집 가능 상태에서는 `NEW/EDIT + 템플릿 이름 + 작업 가이드`, 보기 전용 상태에서는 `템플릿 이름 + 티어표 제목 + 설명` 구조를 사용하고 아이브로우 클릭 시 해당 주제 허브로 이동, 공통 오른쪽 레일 안에 직접 배치되는 우측 편집 섹션에서 티어표 제목/설명/대표 썸네일/공개 여부/저장 제어와 커스텀 아이템 이름 정리, 읽기 전용 상태의 즐겨찾기 단독 CTA, PNG 다운로드, 저장된 티어표 기준 템플릿 등록/업데이트 요청, 댓글 카드 표시, 데이터가 준비되기 전에는 편집 모드/프리뷰 모드 모두 전용 스켈레톤 레이아웃을 먼저 보여주고, 저장용 랜덤 제목이나 내부 ID는 화면 표시용 제목으로 직접 노출하지 않으며, `?preview=1` 진입 시 공통 앱 셸은 유지한 채 중앙 본문에서 완성본 프리뷰와 하단 댓글 카드를 렌더링하며, 우측 뷰어 카드(`공유 티어표 보기`)는 스폰서 카드 바로 아래에서 유지하고 즐겨찾기 CTA도 함께 노출
- 역할: 주제 slug 기반 에디터 진입, 티어 그룹 편집, 티어 행 추가/삭제, 보드 옆 아이템 풀에서 관리자 아이템/커스텀 아이템 다중 드래그 앤 드롭 업로드, 아이템 클릭 선택 후 셀/풀 재배치, 아이템 우클릭 메뉴 기반 복제본 생성, 헤더는 공통 `pageHead` 문법을 따르며 편집 가능 상태에서는 `NEW/EDIT + 템플릿 이름 + 작업 가이드`, 보기 전용 상태에서는 `템플릿 이름 + 티어표 제목 + 설명` 구조를 사용하고 아이브로우 클릭 시 해당 주제 허브로 이동하며 별도 `Preview` 아이브로우는 두지 않음, 공통 오른쪽 레일 안에 직접 배치되는 우측 편집 섹션에서 티어표 제목/설명/대표 썸네일/공개 여부/저장 제어와 커스텀 아이템 이름 정리, 읽기 전용 상태의 즐겨찾기 단독 CTA, PNG 다운로드, 저장된 티어표 기준 템플릿 등록/업데이트 요청, 댓글 카드 표시, 데이터가 준비되기 전에는 편집 모드/프리뷰 모드 모두 전용 스켈레톤 레이아웃을 먼저 보여주고, 저장용 랜덤 제목이나 내부 ID는 화면 표시용 제목으로 직접 노출하지 않으며, 스켈레톤이 내려간 뒤 실제 보드/풀 DOM에서만 드래그 초기화를 실행하고, `?preview=1` 진입 시 공통 앱 셸은 유지한 채 중앙 본문에서 완성본 프리뷰와 하단 댓글 카드를 렌더링하며, 우측 뷰어 카드(`공유 티어표 보기`)는 스폰서 카드 바로 아래에서 유지하고 즐겨찾기 CTA도 함께 노출
- 연동 API: `GET /api/topics/:topicId`, `GET /api/tierlists/:id`, `GET /api/tierlists/:id/comments`, `POST /api/tierlists/:id/comments`, `DELETE /api/tierlists/:id/comments/:commentId`, `POST /api/tierlists/:id/favorite`, `DELETE /api/tierlists/:id/favorite`, `POST /api/tierlists/thumbnail`, `POST /api/tierlists/custom-items`, `POST /api/tierlists`, `POST /api/tierlists/template-request`
## `/comments`

View File

@@ -72,6 +72,7 @@
- 즐겨찾기: 내가 즐겨찾기한 티어표
- 위 네 화면의 목록 데이터는 현재 페이지네이션이나 무한 스크롤 없이 조회 결과 전체를 한 번에 렌더링한다.
- 특정 주제 화면(`TopicHubView`)의 헤더는 데이터 준비 전 문자열 fallback 대신 제목/설명 스켈레톤만 보여준다.
- `freeform` 시작 액션과 주요 CTA 문구는 `커스텀 티어표`로 통일한다.
- 저장된 티어표에는 댓글 스레드가 붙을 수 있다. 작성자 본인 편집 화면에서는 `작업 팁` 아래, 작성자가 아닌 사용자의 보기 전용 화면에서는 `preview` 보드 아래에서 같은 댓글 카드를 사용한다.
- 댓글 알림 메뉴는 좌측 사이드 `댓글 관리`로 노출하며, 읽지 않은 댓글이 하나라도 있으면 빨간 dot을 표시한다.
- 댓글 관리(`/comments`)는 기본적으로 `안 읽은 댓글만 보기`를 켠 상태로 시작한다.
@@ -98,6 +99,8 @@
- 편집 모드와 `preview=1` 뷰어 모드 모두 제목/보드/우측 패널 데이터가 준비되기 전까지는 실제 화면 대신 전용 스켈레톤 레이아웃을 먼저 보여준다.
- 저장용 랜덤 제목은 내부 저장/도배 방지용으로만 쓰고, 화면 표시용 제목 fallback 에서는 내부 `tierListId`나 랜덤 문자열을 직접 노출하지 않는다.
- 헤더 문법은 공통 `pageHead` 흐름을 따르되, 편집 가능 상태에서는 `NEW/EDIT + 현재 템플릿 이름 + 작업 가이드`, 보기 전용 상태에서는 `템플릿 이름 + 티어표 제목 + 티어표 설명` 규칙을 사용한다.
- 보기 전용 헤더에는 별도 `Preview` 아이브로우를 두지 않고, 템플릿 이름 아이브로우만 남긴다.
- 드래그 초기화는 스켈레톤이 내려간 뒤 실제 보드/풀 DOM이 렌더링된 다음 `Sortable`을 붙이는 순서를 따른다.
- 제목, 설명, 대표 썸네일, 공개 여부, 저장/삭제/요청 액션을 우측 로컬 패널에 배치한다. 템플릿 등록/업데이트 요청 버튼은 저장된 티어표가 있을 때만 노출하며, 제목이 비어 있는 상태에서 저장하면 랜덤 고유 제목을 먼저 부여해 저장본을 만든다.
- 보기 전용 상태에서 상단 아이브로우(템플릿 이름)는 해당 주제 허브로 이동하는 액션으로 사용하며, 편집 중 미저장 변경이 있으면 이동 전에 확인 모달을 띄운다.
- 보드 바로 옆에는 드래그용 아이템 풀을 별도 패널로 두고, 커스텀 아이템 이름 정리 목록은 우측 편집 패널 내부에서 관리한다.

View File

@@ -1,6 +1,9 @@
# 할 일 및 이슈
## 단기 확인
- `v1.1.29` 이후 보기 전용 티어표 헤더에서 아이브로우가 템플릿 이름 한 줄만 남고, 더 이상 `Preview`와 중복되지 않는지 확인한다.
- `v1.1.29` 이후 좌측 빠른 시작 버튼과 템플릿 화면 액션의 `커스텀 티어표` 문구가 전체 화면에서 같은 톤으로 보이는지 확인한다.
- `v1.1.29` 이후 편집 화면에서 `풀 → 보드`, `보드 → 다른 칸`, `보드 → 풀` 드래그가 모두 다시 동작하는지 최우선으로 확인한다.
- `v1.1.27` 이후 편집 가능한 티어표 화면에서 헤더가 `NEW/EDIT → 템플릿 이름 → 작업 가이드` 순서로 자연스럽게 보이는지 확인한다.
- `v1.1.27` 이후 보기 전용 티어표 화면에서는 아이브로우만 템플릿 이동 링크로 동작하고, 제목은 더 이상 클릭 가능한 요소처럼 보이지 않는지 확인한다.
- `v1.1.27` 이후 freeform 티어표도 헤더 제목이 비지 않고 `커스텀 티어표`로 안정적으로 보이는지 확인한다.

View File

@@ -1,5 +1,12 @@
# 업데이트 로그
## 2026-04-07 v1.1.29
- 보기 전용 티어표 헤더에서 중복이던 `Preview` 아이브로우를 제거했다. 이제 프리뷰 화면 헤더는 템플릿 이름 아이브로우 한 줄만 남고, 그 아래 티어표 제목과 설명이 이어진다.
- 앱 전반의 `커스텀 티어표 만들기` 문구를 `커스텀 티어표`로 짧게 통일했다. 좌측 빠른 시작 버튼, 템플릿 화면 우측 액션, 가이드 설명 문구까지 같은 표현을 사용한다.
- 스켈레톤 로딩 도입 후 드래그가 동작하지 않던 회귀를 수정했다. 원인은 `isEditorLoading`이 아직 true인 상태에서 `Sortable`을 먼저 초기화해 실제 드래그 대상 DOM이 없던 것이었고, 이제는 스켈레톤을 먼저 내린 뒤 `nextTick()` 후 실제 에디터 DOM에서 `initSortables()`를 실행한다.
- 이 수정으로 클릭 이동은 유지하면서도 `풀 → 보드`, `보드 → 보드`, `보드 → 풀` 드래그가 다시 정상 동작한다.
- 확인: `npm run build`
## 2026-04-07 v1.1.27
- 티어표 편집 화면 헤더를 다른 주요 화면과 같은 `pageHead__main` 3단 문법으로 재구성했다. 이제 편집 가능한 화면도 `eyebrow / title / desc` 흐름을 그대로 따른다.
- 보기 전용 티어표 화면은 `템플릿 이름 / 티어표 제목 / 티어표 설명` 규칙으로 정리했다. 기존처럼 제목 클릭으로 템플릿 화면으로 이동하지 않고, 아이브로우(템플릿 이름)만 클릭 이동 대상으로 바꾸고 hover/focus 스타일도 함께 넣었다.

View File

@@ -101,8 +101,9 @@ const guideSteps = [
id: 'select-topic',
title: '주제 또는 양식 선택',
summary: '주제 템플릿을 고르거나 커스텀 티어표 만들기로 바로 시작합니다.',
summary: '주제 템플릿을 고르거나 커스텀 티어표로 바로 시작합니다.',
description:
'홈 화면에서는 주제 템플릿을 선택하거나 커스텀 티어표 만들기로 바로 새 보드를 열 수 있어요. 원하는 주제를 먼저 고르면 해당 주제의 공개 티어표도 같이 살펴볼 수 있어서, 완전히 처음 만드는지 기존 흐름을 참고할지 결정하기 쉽습니다.',
'홈 화면에서는 주제 템플릿을 선택하거나 커스텀 티어표로 바로 새 보드를 열 수 있어요. 원하는 주제를 먼저 고르면 해당 주제의 공개 티어표도 같이 살펴볼 수 있어서, 완전히 처음 만드는지 기존 흐름을 참고할지 결정하기 쉽습니다.',
},
{
id: 'arrange-board',
@@ -174,10 +175,10 @@ const shouldLockRightRailBodyScroll = computed(() => isRightRailOverlay.value &&
const leftBottomPrimaryAction = computed(() => {
if (!authReady.value) return null
if (route.name === 'home' && auth.user) {
return { label: '커스텀 티어표 만들기', to: editorNewPath('freeform'), iconSrc: iconDashboardCustomize }
return { label: '커스텀 티어표', to: editorNewPath('freeform'), iconSrc: iconDashboardCustomize }
}
if (route.name === 'templates' && auth.user) {
return { label: '커스텀 티어표 만들기', to: editorNewPath('freeform'), iconSrc: iconDashboardCustomize }
return { label: '커스텀 티어표', to: editorNewPath('freeform'), iconSrc: iconDashboardCustomize }
}
if (route.name === 'topicHub') {
const target = editorNewPath(currentTopicId.value)
@@ -192,7 +193,7 @@ const routeMeta = computed(() => {
title: '홈',
subtitle: '공개 티어표 피드',
contextTitle: '빠른 시작',
contextText: auth.user ? '추천 티어표와 최신 공개 티어표를 둘러보고 바로 새 작업을 시작할 수 있어요.' : '로그인하면 커스텀 티어표 생성과 개인 목록 관리가 열립니다.',
contextText: auth.user ? '추천 티어표와 최신 공개 티어표를 둘러보고 바로 새 작업을 시작할 수 있어요.' : '로그인하면 커스텀 티어표 개인 목록 관리가 열립니다.',
actionLabel: '템플릿 보기',
action: () => {
router.push(templatesPath())
@@ -204,8 +205,8 @@ const routeMeta = computed(() => {
title: '템플릿',
subtitle: '주제 템플릿 선택',
contextTitle: '빠른 시작',
contextText: auth.user ? '주제 템플릿을 고르거나 커스텀 티어표 만들기로 바로 시작할 수 있어요.' : '로그인하면 커스텀 티어표 생성과 개인 목록 관리가 열립니다.',
actionLabel: auth.user ? '커스텀 티어표 만들기' : '로그인하러 가기',
contextText: auth.user ? '주제 템플릿을 고르거나 커스텀 티어표로 바로 시작할 수 있어요.' : '로그인하면 커스텀 티어표 개인 목록 관리가 열립니다.',
actionLabel: auth.user ? '커스텀 티어표' : '로그인하러 가기',
action: () => {
router.push(auth.user ? editorNewPath('freeform') : loginPath())
},

View File

@@ -1417,6 +1417,7 @@ async function loadEditorState() {
}
}
isEditorLoading.value = false
await nextTick()
if (loadToken !== editorLoadToken) return
@@ -1425,8 +1426,6 @@ async function loadEditorState() {
if (canEdit.value) {
await initSortables()
}
if (loadToken !== editorLoadToken) return
isEditorLoading.value = false
}
watch(
@@ -1492,7 +1491,6 @@ onUnmounted(() => {
<section v-else-if="previewMode" class="previewOnly" :style="{ '--thumb-size': `${iconSize}px` }">
<header class="pageHead">
<div class="pageHead__main">
<div class="pageHead__eyebrow">Preview</div>
<button
class="pageHead__eyebrow pageHead__eyebrowButton"
type="button"