블록 메뉴와 드래그 이동 안정화

This commit is contained in:
2026-05-07 15:31:57 +09:00
parent 4e5ccb2726
commit 0f60039126
8 changed files with 76 additions and 27 deletions

View File

@@ -1236,6 +1236,46 @@ const updateBlockDropTarget = (event, targetIndex) => {
dragTargetPosition.value = event.clientY < rect.top + rect.height / 2 ? 'before' : 'after'
}
/**
* 블록 드래그 상태 초기화
* @returns {void}
*/
const clearBlockDragState = () => {
draggingBlockId.value = ''
dragTargetIndex.value = -1
dragTargetPosition.value = ''
}
/**
* 현재 드롭 위치 기준으로 블록 이동
* @param {string} draggedId - 이동할 블록 ID
* @param {number} targetIndex - 이동 대상 인덱스
* @param {string} targetPosition - 이동 위치
* @returns {boolean} 이동 여부
*/
const moveDraggedBlock = (draggedId, targetIndex, targetPosition) => {
const sourceIndex = getBlockIndex(draggedId)
const insertionIndex = targetPosition === 'after' ? targetIndex + 1 : targetIndex
if (sourceIndex < 0 || targetIndex < 0) {
return false
}
const nextTargetIndex = sourceIndex < insertionIndex ? insertionIndex - 1 : insertionIndex
if (sourceIndex === nextTargetIndex) {
return false
}
const [draggedBlock] = editorBlocks.value.splice(sourceIndex, 1)
editorBlocks.value.splice(nextTargetIndex, 0, draggedBlock)
selectedBlockId.value = draggedBlock.id
normalizeTrailingTextBlock()
emitContent()
return true
}
/**
* 블록 드롭 이동 처리
* @param {DragEvent} event - 드롭 이벤트
@@ -1245,26 +1285,10 @@ const updateBlockDropTarget = (event, targetIndex) => {
const dropBlock = (event, targetIndex) => {
event.preventDefault()
const draggedId = event.dataTransfer.getData('text/plain') || draggingBlockId.value
const sourceIndex = getBlockIndex(draggedId)
const targetPosition = dragTargetIndex.value === targetIndex ? dragTargetPosition.value : 'after'
const insertionIndex = targetPosition === 'after' ? targetIndex + 1 : targetIndex
if (sourceIndex < 0 || targetIndex < 0) {
draggingBlockId.value = ''
dragTargetIndex.value = -1
dragTargetPosition.value = ''
return
}
const [draggedBlock] = editorBlocks.value.splice(sourceIndex, 1)
const nextTargetIndex = sourceIndex < insertionIndex ? insertionIndex - 1 : insertionIndex
editorBlocks.value.splice(nextTargetIndex, 0, draggedBlock)
draggingBlockId.value = ''
dragTargetIndex.value = -1
dragTargetPosition.value = ''
selectedBlockId.value = draggedBlock.id
normalizeTrailingTextBlock()
emitContent()
moveDraggedBlock(draggedId, targetIndex, targetPosition)
clearBlockDragState()
}
/**
@@ -1272,9 +1296,11 @@ const dropBlock = (event, targetIndex) => {
* @returns {void}
*/
const finishBlockDrag = () => {
draggingBlockId.value = ''
dragTargetIndex.value = -1
dragTargetPosition.value = ''
if (draggingBlockId.value && dragTargetIndex.value >= 0 && dragTargetPosition.value) {
moveDraggedBlock(draggingBlockId.value, dragTargetIndex.value, dragTargetPosition.value)
}
clearBlockDragState()
}
/**
@@ -1350,6 +1376,7 @@ defineExpose({
'admin-block-editor__row--dragging opacity-50': draggingBlockId === block.id,
'admin-block-editor__row--drop-before': dragTargetIndex === index && dragTargetPosition === 'before',
'admin-block-editor__row--drop-after': dragTargetIndex === index && dragTargetPosition === 'after',
'admin-block-editor__row--menu-open z-30': visibleCommands.length && activeBlockId === block.id,
'admin-block-editor__row--text': isTextBlock(block),
'admin-block-editor__row--structure': !isTextBlock(block)
}"

View File

@@ -1,5 +1,15 @@
# 의사결정 이력
## 2026-05-07 v0.0.41
### 명령 메뉴 계층과 개발 도구 표시 결정
관리자 블록 에디터의 `/` 명령 메뉴가 열린 행은 다른 블록 행보다 위 stacking 순서로 올린다. 메뉴가 절대 위치로 열릴 때 아래 블록의 텍스트가 같은 레이어에 남아 있으면 메뉴 배경 위로 겹쳐 보일 수 있기 때문이다.
블록 이동은 `drop` 이벤트뿐 아니라 `dragend`에서도 현재 삽입선 위치를 기준으로 확정한다. 브라우저와 입력 요소 조합에 따라 contenteditable 주변에서 `drop` 이벤트가 안정적으로 들어오지 않을 수 있으므로, 사용자가 본 삽입선과 실제 결과가 어긋나지 않게 하기 위해서다.
개발 서버의 Nuxt DevTools는 현재 관리자 글쓰기 전체 화면 QA를 방해하므로 기본 비활성화한다. 하단 검은 도킹 패널은 애플리케이션 UI가 아니라 개발 도구 영역이지만, 편집 화면 높이와 스크롤 문제를 확인할 때 혼동을 만들 수 있기 때문이다.
## 2026-05-07 v0.0.40
### 글쓰기 스크롤과 드래그 드롭 피드백 결정

View File

@@ -156,7 +156,7 @@
| 파일 | 기능 |
|------|------|
| package.json | Nuxt 실행 스크립트와 의존성 |
| nuxt.config.js | Nuxt 앱 설정 |
| nuxt.config.js | Nuxt 앱 설정, Tailwind 모듈 연결, 관리자 QA를 위한 개발 도구 비활성화 |
| tailwind.config.js | Tailwind 테마 설정 |
| assets/css/main.css | 전역 스타일 |
| composables/useMenuState.js | 좌측 메뉴 열림 상태 관리 |

View File

@@ -277,6 +277,7 @@ components/content/
- 저장 데이터는 기존 `content` 필드의 마크다운 문자열을 유지한다.
- `/` 입력 시 블록 선택 메뉴를 표시한다.
- `/` 명령 메뉴는 화면 하단 공간이 부족하면 현재 블록 위쪽으로 표시한다.
- `/` 명령 메뉴가 열린 블록 행은 아래 블록보다 위 stacking 순서로 표시해 메뉴와 본문 텍스트가 겹쳐 보이지 않게 한다.
- `/` 명령 메뉴가 열린 상태에서 Enter를 누르면 현재 강조된 메뉴 항목을 적용한다.
- `/` 명령 메뉴가 열린 상태에서 위/아래 방향키로 강조 항목을 이동한다.
- `/` 명령 메뉴 필터는 한글 조합 입력 완료와 방향키/Enter 입력 직전에 현재 DOM 텍스트를 기준으로 동기화한다.
@@ -292,7 +293,7 @@ components/content/
- 블록 왼쪽 핸들은 hover/focus 상태에서 AFFiNE 참고 스타일의 세로 막대로 표시되며, hover 시 해당 블록 높이만큼 확장해 선택 범위를 드러낸다.
- 블록 왼쪽 핸들을 클릭하면 블록을 선택하고 Delete 또는 Backspace로 해당 블록을 삭제할 수 있다.
- 블록 왼쪽 핸들을 드래그하면 블록 순서를 이동할 수 있다.
- 블록 드래그 중에는 현재 포인터 위치 기준으로 대상 블록 위 또는 아래에 삽입선을 표시하고, 드롭 시 표시 위치와 같은 곳으로 이동한다.
- 블록 드래그 중에는 현재 포인터 위치 기준으로 대상 블록 위 또는 아래에 삽입선을 표시하고, 드롭 또는 드래그 종료 시 표시 위치와 같은 곳으로 이동한다.
- 빈 블록 placeholder는 현재 활성 블록 또는 첫 빈 블록에만 표시한다.
- 에디터 마지막에는 클릭 가능한 빈 문단 블록을 항상 유지하며, 해당 블록이 비어 있으면 저장 콘텐츠에는 포함하지 않는다.
- 제목은 별도 라벨 영역이 아니라 에디터 상단의 큰 제목 입력으로 표시한다.
@@ -451,6 +452,6 @@ APP_PORT=43118
## 버전 관리
- 현재 버전: v0.0.40
- 현재 버전: v0.0.41
- 첫 커밋 이후 변경사항을 커밋할 때마다 패치 버전 증가
- 메이저/마이너 버전은 구조 변경 또는 기능 묶음 단위로 결정

View File

@@ -1,5 +1,13 @@
# 업데이트 이력
## v0.0.41
- 관리자 블록 에디터 `/` 명령 메뉴가 아래 블록 텍스트와 겹쳐 보이던 문제 수정.
- 관리자 블록 에디터 드래그 종료 시 삽입선 위치 기준으로 블록 이동이 확정되도록 보정.
- 개발 서버에서 Nuxt DevTools 도킹 패널로 보이는 하단 검은 영역을 막기 위해 DevTools 비활성화.
- 기술 명세 현재 버전을 v0.0.41로 갱신.
- 패키지 버전을 0.0.41로 갱신.
## v0.0.40
- 관리자 글쓰기 화면에서 바깥 문서가 함께 스크롤되어 하단 배경이 노출되던 문제 수정.

View File

@@ -1,6 +1,9 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2026-04-29',
devtools: {
enabled: false
},
modules: ['@nuxtjs/tailwindcss'],
components: [
{

4
package-lock.json generated
View File

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

View File

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