Step 2 — ProseMirror 통합 + 핵심 API
상태: ✅ 완료 (2026-04-14) 목표: 실제 Markdown 편집 동작 + 외부에서 사용할 최소 public API 확정
과거 로드맵의 Step 2 (API 보강) 는 본 Step에 병합되었다 (steps/step-01-scaffolding.md 설계 변경 이력 참조).
목차
설치된 의존성
prosemirror-state ^1.4.4 — 에디터 state (transaction, selection)
prosemirror-view ^1.41.8 — DOM 렌더링 및 사용자 입력 처리
prosemirror-model ^1.25.4 — 문서 모델 (Node, Mark, Schema)
prosemirror-markdown ^1.13.4 — 기본 스키마 + 파서 + 직렬화기
prosemirror-commands ^1.7.1 — baseKeymap (Enter, Backspace 등)
prosemirror-keymap ^1.2.3 — keymap 플러그인 래퍼모두 dependencies (not peerDependencies) 로 설치 — 사용자가 별도 설치할 필요 없이 npm i @newtil/editor 한 번으로 완결되도록.
결정 사항
1. 스키마는 prosemirror-markdown 기본값 사용
- 커스텀 스키마 작성하지 않음
- 제공 노드:
doc,paragraph,heading(1~6),blockquote,bullet_list,ordered_list,list_item,code_block,horizontal_rule,hard_break,image - 제공 마크:
strong,em,code,link - 이유: MVP에서는 Markdown 표현 범위와 정확히 일치하는 기본 스키마가 적절. 커스텀은 추후 필요 시.
2. 플러그인 구성: 최소한의 편집 가능성만
ts
plugins: [keymap(baseKeymap)]baseKeymap= Enter (문단 분리), Backspace (문단 합치기/노드 삭제), Delete, Mod-Enter (hard break), Shift-Enter (hard break) 등 기본 편집 키- undo/redo, inputRules (
#→ heading 자동 변환), 커스텀 단축키(Mod-B등) 는 Step 3 에서 추가
3. 출력 포맷 2가지 제공
| Getter | 구현 |
|---|---|
editor.markdown | defaultMarkdownSerializer.serialize(doc) |
editor.html | DOMSerializer.fromSchema(schema).serializeFragment(doc.content) → <div> 에 담아 innerHTML 반환 |
value getter/setter 는 markdown 의 별칭.
4. change 이벤트 발행 조건
ts
if (tr.docChanged) {
dispatchEvent(new CustomEvent("change", { detail: { markdown, html } }));
}- Transaction의
docChanged가 true일 때만 발행 - 즉, 커서 이동이나 selection 변화만으로는 이벤트 안 뜸
detail에markdown,html두 포맷 모두 포함 — 사용자가 필요한 쪽만 사용
5. CSS 자동 주입 전략 유지
import "prosemirror-view/style/prosemirror.css"— PM 기본 스타일import "./styles.css"— 우리 커스텀 스타일- tsup의
injectStyle: true로 최종 JS 번들에<style>태그 주입됨 - 사용자는
import "@newtil/editor"한 번으로 스타일까지 적용
6. 속성 ↔ 프로퍼티 동기화 정책
valueattribute 변경 →attributeChangedCallback에서setMarkdown호출 (단, 현재 값과 같으면 skip)editor.markdown = "..."→ 내부적으로 새 PM state 생성- attribute 와 property 간 무한 루프 방지: 같은 값이면 아무것도 안 함
파일별 변경 내역
src/index.ts — 완전 재작성
Step 1의 contentEditable 기반 stub 제거. ProseMirror 기반으로 교체.
핵심 구조:
ts
export class NewtilEditor extends HTMLElement {
private view: EditorView | null = null;
connectedCallback(): void { this.mount(); }
disconnectedCallback(): void { this.view?.destroy(); /* cleanup */ }
get markdown(): string { /* serialize */ }
set markdown(md: string) { /* parse & replace doc */ }
get html(): string { /* DOMSerializer */ }
private mount(): void {
// parse initial value → EditorState → EditorView
// dispatchTransaction 훅에서 change 이벤트 발행
}
}타입 export:
ts
export interface NewtilEditorChangeDetail {
markdown: string;
html: string;
}src/styles.css — 확장
.ProseMirror 셀렉터 하위의 Markdown 블록 요소(p, h1~h6, ul, ol, li, blockquote, code, pre, a, hr, img)에 기본 스타일 추가.
주: Step 4 (CSS 격리 보강) 에서 이 기본 스타일은 더 정교하게 다듬어지며,
@layer로 래핑될 예정.
src/globals.d.ts — 신규
ts
declare module "*.css";TypeScript 가 .css side-effect import 를 인식하도록 하는 ambient 선언.
demo.ts — 재작성
- 실제 Markdown 샘플 (
# Welcome...) 을 속성으로 주입 change이벤트 구독 → 하단#md-display,#html-display에 실시간 표시
index.html — 확장
- 2개 분할 패널 (Markdown / HTML) 레이아웃 추가
- 반응형 (720px 이하에서 1열로)
공개 API
속성 (Attribute)
| 이름 | 타입 | 설명 |
|---|---|---|
value | string | 초기 Markdown 문자열 (attribute로 주입) |
프로퍼티 (Property)
| 이름 | 타입 | 읽기/쓰기 | 설명 |
|---|---|---|---|
markdown | string | R/W | 현재 내용을 Markdown 으로 |
html | string | R | 현재 내용을 HTML 로 (읽기 전용) |
value | string | R/W | markdown 의 별칭 |
이벤트
| 이벤트명 | detail | 발행 시점 |
|---|---|---|
change | { markdown: string, html: string } | Transaction의 docChanged === true 일 때 |
사용 예시
js
import "@newtil/editor";
const editor = document.querySelector("newtil-editor");
// 1) attribute로 초기값 주입
editor.setAttribute("value", "# Hello");
// 2) property로 동적 변경
editor.markdown = "## Updated content";
// 3) 편집 결과 구독
editor.addEventListener("change", (e) => {
console.log(e.detail.markdown);
console.log(e.detail.html);
});검증 결과
| 명령 | 결과 |
|---|---|
npm install prosemirror-* | ✅ 6개 패키지 + 전이 의존성 설치 |
npm run typecheck | ✅ 오류 0건 |
npm run build | ✅ ESM 5.69KB + CJS 5.96KB + DTS (ProseMirror는 external) |
npm run dev (Vite) | ✅ HMR 정상, 의존성 사전 최적화 자동 수행 |
브라우저 동작 확인 항목 (http://localhost:5174):
- 초기 Markdown (
# Welcome...) 이 제목/리스트/인용구 등으로 렌더링됨 - Enter → 새 문단 생성
- Backspace → 빈 문단 제거
- 하단 Markdown 패널에 현재 상태가
\n\n포함된 올바른 Markdown 으로 표시 - 하단 HTML 패널에
<h1>,<p>,<ul>등 올바른 HTML 로 표시 - 외부 HTML(예: 웹페이지 텍스트) 붙여넣기 시 자동 정제 — 스키마 허용 태그만 유지
알려진 제한 / 다음 Step에서 해결할 것
| 제한 | 해결 예정 Step |
|---|---|
# , ** 등 Markdown 단축 입력 시 자동 변환 안 됨 | Step 3 (inputRules) |
Ctrl+B, Ctrl+I 등 단축키 없음 | Step 3 (keymap 확장) |
| undo/redo 없음 | Step 3 (prosemirror-history) |
| 툴바 UI 없음 | Step 3 |
| placeholder 미지원 | Step 3 |
| readonly 미지원 | Step 3 |
| focus/blur 이벤트 미발행 | Step 3 |
| 호스트 페이지 CSS 영향 가능성 | Step 4 (격리 다층 전략) |
@newtil/* 패밀리 테마 없음 | Step 4 |
| React/Vue 래퍼 없음 | Step 5 |
설계 변경 이력
(없음 — Step 2는 기존 설계를 그대로 구현)