Skip to content

구현 로드맵

@newtil/editor 를 단계별로 구현하는 계획입니다. 전체 설계는 design.md 를 참조하세요.

단계별 접근 원칙

전체를 한 번에 만들면 어디서 깨졌는지 추적하기 어렵기 때문에 각 Step마다 실제 브라우저 동작을 검증한 뒤 다음으로 넘어갑니다. 각 Step은 완결된 커밋 단위가 되며, 문제가 생기면 직전 Step으로 안전하게 되돌아갈 수 있습니다.

편집 모델 (중요 결정)

편집은 WYSIWYG만 지원합니다. Markdown은 읽기 전용으로만 노출되며, source/WYSIWYG 모드 토글은 제공하지 않습니다.

항목지원 여부
WYSIWYG 편집 (ProseMirror)
Markdown 읽기 (editor.markdown getter)
HTML 읽기 (editor.html getter)
HTML 붙여넣기 (자동 정제)
Markdown source 직접 편집 (mode="source")❌ 미지원
분할 뷰 (source ↔ wysiwyg 라이브)❌ 미지원

결정 근거:

  • 양방향 편집은 round-trip 정규화 문제가 피할 수 없음 (강조 기호, 리스트 기호, 여백 등이 통일됨)
  • CodeMirror 통합 및 양방향 동기화 로직은 복잡도 대비 사용자 가치 낮음
  • 읽기 전용 Markdown 출력으로 대부분의 실사용 시나리오(저장, 복사, 백엔드 전송)가 커버됨

Step 1 — 스캐폴딩 ✅

상태: 완료 (2026-04-14) 상세 기록: steps/step-01-scaffolding.md

달성한 것

  • [x] 프로젝트 폴더 구조 수립
  • [x] package.json 작성 (@newtil/editor@0.1.0, scoped + public)
  • [x] tsup 라이브러리 빌드 설정 (ESM + CJS + .d.ts, CSS 자동 주입)
  • [x] Vite 데모 개발 서버 설정
  • [x] TypeScript strict 설정
  • [x] 최소 <newtil-editor> Web Component (contentEditable 기반 뼈대)
  • [x] BEM 스타일 훅 네이밍 (.newtil-editor, .newtil-editor__content) 적용
  • [x] Shadow DOM 미사용 확정 (Light DOM + 계층 1 격리)
  • [x] 데모 페이지 (index.html + demo.ts)
  • [x] git init + GitHub 원격 연결 + 첫 푸시

검증 결과

  • npm install — 54 패키지, 정상
  • npm run typecheck — 오류 없음
  • npm run build — ESM(2.3KB) + CJS(2.3KB) + DTS 생성
  • npm run dev — Vite 서버 정상 기동

Step 2 — ProseMirror 통합 + 핵심 API ✅

상태: 완료 (2026-04-14) 상세 기록: steps/step-02-prosemirror.md목표: 실제 Markdown 편집 동작 + 외부에서 사용할 최소 public API 확정

과거 Step 2 (API 보강) 는 이 Step에 병합됨. API는 ProseMirror state를 데이터 원천으로 삼아야 의미가 있으므로 통합이 자연스러움.

달성한 것

2-A. ProseMirror 통합

  • [x] 의존성 추가: prosemirror-state, prosemirror-view, prosemirror-model, prosemirror-markdown, prosemirror-commands, prosemirror-keymap (기본 스키마로 prosemirror-markdown 제공 스키마 채택 — prosemirror-schema-basic 미설치)
  • [x] defaultMarkdownParser 로 초기 value 를 ProseMirror Document 로 변환
  • [x] EditorView.newtil-editor__content 에 마운트
  • [x] 기존 contentEditable 뼈대를 ProseMirror 기반으로 교체
  • [x] baseKeymap 플러그인으로 Enter/Backspace 등 기본 편집 키 활성화

2-B. 핵심 API

  • [x] 속성(attribute): value (초기 Markdown 문자열)
  • [x] 프로퍼티(property): markdown getter/setter, html getter, value 별칭
  • [x] 이벤트: changeCustomEvent<{markdown: string, html: string}> 발행 (Transaction의 docChanged === true 일 때)
  • [x] 출력 직렬화: Markdown은 defaultMarkdownSerializer, HTML은 DOMSerializer 사용
  • [x] 타입 export: NewtilEditorChangeDetail

2-C. 데모 페이지 갱신

  • [x] index.html 2개 분할 패널 (Markdown / HTML) 추가, 반응형 레이아웃
  • [x] demo.ts 에서 실제 Markdown 샘플을 속성으로 주입
  • [x] change 이벤트 구독으로 하단 패널 실시간 갱신 (읽기 전용)

검증 결과

  • npm run typecheck — 오류 없음
  • npm run build — ESM 5.69KB + CJS 5.96KB + DTS (ProseMirror 는 external)
  • npm run dev — Vite HMR 정상, 의존성 사전 최적화 자동 완료
  • 브라우저 — 초기 Markdown 렌더링, Enter/Backspace 동작, 실시간 Markdown/HTML 표시, HTML 붙여넣기 자동 정제 확인

Step 3 — 명령어 입력 시스템 ⬜

상태: 예정 목표: 사용자가 명령어를 외우지 않고도 모든 편집 기능에 접근 가능 + 파워유저 편의 제공

설계 재검토 (2026-04-14): 초기에는 "툴바/단축키/InputRules" 로 묶여 있었으나, 슬래시 명령 메뉴(Notion/Linear 스타일)가 주 UX 여야 한다는 결정에 따라 Step 3를 명령어 입력 시스템 전용 으로 재정의. 툴바는 Step 4로 분리.

3-A. 기반 플러그인 (파워유저 편의) ✅

상세 기록: steps/step-03a-commands-foundation.md

prosemirror-history + prosemirror-inputrules + 확장 keymap. 슬래시 메뉴 없이도 Markdown 문법이나 단축키로 바로 편집 가능한 상태를 만든다.

  • [x] 의존성 추가: prosemirror-history, prosemirror-inputrules, prosemirror-schema-list
  • [x] history() 플러그인 추가 → undo/redo 상태 관리
  • [x] InputRules — Markdown 타이핑 시 자동 블록 변환:
    • [x] # ~ ###### → heading 1~6
    • [x] - / * / + → bullet list
    • [x] 1. → ordered list
    • [x] > → blockquote
    • [x] ``` → code block
    • [x] 보너스: smartQuotes, emDash (--), ellipsis (...)
  • [x] 확장 keymap:
    • [x] Mod-z → undo
    • [x] Mod-Shift-z / Mod-y → redo
    • [x] Mod-b → toggle strong
    • [x] Mod-i → toggle em
    • [x] Mod-` → toggle inline code
    • [x] Enter (리스트 안) → splitListItem
    • [x] Tab / Shift-Tab (리스트 안) → sinkListItem / liftListItem
  • [x] 데모 페이지에 간단한 사용 힌트 추가 (Markdown 타이핑, 주요 단축키)

3-B. 슬래시 명령 메뉴 ⭐ (주 UX) ✅

상세 기록: steps/step-03b-slash-menu.md

"/" 입력 시 부유 메뉴가 뜨고, 타이핑으로 필터링 후 키보드/마우스/터치로 선택 → 현재 블록 변환 또는 새 요소 삽입.

  • [x] 커스텀 ProseMirror 플러그인 slashMenu:
    • [x] 빈 블록 (또는 공백 뒤) 에서 / 감지 (코드 블럭 내부는 제외)
    • [x] 플러그인 상태 유지: open, from, query, selectedIndex
    • [x] 타이핑 시 query 업데이트 및 메뉴 필터 (apply 로직에서 doc 변경 추적)
    • [x] ArrowUp/ArrowDown → 항목 이동 (순환)
    • [x] Enter → 선택 항목 실행 → / + 쿼리 삭제 + 명령 적용
    • [x] Escape / Space / 외부 클릭 / 커서 이동 → 메뉴 닫기
  • [x] 부유 메뉴 UI (.newtil-editor__slash-menu):
    • [x] document.body 에 append + position: fixed (overflow/stack 컨텍스트 격리)
    • [x] EditorView.coordsAtPos 로 위치, 뷰포트 경계 넘으면 좌/상 반전
    • [x] 아이템 아이콘 + 라벨 + 설명 렌더링
    • [x] 키보드 포커스 표시 (aria-selected), 마우스 호버 선택
    • [x] 결과 없을 때 "해당하는 명령이 없습니다" 표시
  • [x] 기본 명령 세트 (12개):
    • [x] Heading 1~3, Text, Bullet list, Numbered list, Quote, Code block, Divider
    • [x] Link (window.prompt 으로 URL + 텍스트 입력)
    • [x] Image (로컬 파일 선택 → Data URL, 취소 시 URL prompt fallback)
    • [x] Video (window.prompt 으로 URL + poster 입력, 스키마 확장으로 video 노드 추가)
    • [x] 한글/영문 키워드로 필터링 (예: /불, /quote, /인용, /동영상)
  • [x] 확장 API 준비: buildSlashMenu(schema, commands?) 에 커스텀 명령 배열 주입 가능, SlashCommand 타입 export
    • 사용자 대상 editor.registerCommand() 래퍼는 Step 4+ 에서 추가 예정

Step 4 — 툴바 + 속성 + 이벤트 ⬜

상태: 예정 목표: 선택 영역 기반 서식 조작 UI + 나머지 Web Component API

할 일

4-A. 선택 영역 floating toolbar

  • [ ] 텍스트 선택 시 선택 영역 위에 뜨는 소형 툴바 (BEM .newtil-editor__floating-toolbar)
  • [ ] 버튼: B (strong), I (em), <> (code), 🔗 (link)
  • [ ] 버튼 활성화 상태 반영 (현재 mark 여부)

4-B. 상단 고정 툴바 (선택)

  • [ ] <newtil-editor toolbar="top"> 속성 시 상단 고정 툴바 표시
  • [ ] Undo/Redo + Heading 드롭다운 + 리스트 버튼

4-C. 속성/이벤트 확장

  • [ ] placeholder 속성 — 빈 에디터일 때 회색 힌트
  • [ ] readonly 속성 — 편집 비활성화
  • [ ] focus, blur, input 이벤트 passthrough

Step 5 — CSS 격리 보강 + @newtil/design-tokens 테마 파일 ⬜

상태: 예정 목표: Light DOM 환경에서도 호스트 페이지 CSS 영향을 최소화하고, @newtil/* 패밀리와 브랜드 일관성 확보

할 일

5-A. CSS 격리 다층 전략 적용 (design.md §9.5.3 참조)

  • [ ] 계층 2 — 에디터 내부 모든 Markdown 블록 요소(h1~h6, p, ul, ol, li, blockquote, code, pre, a, hr, img)에 기본 스타일 명시 재정의
  • [ ] 계층 3 — 전체 에디터 CSS를 @layer newtil-editor.base / @layer newtil-editor.theme 로 래핑
  • [ ] color: inherit, font-family: inherit 을 적극 활용하여 부모 테마와 자연스러운 조화 유지

5-B. 테마 통합

  • [ ] src/themes/newtil.css 작성 — @newtil/design-tokens 의 CSS 변수(--newtil-color-*, --newtil-space-* 등)를 참조하여 에디터 스타일 덮어쓰기
  • [ ] tsup 빌드에 테마 파일 별도 출력 설정 (해시 없는 고정 파일명)
  • [ ] package.json exports 필드에 ./themes/newtil 추가
  • [ ] README 및 demo 페이지에 테마 적용 예시 추가

패키지 구조 반영 (2026-04-14): @newtil/* 가 3-패키지로 분리되면서 토큰은 @newtil/design-tokens 가 단일 출처가 됨. 따라서 테마 파일은 특정 패키지(@newtil/css 또는 @newtil/ui)가 아니라 @newtil/design-tokens 의 변수만 참조한다. 결과적으로 utility(@newtil/css) 사용자, 컴포넌트(@newtil/ui) 사용자, 토큰만 쓰는 사용자 모두 동일한 테마를 받음.

선행 조건

  • @newtil/design-tokens 가 npm에 publish 되어 있을 것 (변수명/계층 구조 확정 후)
  • 변수 명명 규칙(--newtil-color-primary, --newtil-space-4 등)이 안정화될 것

NEWTIL-MIGRATION.md 참조.


Step 6 — React / Vue 래퍼 ⬜

상태: 예정 목표: 프레임워크 사용자 편의성

할 일

  • [ ] src/react/index.tsx — React 래퍼 (<NewtilEditor> 컴포넌트)
    • [ ] ref 전달, 이벤트는 onChange, onInput 형태로 매핑
  • [ ] src/vue/index.ts — Vue 래퍼 (v-model 지원)
  • [ ] tsup 멀티 엔트리 설정
  • [ ] package.json exports./react, ./vue 추가
  • [ ] 각 래퍼의 peerDependencies 정의 (react, vue 를 optional 로)

Step 7 — 첫 npm 배포 ⬜

상태: 예정 목표: @newtil/editor@0.1.0 공개

할 일

  • [ ] npm pack --dry-run 으로 배포 내용 검증
  • [ ] README.md 최종화 (실사용 예시, 스크린샷)
  • [ ] CHANGELOG.md 작성
  • [ ] npm publish --access public
  • [ ] GitHub Release 태그 v0.1.0

선행 조건

  • 최소한 Step 3 (명령어 입력 시스템) 까지는 완료되어야 함
  • Step 4, 5, 6 은 이후 minor 버전(0.2.x) 에서 추가해도 무방

Step 이후 — 추가 기능 로드맵 (참고)

기능우선순위메모
읽기 전용 Markdown 미리보기 패널<newtil-editor preview="markdown"> 속성. 분할 뷰로 현재 Markdown 을 실시간 표시 (읽기 전용). Step 2의 API만으로도 사용자가 직접 구현 가능하므로 우선순위 중
모바일 floating toolbardesign.md §10.3
이미지 업로드 훅사용자 콜백으로 S3/CDN 업로드 연동
YouTube/Vimeo URL 자동 임베드URL 패턴 감지 → iframe 임베드 (동영상 지원의 첫 단계)
@newtil/ui 툴바 통합 옵션기본은 자체 toolbar 유지 (의존성 0). @newtil/ui 사용자가 옵트인하면 툴바를 @newtil/ui 의 버튼/아이콘으로 구성. 별도 빌드 옵션 또는 별도 패키지(@newtil/editor-ui-toolbar?) 검토
협업 편집 (Yjs)ProseMirror 표준 연동 존재
코드블럭 신택스 하이라이팅prosemirror-highlight or highlight.js
표 편집prosemirror-tables
Slash 커맨드 메뉴Notion 스타일
<video> 태그 임베드 노드스키마 확장 + HTML 임베드 허용. XSS 주의