# Milkdown Block Handle 고도화 계획

> Canonical: https://oaty.lol/posts/milkdown-block-handle-%EA%B3%A0%EB%8F%84%ED%99%94-%EA%B3%84%ED%9A%8D
> Markdown alternate: https://oaty.lol/posts/milkdown-block-handle-%EA%B3%A0%EB%8F%84%ED%99%94-%EA%B3%84%ED%9A%8D.md
> Published: 2026-04-27T09:49:36.823Z
> Updated: 2026-04-27T11:27:03.095Z
> Category: Web

Crepe의 기본 BlockEdit를 유지하고, 공식 확장 지점인 blockHandle, buildMenu, 그룹 설정만 확장한다.

## 요약

* Crepe의 기본 BlockEdit를 유지하고, 공식 확장 지점인 blockHandle, buildMenu, 그룹 설정만 확장한다.
* 새 의존성 없이 @milkdown/kit와 표준 DOM CustomEvent로 구현한다.
* 근거: Milkdown 공식 [Block Plugin 예제](https://github.com/Milkdown/website/blob/main/docs/plugin/example-block-plugin.md), [@milkdown/kit 가이드](https://github.com/Milkdown/website/blob/main/docs/guide/using-milkdown-kit.md), [Crepe BlockEdit 소스](https://github.com/Milkdown/milkdown/blob/main/packages/crepe/src/feature/block-edit/index.ts).

## Key Changes

* src/utils/create-crepe.ts

  * BlockEdit 설정을 CMS용 config 함수로 분리한다.

  * blockHandle.getOffset/getPlacement를 반응형으로 조정하되 Crepe 기본 tall-block 정렬 로직은 유지한다.

  * 메뉴 라벨을 한국어화하고 H1/H4-H6/수식은 계속 비활성화한다.

  * buildMenu로 CMS 그룹을 추가하고 미디어 라이브러리 항목을 만든다.

  * 메뉴 실행 시 editorViewCtx의 selection bookmark를 담아 cms:open-media CustomEvent를 dispatch한다.

* src/utils/editor-init.ts

  * cms:open-media 이벤트를 AbortController로 등록/정리한다.

  * openMediaModal(mode, selectionBookmark?) 헬퍼를 추가해 썸네일 모드와 본문 삽입 모드를 분리한다.

  * 본문 삽입은 parserCtx로 Markdown 조각을 ProseMirror slice로 파싱해 selection bookmark 위치에 삽입한다.

  * 파싱/selection 복원 실패 시 기존 동작처럼 현재 Markdown 끝에 append하고 Crepe를 재생성한다.

  * 모달 취소, editor 재생성, 저장 불가 상태에서는 pending bookmark를 반드시 제거한다.

* src/pages/admin/editor.astro

  * .milkdown-block-handle, .milkdown-slash-menu 스타일을 admin token 기반으로 정돈한다.

  * 데스크톱 hover 환경에서는 핸들/plus 버튼이 문서 여백과 겹치지 않게 하고, hover: none 또는 좁은 화면에서는 핸들 노출을 억제한다.

  * prefers-reduced-motion에서 transition을 제거한다.

## Edge Cases

* bodyLoadError 또는 readonly 상태에서는 이벤트와 삽입 로직이 no-op 처리된다.

* 모달이 여러 번 열리면 가장 최근 bookmark만 사용하고 이전 값은 폐기한다.

* 미디어 URL이 비어 있거나 선택이 없으면 삽입하지 않는다.

* 이미지 삽입은 raw text를 ProseMirror에 넣지 않고 Milkdown parser를 거쳐 Markdown 의미를 유지한다.

* 기존 썸네일 적용, URL 복사, AI 재작성, dirty tracking, SEO 분석 흐름은 유지한다.

## Test Plan

* pnpm build로 Astro/Vite 번들 검증.

* 수동 확인: /admin/editor에서 문단, H2/H3, 목록, 코드, 이미지, 표 근처 handle 위치와 drag reorder 확인.

* plus 버튼과 / slash 메뉴 모두에서 한국어 메뉴, 키보드 이동, Enter 실행 확인.

* 미디어 라이브러리 메뉴로 모달을 열고 선택 위치에 이미지가 들어가는지 확인.

* 썸네일 모드에서 기존 썸네일 적용 동작이 변하지 않는지 확인.

* 모바일 폭과 coarse pointer 환경에서 handle이 UI를 가리지 않는지 확인.

* 참고: 현재 astro check는 @astrojs/check 미설치로 바로 실행할 수 없으므로, 의존성 추가 요청이 있을 때만 별도 검증에 포함한다.

## Assumptions

* v1에서는 Crepe 기본 BlockHandle 컴포넌트를 교체하지 않는다.

* 삭제/복제/AI 블록 재작성 같은 handle 자체 액션은 이번 범위에서 제외한다.

* 새 패키지는 추가하지 않고 현재 @milkdown/crepe\@7.20.0, @milkdown/kit\@7.20.0 기준으로 구현한다.
