Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6fdd780859 | |||
| f273233c41 |
@@ -1,5 +1,13 @@
|
||||
# 의사결정 이력
|
||||
|
||||
## 2026-04-07 v1.1.17
|
||||
- 가이드 모달은 같은 기능의 이동 수단을 중복으로 두기보다, 화살표와 점 네비게이션만 유지하는 편이 더 깔끔하다고 정리했다.
|
||||
- 설명 길이 때문에 페이지 전환마다 미디어 영역이 출렁이면 완성도가 떨어져 보이므로, 설명 블록에 최소 높이를 두는 방식으로 페이지 간 높이를 최대한 통일하기로 했다.
|
||||
|
||||
## 2026-04-07 v1.1.16
|
||||
- 전역 단축키는 영문 키만 처리하지 말고 두벌식 한글 자판 입력도 같은 의미로 받아들이는 편이 실제 사용성에 더 맞다고 정리했다.
|
||||
- `S/ㄴ`은 화면 문맥에 따라 “편집기에서는 아이템 검색, 일반 목록에서는 공통 검색창 포커스”로 나누는 것이 기존 습관과 새 검색 동선을 모두 살리는 절충안이라고 판단했다.
|
||||
|
||||
## 2026-04-07 v1.1.15
|
||||
- `나의 티어표`도 주요 목록 화면 중 하나이므로 왼쪽 공통 검색창 범위에서 빼는 것보다 포함하는 편이 더 일관적이라고 정리했다.
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
## 공통 레이아웃
|
||||
- 앱 셸 파일: `frontend/src/App.vue`
|
||||
- 역할: 좌측 내비게이션, 중앙 워크스페이스, 우측 컨텍스트 패널로 구성된 공통 앱 셸 렌더링, `홈 / 템플릿 / 댓글 관리` 네비게이션과 화면별 검색 placeholder 전환, `preview=1` 공유 프리뷰에서도 같은 좌우 레일과 중앙 헤더 유지, 로그인 상태 반영, 최근 즐겨찾기 바로가기와 전역 검색 입력, 댓글 알림 unread dot, 관리자 메뉴 노출 제어, 실제 SVG 에셋과 선형 SVG 아이콘이 혼합된 레일 UI, 전역 우측 상단 토스트 렌더링, 관리자/에디터 화면이 Teleport로 사용하는 `#local-right-rail-root` 대상 DOM을 상시 유지해 라우트 전환 중 우측 레일 언마운트 순서를 안정화
|
||||
- 역할: 좌측 내비게이션, 중앙 워크스페이스, 우측 컨텍스트 패널로 구성된 공통 앱 셸 렌더링, `홈 / 템플릿 / 댓글 관리` 네비게이션과 화면별 검색 placeholder 전환, `preview=1` 공유 프리뷰에서도 같은 좌우 레일과 중앙 헤더 유지, 로그인 상태 반영, 최근 즐겨찾기 바로가기와 전역 검색 입력, 댓글 알림 unread dot, 관리자 메뉴 노출 제어, 실제 SVG 에셋과 선형 SVG 아이콘이 혼합된 레일 UI, 전역 우측 상단 토스트 렌더링, 관리자/에디터 화면이 Teleport로 사용하는 `#local-right-rail-root` 대상 DOM을 상시 유지해 라우트 전환 중 우측 레일 언마운트 순서를 안정화, `S/ㄴ`, `G/ㅎ`, `L/ㅣ`, `A/ㅁ` 같은 전역 단축키 처리, 설정 가이드 모달 단계 이동/높이 안정화
|
||||
- 세부: 좌측 패널은 `248px` 기준 폭을 사용하되 축소 시 아이콘 중심의 좁은 레일로 전환하고, 우측 패널은 `320px` 기준 폭을 사용한다. 세 컬럼 모두 상단에 높이 `56px`의 헤더 블록을 유지한다. 중앙 헤더에는 고정 브랜드 `Tier Maker`와 서비스 설명이 표시되고, 우측 패널 토글은 닫혀 있을 때 중앙 헤더, 열려 있을 때 우측 헤더에 아이콘만 표시된다. 좌우 레일의 주요 액션은 각각 하단 `56px` 푸터 안에서 항상 보이도록 유지하면서 아래쪽 패딩으로 여백을 확보한다.
|
||||
|
||||
## 백엔드 진입점
|
||||
|
||||
@@ -49,6 +49,12 @@
|
||||
- `featuredTierLists`: 상단 추천 티어표
|
||||
- `tierLists`: 추천 제외 최신 공개 티어표
|
||||
- 홈, 템플릿, 나의 티어표, 즐겨찾기, 팔로우 피드 화면은 공통 `viewToggle`로 `그리드 / 리스트` 보기를 전환하며, 상태는 현재 라우트의 `?view=list` 쿼리로 반영한다.
|
||||
- 전역 단축키
|
||||
- `S/ㄴ`: 검색 포커스. 편집 화면에서는 아이템 검색창, 그 외 화면에서는 왼쪽 공통 검색창
|
||||
- `G/ㅎ`: 그리드 보기
|
||||
- `L/ㅣ`: 리스트 보기
|
||||
- `A/ㅁ`: 관리자 계정일 때 관리자 화면으로 이동
|
||||
- 설정의 가이드 모달은 좌우 화살표, 점 네비게이션, 좌측 단계 목록으로만 이동하고, 설명 영역은 최소 4줄 높이를 유지해 페이지별 높이 차이를 줄인다.
|
||||
- 왼쪽 공통 검색창은 현재 화면 범위만 검색한다.
|
||||
- 홈: 전체 공개 티어표
|
||||
- 템플릿: 공개 템플릿
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
# 할 일 및 이슈
|
||||
|
||||
## 단기 확인
|
||||
- `v1.1.17` 이후 설정의 가이드 모달에서 페이지를 넘길 때 썸네일 영역 위치가 이전보다 안정적으로 유지되는지 확인한다.
|
||||
- `v1.1.17` 이후 가이드 하단 `다음` 버튼이 사라지고, 좌우 화살표/점 네비게이션만으로도 단계 이동이 충분히 자연스러운지 확인한다.
|
||||
- `v1.1.16` 이후 `S/ㄴ`이 편집 화면에서는 아이템 검색, 일반 목록 화면에서는 왼쪽 공통 검색창 포커스로 정확히 나뉘어 동작하는지 확인한다.
|
||||
- `v1.1.16` 이후 `G/ㅎ`, `L/ㅣ`가 목록 화면에서만 그리드/리스트 전환을 수행하고, 입력칸을 타이핑 중일 때는 단축키가 발동하지 않는지 확인한다.
|
||||
- 관리자 계정에서만 `A/ㅁ`이 관리자 화면으로 이동하고, 일반 계정에서는 무시되는지 확인한다.
|
||||
- `v1.1.15` 이후 `나의 티어표`에서도 왼쪽 공통 검색창이 정상 동작하고, 검색 결과가 없을 때 전용 빈 상태 문구가 자연스럽게 보이는지 확인한다.
|
||||
- `v1.1.14` 이후 왼쪽 공통 검색창이 홈/템플릿/주제/팔로우 피드/즐겨찾기 각각의 범위만 정확히 검색하는지 확인한다.
|
||||
- `v1.1.14` 이후 주제 허브, 팔로우 피드, 즐겨찾기 화면 상단에서 중복 검색창이 모두 사라졌는지 확인한다.
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# 업데이트 로그
|
||||
|
||||
## 2026-04-07 v1.1.17
|
||||
- 설정의 `가이드 보기` 모달 9페이지 단축키 설명은 최근 추가된 전역 단축키 기준으로 최신 상태를 유지하도록 다시 확인했다.
|
||||
- 가이드 하단 `다음` 버튼은 좌우 화살표와 역할이 겹쳐 제거했다. 이제 단계 이동은 좌우 화살표, 점 네비게이션, 좌측 단계 목록만 사용한다.
|
||||
- `guideModal__stepDescription`에는 최소 4줄 높이를 부여해, 설명 길이에 따라 페이지를 넘길 때 미디어 영역이 위아래로 튀어 보이던 현상을 줄였다.
|
||||
- 확인: `npm run build`
|
||||
|
||||
## 2026-04-07 v1.1.16
|
||||
- 전역 단축키를 보강했다. `S/ㄴ`은 검색 포커스로 동작하며, 편집 화면에서는 기존처럼 아이템 검색창에, 그 외 화면에서는 왼쪽 공통 검색창에 포커스를 준다.
|
||||
- 새 보기 전환 단축키를 추가했다. `G/ㅎ`는 그리드 보기, `L/ㅣ`는 리스트 보기로 전환하며, 공통 `viewToggle`이 있는 목록 화면에서만 동작한다.
|
||||
- 관리자 계정에서는 `A/ㅁ` 단축키로 바로 관리자 화면(`/admin/featured`)으로 이동할 수 있게 했다.
|
||||
- 한글 두벌식 자판 상태에서도 `ㄴ/ㅎ/ㅣ/ㅁ`이 각각 `S/G/L/A`와 같은 의미로 처리되도록 맞췄다.
|
||||
- 가이드 모달의 단축키 설명도 현재 동작에 맞게 갱신했다.
|
||||
- 확인: `npm run build`
|
||||
|
||||
## 2026-04-07 v1.1.15
|
||||
- `나의 티어표`도 왼쪽 공통 검색창 범위에 포함했다. 이제 `/me` 화면에서 입력한 검색어는 내 저장 티어표 제목/주제명/작성자 이름 기준으로 즉시 필터링된다.
|
||||
- `MyTierListsView`는 `route.query.q`를 받아 클라이언트에서 목록을 필터링하고, 검색 결과가 없으면 전용 빈 상태 문구를 표시한다.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue'
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useAuthStore } from './stores/auth'
|
||||
import { commentsPath, editorNewPath, favoritesPath, followingFeedPath, homePath, loginPath, mePath, templatesPath } from './lib/paths'
|
||||
@@ -32,6 +32,8 @@ const leftRailCollapsed = ref(false)
|
||||
const mobileLeftNavOpen = ref(false)
|
||||
const rightRailOpen = ref(true)
|
||||
const searchQuery = ref('')
|
||||
const searchInputEl = ref(null)
|
||||
const collapsedSearchInputEl = ref(null)
|
||||
const leftRailSearchPlaceholder = computed(() => {
|
||||
if (route.name === 'templates') return '주제 템플릿 검색'
|
||||
if (route.name === 'topicHub') return '이 템플릿의 공개 티어표 검색'
|
||||
@@ -155,7 +157,7 @@ const guideSteps = [
|
||||
title: '단축키로 빠른 조작',
|
||||
summary: '사이드 패널과 전체 화면을 키보드로 빠르게 전환합니다.',
|
||||
description:
|
||||
'[ 키는 왼쪽 사이드를 열고 닫고, ] 키는 오른쪽 사이드를 열고 닫습니다. F 키는 전체 화면 보기 토글, S 키는 티어표 편집 화면의 아이템 검색창으로 바로 이동할 때 사용할 수 있어요. 한글 입력 상태에서는 F 자리의 ㄹ, S 자리의 ㄴ 키도 같은 단축키로 처리됩니다. 각종 모달은 Esc 키로 닫을 수 있습니다. 단, 검색창이나 입력칸에 글을 쓰는 중에는 단축키가 동작하지 않도록 처리되어 있어요.',
|
||||
'[ 키는 왼쪽 사이드를 열고 닫고, ] 키는 오른쪽 사이드를 열고 닫습니다. F/ㄹ은 전체 화면, S/ㄴ은 검색 포커스(편집 화면에서는 아이템 검색), G/ㅎ은 그리드 보기, L/ㅣ는 리스트 보기, A/ㅁ은 관리자 계정일 때 관리자 화면으로 이동합니다. 각종 모달은 Esc 키로 닫을 수 있고, 입력칸에 글을 쓰는 중에는 단축키가 동작하지 않도록 처리되어 있어요.',
|
||||
},
|
||||
]
|
||||
const currentGuideStep = computed(() => guideSteps[guideStepIndex.value] || guideSteps[0])
|
||||
@@ -402,6 +404,7 @@ onMounted(async () => {
|
||||
})
|
||||
|
||||
function handleGlobalKeydown(event) {
|
||||
const normalizedKey = String(event.key || '').toLowerCase()
|
||||
if (event.key === 'Escape' && isGuideModalOpen.value) {
|
||||
closeGuideModal()
|
||||
return
|
||||
@@ -423,14 +426,33 @@ function handleGlobalKeydown(event) {
|
||||
toggleRightRail()
|
||||
return
|
||||
}
|
||||
if (['f', 'ㄹ'].includes(String(event.key || '').toLowerCase())) {
|
||||
if (['f', 'ㄹ'].includes(normalizedKey)) {
|
||||
event.preventDefault()
|
||||
toggleFullscreen()
|
||||
return
|
||||
}
|
||||
if (['s', 'ㄴ'].includes(String(event.key || '').toLowerCase()) && ['editEditor', 'newEditor'].includes(String(route.name || ''))) {
|
||||
if (['s', 'ㄴ'].includes(normalizedKey)) {
|
||||
event.preventDefault()
|
||||
window.dispatchEvent(new CustomEvent('tier-maker:focus-editor-item-search'))
|
||||
if (['editEditor', 'newEditor'].includes(String(route.name || ''))) {
|
||||
window.dispatchEvent(new CustomEvent('tier-maker:focus-editor-item-search'))
|
||||
return
|
||||
}
|
||||
focusGlobalSearch()
|
||||
return
|
||||
}
|
||||
if (['g', 'ㅎ'].includes(normalizedKey) && showTopicViewToggle.value) {
|
||||
event.preventDefault()
|
||||
setTopicViewMode('grid')
|
||||
return
|
||||
}
|
||||
if (['l', 'ㅣ'].includes(normalizedKey) && showTopicViewToggle.value) {
|
||||
event.preventDefault()
|
||||
setTopicViewMode('list')
|
||||
return
|
||||
}
|
||||
if (['a', 'ㅁ'].includes(normalizedKey) && isAdmin.value) {
|
||||
event.preventDefault()
|
||||
router.push('/admin/featured')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,6 +592,23 @@ function closeCollapsedSearch() {
|
||||
isCollapsedSearchOpen.value = false
|
||||
}
|
||||
|
||||
async function focusGlobalSearch() {
|
||||
if (leftRailCollapsed.value && !isMobileLayout.value) {
|
||||
openCollapsedSearch()
|
||||
await nextTick()
|
||||
if (collapsedSearchInputEl.value?.focus) {
|
||||
collapsedSearchInputEl.value.focus()
|
||||
collapsedSearchInputEl.value.select?.()
|
||||
}
|
||||
return
|
||||
}
|
||||
await nextTick()
|
||||
if (searchInputEl.value?.focus) {
|
||||
searchInputEl.value.focus()
|
||||
searchInputEl.value.select?.()
|
||||
}
|
||||
}
|
||||
|
||||
function openGuideModal(stepIndex = 0) {
|
||||
guideStepIndex.value = Math.min(Math.max(Number(stepIndex) || 0, 0), guideSteps.length - 1)
|
||||
isGuideModalOpen.value = true
|
||||
@@ -690,7 +729,7 @@ function reloadApp() {
|
||||
<SvgIcon :src="iconSearch" :size="24" />
|
||||
</span>
|
||||
</button>
|
||||
<input v-model="searchQuery" class="searchStub__input" type="search" :placeholder="leftRailCollapsed ? '' : leftRailSearchPlaceholder" />
|
||||
<input ref="searchInputEl" v-model="searchQuery" class="searchStub__input" type="search" :placeholder="leftRailCollapsed ? '' : leftRailSearchPlaceholder" />
|
||||
</form>
|
||||
|
||||
<nav
|
||||
@@ -771,7 +810,7 @@ function reloadApp() {
|
||||
<span class="collapsedSearchBar__icon">
|
||||
<SvgIcon :src="iconSearch" :size="24" />
|
||||
</span>
|
||||
<input v-model="searchQuery" class="collapsedSearchBar__input" type="search" :placeholder="leftRailSearchPlaceholder" autofocus />
|
||||
<input ref="collapsedSearchInputEl" v-model="searchQuery" class="collapsedSearchBar__input" type="search" :placeholder="leftRailSearchPlaceholder" autofocus />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -830,9 +869,6 @@ function reloadApp() {
|
||||
@click="selectGuideStep(index)"
|
||||
></button>
|
||||
</div>
|
||||
<button class="guideModal__next" type="button" @click="isGuideNextDisabled ? closeGuideModal() : showNextGuideStep()">
|
||||
{{ isGuideNextDisabled ? '닫기' : '다음' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="guideModal__arrow" type="button" aria-label="다음 단계" :disabled="isGuideNextDisabled" @click="showNextGuideStep">›</button>
|
||||
@@ -1876,6 +1912,7 @@ function reloadApp() {
|
||||
.guideModal__text {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.guideModal__stepLabel {
|
||||
@@ -1900,6 +1937,7 @@ function reloadApp() {
|
||||
.guideModal__stepDescription {
|
||||
margin: 0;
|
||||
max-width: 720px;
|
||||
min-height: calc(1.7em * 4);
|
||||
line-height: 1.7;
|
||||
color: var(--theme-text-soft);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user