관리자 블록 에디터 키보드 흐름 보정

This commit is contained in:
2026-05-01 23:04:50 +09:00
parent c2b3e3a204
commit 3afef9d0d2
7 changed files with 73 additions and 10 deletions

View File

@@ -13,6 +13,7 @@ const blockRefs = ref([])
const activeBlockId = ref('')
const slashQuery = ref('')
const slashMenuDirection = ref('down')
const highlightedCommandIndex = ref(0)
const isApplyingExternalValue = ref(false)
let blockIdSeed = 0
@@ -368,6 +369,8 @@ const updateSlashQuery = (block) => {
slashQuery.value = block.text.startsWith('/')
? block.text.slice(1).trim().toLowerCase()
: ''
highlightedCommandIndex.value = 0
}
const activeBlockIndex = computed(() => editorBlocks.value.findIndex((block) => block.id === activeBlockId.value))
@@ -390,6 +393,8 @@ const visibleCommands = computed(() => {
].some((keyword) => keyword.toLowerCase().includes(slashQuery.value)))
})
const highlightedCommand = computed(() => visibleCommands.value[highlightedCommandIndex.value])
/**
* 슬래시 메뉴 명령 적용
* @param {Object} command - 블록 명령
@@ -425,6 +430,36 @@ const applyCommand = (command) => {
focusBlock(index)
}
/**
* 슬래시 메뉴 선택을 아래로 이동
* @param {KeyboardEvent} event - 키보드 이벤트
* @returns {void}
*/
const highlightNextCommand = (event) => {
if (!visibleCommands.value.length) {
return
}
event.preventDefault()
highlightedCommandIndex.value = (highlightedCommandIndex.value + 1) % visibleCommands.value.length
}
/**
* 슬래시 메뉴 선택을 위로 이동
* @param {KeyboardEvent} event - 키보드 이벤트
* @returns {void}
*/
const highlightPreviousCommand = (event) => {
if (!visibleCommands.value.length) {
return
}
event.preventDefault()
highlightedCommandIndex.value = highlightedCommandIndex.value === 0
? visibleCommands.value.length - 1
: highlightedCommandIndex.value - 1
}
/**
* 엔터 키로 다음 블록 생성
* @param {KeyboardEvent} event - 키보드 이벤트
@@ -434,16 +469,18 @@ const applyCommand = (command) => {
const handleEnter = (event, index) => {
const currentBlock = editorBlocks.value[index]
if (visibleCommands.value.length && currentBlock.text.startsWith('/')) {
event.preventDefault()
applyCommand(highlightedCommand.value || visibleCommands.value[0])
return
}
if (currentBlock.type === 'code' && !event.shiftKey) {
return
}
event.preventDefault()
if (!currentBlock.text.trim() && currentBlock.type === 'paragraph') {
return
}
if (currentBlock.type === 'divider') {
editorBlocks.value.splice(index + 1, 0, createEditorBlock())
emitContent()
@@ -550,6 +587,8 @@ watch(editorBlocks, () => {
@focus="activateBlock(block)"
@input="updateBlockText($event, index)"
@keydown.enter="handleEnter($event, index)"
@keydown.down="highlightNextCommand"
@keydown.up="highlightPreviousCommand"
@keydown.backspace="handleBackspace($event, index)"
/>
@@ -569,9 +608,10 @@ watch(editorBlocks, () => {
:class="slashMenuDirection === 'up' ? 'bottom-full mb-2' : 'top-full mt-2'"
>
<button
v-for="command in visibleCommands"
v-for="(command, commandIndex) in visibleCommands"
:key="`${command.type}-${command.level || 'default'}`"
class="admin-block-editor__slash-item grid w-full gap-0.5 px-4 py-3 text-left hover:bg-surface"
:class="commandIndex === highlightedCommandIndex ? 'bg-surface' : ''"
type="button"
@mousedown.prevent="applyCommand(command)"
>

View File

@@ -1,5 +1,13 @@
# 의사결정 이력
## 2026-05-01 v0.0.11
### 블록 에디터 키보드 흐름 보정
빈 문단에서 Enter를 누를 때도 다음 빈 문단 블록을 생성하도록 유지한다. 작성 중 의도적으로 여백을 두거나 다음 입력 위치로 이동하는 행동이 자연스러운 글쓰기 흐름이기 때문이다. 저장 시에는 기존처럼 비어 있는 블록을 마크다운 문자열에 포함하지 않는다.
슬래시 메뉴는 입력 포커스를 본문 블록에 둔 채 키보드로 선택한다. `/제목 3`처럼 필터링한 뒤 Enter를 누르면 현재 강조된 항목을 적용하고, 위/아래 방향키로 강조 항목을 이동한다. 이렇게 하면 메뉴 항목으로 실제 DOM 포커스를 옮기지 않아도 Ghost류 에디터처럼 연속 입력 흐름을 유지할 수 있다.
## 2026-05-01 v0.0.10
### 블록 에디터 입력 안정화 결정

View File

@@ -205,8 +205,11 @@ components/content/
- 저장 데이터는 기존 `content` 필드의 마크다운 문자열을 유지한다.
- `/` 입력 시 블록 선택 메뉴를 표시한다.
- `/` 명령 메뉴는 화면 하단 공간이 부족하면 현재 블록 위쪽으로 표시한다.
- `/` 명령 메뉴가 열린 상태에서 Enter를 누르면 현재 강조된 메뉴 항목을 적용한다.
- `/` 명령 메뉴가 열린 상태에서 위/아래 방향키로 강조 항목을 이동한다.
- 블록 메뉴는 문단, 제목 2, 제목 3, 인용, 목록, 코드, 구분선을 제공한다.
- `#`, `##`, `###`, `>`, `-` 입력 후 공백을 누르면 현재 블록 타입을 즉시 변환한다.
- 빈 문단에서 Enter를 누르면 다음 빈 문단 블록을 생성한다.
- 빈 블록 placeholder는 현재 활성 블록 또는 첫 빈 블록에만 표시한다.
- 제목은 별도 라벨 영역이 아니라 에디터 상단의 큰 제목 입력으로 표시한다.
- 관리자 작성 화면과 공개 본문은 같은 마크다운 렌더링 기준을 사용한다.
@@ -288,6 +291,6 @@ APP_PORT=43118
## 버전 관리
- 현재 버전: v0.0.10
- 현재 버전: v0.0.11
- 첫 커밋 이후 변경사항을 커밋할 때마다 패치 버전 증가
- 메이저/마이너 버전은 구조 변경 또는 기능 묶음 단위로 결정

View File

@@ -2,7 +2,11 @@
## 1차 관리자 개발
- [ ] 이미지 업로드
- [ ] 블록 에디터 브라우저 수동 QA: 빈 줄 Enter, `/` 메뉴 필터, 방향키, Enter 선택, 한글 조합 입력 확인
- [ ] 블록 에디터 저장/수정 왕복 QA: 기존 글 수정 시 블록 파싱, 저장 후 다시 열기 확인
- [ ] 이미지 업로드 블록 구현
- [ ] 콜아웃, 토글, 임베드 블록 추가
- [ ] 글 작성 중 자동 저장
## 2차 관리자 개발

View File

@@ -1,5 +1,13 @@
# 업데이트 이력
## v0.0.11
- 관리자 블록 에디터에서 빈 문단 Enter 입력 시 새 빈 블록이 생성되도록 수정.
- 관리자 블록 에디터의 `/` 명령 메뉴에서 Enter로 선택 항목을 적용하도록 수정.
- 관리자 블록 에디터의 `/` 명령 메뉴에 위/아래 방향키 선택 이동 추가.
- 관리자 글 에디터 후속 작업 순서 정리.
- 패키지 버전을 0.0.11로 갱신.
## v0.0.10
- 관리자 블록 에디터의 `contenteditable` 입력 중복 문제 수정.

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "sori.studio",
"version": "0.0.10",
"version": "0.0.11",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sori.studio",
"version": "0.0.10",
"version": "0.0.11",
"hasInstallScript": true,
"dependencies": {
"@nuxtjs/tailwindcss": "^6.14.0",

View File

@@ -1,6 +1,6 @@
{
"name": "sori.studio",
"version": "0.0.10",
"version": "0.0.11",
"private": true,
"type": "module",
"scripts": {