API 레퍼런스
<newtil-editor> 웹 컴포넌트의 전체 API. 정본은 src/index.ts 이다.
속성 (Attributes)
HTML 속성으로 설정한다. setAttribute / removeAttribute 로 동적으로 바꿀 수 있다.
| 속성 | 값 | 기본 | 설명 |
|---|---|---|---|
value | 마크다운 문자열 | "" | 초기 내용. 바꾸면 내용이 교체된다. 편집기가 마지막에 낸 markdown/html 과 같은 값이면 무시한다(순환 방지) |
placeholder | 문자열 | — | 빈 편집기의 안내 문구. 마크다운 모드의 textarea 에도 같이 붙는다 |
readonly | (존재 여부) | — | 있으면 읽기 전용. 마크다운 모드의 textarea 도 읽기 전용 |
toolbar | (존재 여부) | — | 있으면 상단 툴바. 붙이고 떼면 즉시 반영 |
floating-toolbar | "false" | (켜짐) | "false" 면 글자를 골랐을 때 뜨는 플로팅 툴바를 숨긴다 |
mode | "wysiwyg" | "source" | wysiwyg | 마크다운 원문 편집 모드로 시작. 프로퍼티 mode 로 바꾸면 속성도 따라간다 (0.6.0) |
lang | ko | en (ko-KR 같은 태그도 됨) | 조상 lang → <html lang> → en | 툴팁·메뉴·안내 문자열 언어. 바꾸면 내용과 모드를 보존한 채 다시 마운트한다(되돌리기 이력 초기화) (0.10.2) |
empty-line-hint | 문자열 | messages.emptyLineHint | 빈 문단에서 Enter 를 눌렀을 때의 안내. 빈 문자열이면 끈다 (0.10.0) |
readonly·toolbar 는 값이 아니라 존재 여부로 판단한다. <newtil-editor toolbar> 가 켜기, 속성 제거가 끄기.
프로퍼티 (Properties)
| 프로퍼티 | 타입 | R/W | 설명 |
|---|---|---|---|
markdown | string | R/W | 현재 내용의 마크다운. 마크다운 모드에서는 textarea 원문 그대로 |
value | string | R/W | markdown 의 별칭 |
html | string | R | 현재 내용의 HTML. 마크다운 모드에서는 원문을 파싱해 만든다 |
mode | EditorMode | R/W | "wysiwyg" | "source". 바꾸는 순간에만 한 번 변환한다. 되돌리기 이력은 초기화 |
messages | Messages | R/W | 읽으면 지금 쓰는 문자열 표(언어 + 덮어쓴 것). 쓰면 일부만 덮어쓴다 — editor.messages = { emptyLineHint: "…" }. 툴바·메뉴 문구는 다시 마운트해 반영 (0.10.2) |
onImageUpload | ImageUploadHandler | R/W | 이미지 업로드 훅. 파일 선택·붙여넣기·끌어놓기가 모두 이 훅을 탄다 (0.10.8) |
onDrawingSave | DrawingSaveHandler | R/W | 그림판 저장 훅 — 구운 PNG 와 도형 데이터를 받아 문서에 넣을 주소를 준다. 없으면 data URL (0.10.7) |
onDrawingLoad | DrawingLoadHandler | R/W | 그림판 불러오기 훅 — 이미지 주소의 도형 데이터(있으면). 없으면 그 그림을 배경으로 새로 그린다 |
selectionActions | SelectionAction[] | R/W | 선택 범위 동작 — 호스트가 정한 동작을 말풍선 툴바·슬래시 메뉴에 붙인다. 비어 있으면(기본) 버튼도 없다 (0.10.17) |
onImageUpload
editor.onImageUpload = async (file) => {
const form = new FormData(); form.append("file", file);
const res = await fetch("/api/upload", { method: "POST", body: form });
return (await res.json()).url;
};- 훅 있음 → 업로드 후 반환 URL 로 이미지 삽입. 훅이 던지면 data URL 로 대체
- 훅 없음 → data URL(base64) 로 본문에 들어간다
- 파일 선택을 취소하면 URL 을 묻는다(
messages.imageUrlPrompt,altPrompt)
onDrawingSave / onDrawingLoad
editor.onDrawingSave = async ({ png, data, previousSrc }) => {
// png: Blob, data: DrawingData(도형 JSON), previousSrc: 고치던 그림의 주소 | null
return "/upload/drawing-1.png"; // 문서에 넣을 주소
};
editor.onDrawingLoad = async (src) => await fetchDrawingJson(src); // DrawingData | null자세한 데이터 흐름은 그림판 연동.
selectionActions
editor.selectionActions = [{
id: "translate", label: "영어로 번역",
prompt: { presets: ["격식체", "구어체"] }, // 없으면 누르는 즉시 실행
run: async ({ markdown, document, before, after, input }) => "…대상을 대신할 마크다운…", // null 이면 취소, 던지면 이유를 보인다
}];대상은 선택을 최상위 블록 경계로 넓힌 범위. 결과는 한 번의 편집으로 들어간다. 자세한 것은 선택 범위 동작.
이벤트
| 이벤트 | detail | 언제 |
|---|---|---|
change | { markdown, html } | 문서가 실제로 바뀔 때(tr.docChanged). 커서 이동은 아님. 마크다운 모드에서는 매 입력마다, 디자인 모드로 돌아올 때 한 번 |
modechange | { mode } | 디자인 ↔ 마크다운 모드 전환 (0.6.0) |
focus · blur | — | 편집 영역의 focus/blur 를 호스트에서 다시 발행(원래는 버블링되지 않는다) |
input | — | 편집 영역의 input 을 재발행(bubbles: true) |
editor.addEventListener("change", (e) => {
save(e.detail.markdown);
preview.innerHTML = e.detail.html;
});붙여넣기 · 끌어놓기
- 마크다운 글 (0.10.5): 붙여 넣은 글에 줄 머리 표식(
#·>·-·1.·```·|…|)이나 인라인 표식(**굵게**·[링크](url)·`코드`)이 있으면 마크다운으로 해석한다. Shift 를 누른 붙여넣기(plain)는 글자 그대로. 마크다운 같지 않은 글은 줄마다 문단 - 이미지 파일 (0.10.8): 붙여넣기·끌어놓기의
image/*파일은onImageUpload를 탄다. 없으면 data URL. 끌어놓은 자리에 들어간다
Enter · 빈 문단 정책
마크다운에는 빈 문단이 없으므로 편집기도 만들지 않는다 (0.6.2).
- 빈 문단에서 Enter → 아무 일도 없고 안내 말풍선(
messages.emptyLineHint)이 약 2초 뜬다. 인용구 안이면 인용구 탈출, 빈 목록 항목이면 목록 탈출 - 글자 줄 맨 앞에서 Enter → 위에 빈 문단을 열고 커서를 올린다(코드 블록 뒤에 글을 끼우는 길). 안 쓰고 떠나면 지워진다 (0.10.6)
- 커서가 없는 빈 문단은 어떻게 생겼든(한글 IME 조합 직후 브라우저가 만든 것 포함) 지운다. 목록 항목·표 셀 안은 예외
- 문서 끝에는 항상 빈 문단 하나가 있어 표·코드·영상 아래로 나갈 수 있다. 저장되지는 않는다
저장 형식
markdown 은 표준 마크다운(CommonMark + GFM 표)이다. 마크다운이 표현하지 못하는 것만 CommonMark 가 허용하는 원문 HTML 로 남긴다.
| 내용 | 저장 |
|---|---|
| 제목·문단·강조·링크·목록·인용·코드·표 | 마크다운. 표는 열 정렬(:---:) 포함 |
| 이미지 | . 폭·정렬·테두리를 바꾼 것만 <img src alt title width="480" align="center" data-border="thin"> |
| 문단 상자(정렬·테두리) | <div align="center" data-border="thin"> … </div> — 안쪽은 마크다운 그대로. 둘 다 없으면 상자를 걷는다 (0.10.9) |
| MP4 영상 | <video src controls> (폭을 바꾸면 width) |
| 영상 임베드 | <iframe src> (폭을 바꾸면 width + 16:9 height) |
| 오디오·코드 임베드 | <iframe src> (폭을 바꾸면 width) |
| 빈 문단 | 없음 |
| 목록 | 촘촘(tight). 항목 안에 문단이 둘 이상일 때만 느슨 |
편집 영역에는 .n-prose 클래스가 붙어 있다. 보기 화면도 .n-prose(@newtil/components)로 그리면 편집과 읽기가 같은 모습이 된다. 정렬·테두리는 같은 속성 이름으로 CSS 를 두면 된다(.n-prose div[align], [data-border], img[align]).
문자열 표 (Messages)
lang 이 정한 표(ko/en) 위에 messages 로 일부만 덮어쓴다. 내장 표는 messagesKo·messagesEn 으로 가져올 수 있다. 키는 src/i18n.ts 가 정본이며 다음과 같다.
| 영역 | 키 |
|---|---|
| 툴바 | undo redo blockType paragraph heading(n) codeBlock bold italic underline inlineCode link linkPrompt linkTextPrompt textColor colorNone removeColor colorRed colorOrange colorYellow colorGreen colorBlue colorPurple colorPink colorGray bulletList orderedList quote codeBlockButton insertTable image video |
| 표 편집 | rowAdd colAdd rowDel colDel headerRow tableDel rowAddTitle colAddTitle rowDelTitle colDelTitle headerRowTitle tableDelTitle |
| 모드 | modeMarkdown modeDesign toMarkdownTitle toDesignTitle |
| 코드 블록 | toCodeBlock clickToSelect langPlaceholder langAria |
| 손잡이·원문 | dragHandle resizeHandle resetSize resetSizeTitle sourceAria |
| 안내 | emptyLineHint |
| 슬래시 메뉴 | slashHeadingLabel(n) slashHeadingDesc(n) slashTextLabel/Desc slashBulletLabel/Desc slashOrderedLabel/Desc slashQuoteLabel/Desc slashCodeLabel/Desc slashTableLabel/Desc slashDividerLabel/Desc slashLinkLabel/Desc slashImageLabel/Desc slashEmbedLabel/Desc slashAudioLabel/Desc slashVideoLabel/Desc slashDrawingLabel/Desc noCommand(query) |
| 정렬·테두리 | alignLeft alignCenter alignRight border borderNone borderThin borderThick borderDashed borderRound borderShadow |
| 그림판 접착부 | drawing drawingEdit drawingAlt drawingNoDataNotice (그림판 자체 문자열은 @newtil/drawing 이 가진다) |
| 선택 범위 동작 입력칸 | actionRun actionCancel actionWorking actionFailed actionInputPlaceholder (동작의 이름·설명은 호스트가 준다) |
| 프롬프트 | imageUrlPrompt altPrompt embedPrompt embedInvalid audioPrompt audioInvalid videoPrompt |
editor.messages = { emptyLineHint: "빈 줄은 저장되지 않아요.", bold: "굵게" };CSS 변수
스타일은 Shadow DOM 안에 자동 주입되고, 색은 CSS 변수로 넘긴다(변수는 Shadow 경계를 넘어 상속된다). 호스트 요소에 주면 된다.
호스트 전용 변수
| 변수 | 기본 | 대상 |
|---|---|---|
--newtil-code-bg | --color-code-bg → #1e293b | 코드 블록 배경 (두 테마 모두 어두운 상자) |
--newtil-code-fg | --color-code-fg → #e2e8f0 | 코드 블록 글자 |
--newtil-code-border | --color-code-border → transparent | 코드 블록 테두리 |
테마가 읽는 design-tokens 변수 (themes/newtil.css)
| 변수 | 쓰는 곳 |
|---|---|
--color-text | 본문 글자, 툴바 셀렉트·버튼 hover 글자, 코드 언어 입력칸 |
--color-text-muted | 인용구 글자, 플레이스홀더, 툴바 버튼·드래그 핸들 기본색 |
--color-surface | 편집기 배경, 플로팅 툴바·팔레트 배경, 셀렉트 배경 |
--color-surface-2 | 인라인 코드 배경, 상단 툴바 배경, 표 머리칸, 플로팅 버튼 hover |
--color-surface-3 | 상단 툴바 버튼 hover, 드래그 핸들 active |
--color-border | 편집기·툴바·인용구·수평선·표·팔레트 테두리 |
--color-primary | 링크, 포커스 아웃라인, 눌린 버튼 글자, 폭 손잡이, 드롭 표시선 |
--color-primary-subtle | 눌린 버튼 배경 |
--color-code-bg / --color-code-fg / --color-code-border | 코드 블록 (위 --newtil-code-* 의 기본값) |
--radius-2 / --radius-3 | 인라인 코드 / 편집기 모서리 |
--font-size-body, --font-size-heading-lg/md/sm | 본문, h1/h2/h3 |
newtil-editor {
min-height: 420px;
--color-primary: #16a34a;
--color-primary-subtle: #dcfce7;
--newtil-code-bg: #0f172a;
}다크 모드: <html data-theme="dark"> 를 호스트의 data-theme 로 동기화해 따라간다(MutationObserver). 없으면 prefers-color-scheme.
타입 (TypeScript)
import type {
NewtilEditorChangeDetail, ImageUploadHandler, EditorMode, SlashCommand,
Lang, Messages,
DrawingData, DrawingShape, DrawingSaveInput, DrawingSaveHandler, DrawingLoadHandler,
SelectionAction, SelectionActionContext, SelectionActionPrompt,
} from "@newtil/editor";
import { NewtilEditor, messagesKo, messagesEn } from "@newtil/editor";| 타입 | 정의 |
|---|---|
NewtilEditorChangeDetail | { markdown: string; html: string } |
ImageUploadHandler | (file: File) => Promise<string> |
EditorMode | "wysiwyg" | "source" |
SlashCommand | { id, label, description?, icon?, keywords?, run(view) } |
Lang | "ko" | "en" |
Messages | 문자열 표 (위 표의 키) |
DrawingData | @newtil/drawing 의 Drawing — { version: 1, width, height, background, shapes } |
DrawingShape | @newtil/drawing 의 Shape |
DrawingSaveInput | { png: Blob; data: DrawingData; previousSrc: string | null } |
DrawingSaveHandler | (input: DrawingSaveInput) => Promise<string> |
DrawingLoadHandler | (src: string) => Promise<DrawingData | null> |
SelectionAction | { id, label, description?, icon?, prompt?, run(ctx) } — 선택 범위 동작 |
SelectionActionContext | { markdown, document, before, after, input } |
SelectionActionPrompt | { placeholder?, presets? } |
NewtilEditor 클래스도 export 되며 HTMLElementTagNameMap["newtil-editor"] 가 선언돼 있어 document.querySelector("newtil-editor") 가 타입을 안다.
패키지 엔트리
| 경로 | 내용 |
|---|---|
@newtil/editor | 웹 컴포넌트 등록(customElements.define("newtil-editor"), 이미 있으면 건너뜀) + 타입 |
@newtil/editor/react | React 래퍼 — React |
@newtil/editor/vue | Vue 3 래퍼 — Vue 3 |
@newtil/editor/themes/newtil | 테마 CSS 원본. Shadow DOM 에 이미 묶여 있어 따로 import 할 필요 없다(호환용) |