전역 단축키 확장
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# 의사결정 이력
|
||||
|
||||
## 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,11 @@
|
||||
- `featuredTierLists`: 상단 추천 티어표
|
||||
- `tierLists`: 추천 제외 최신 공개 티어표
|
||||
- 홈, 템플릿, 나의 티어표, 즐겨찾기, 팔로우 피드 화면은 공통 `viewToggle`로 `그리드 / 리스트` 보기를 전환하며, 상태는 현재 라우트의 `?view=list` 쿼리로 반영한다.
|
||||
- 전역 단축키
|
||||
- `S/ㄴ`: 검색 포커스. 편집 화면에서는 아이템 검색창, 그 외 화면에서는 왼쪽 공통 검색창
|
||||
- `G/ㅎ`: 그리드 보기
|
||||
- `L/ㅣ`: 리스트 보기
|
||||
- `A/ㅁ`: 관리자 계정일 때 관리자 화면으로 이동
|
||||
- 왼쪽 공통 검색창은 현재 화면 범위만 검색한다.
|
||||
- 홈: 전체 공개 티어표
|
||||
- 템플릿: 공개 템플릿
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# 할 일 및 이슈
|
||||
|
||||
## 단기 확인
|
||||
- `v1.1.16` 이후 `S/ㄴ`이 편집 화면에서는 아이템 검색, 일반 목록 화면에서는 왼쪽 공통 검색창 포커스로 정확히 나뉘어 동작하는지 확인한다.
|
||||
- `v1.1.16` 이후 `G/ㅎ`, `L/ㅣ`가 목록 화면에서만 그리드/리스트 전환을 수행하고, 입력칸을 타이핑 중일 때는 단축키가 발동하지 않는지 확인한다.
|
||||
- 관리자 계정에서만 `A/ㅁ`이 관리자 화면으로 이동하고, 일반 계정에서는 무시되는지 확인한다.
|
||||
- `v1.1.15` 이후 `나의 티어표`에서도 왼쪽 공통 검색창이 정상 동작하고, 검색 결과가 없을 때 전용 빈 상태 문구가 자연스럽게 보이는지 확인한다.
|
||||
- `v1.1.14` 이후 왼쪽 공통 검색창이 홈/템플릿/주제/팔로우 피드/즐겨찾기 각각의 범위만 정확히 검색하는지 확인한다.
|
||||
- `v1.1.14` 이후 주제 허브, 팔로우 피드, 즐겨찾기 화면 상단에서 중복 검색창이 모두 사라졌는지 확인한다.
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# 업데이트 로그
|
||||
|
||||
## 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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user