설치
아래 스니펫을 페이지에 붙이면 끝입니다. 별도 의존성 없이 82KB 한 파일로 동작합니다.
<div id="typdit-editor"></div>
<script src="https://typdit.com/embed/v1.js"></script>
<script>
const editor = Typdit.create(document.getElementById('typdit-editor'), {
placeholder: 'Write here',
licenseKey: 'YOUR_KEY',
onChange: (snapshot) => console.log(snapshot),
});
</script>에디터 지면에서 바로 가져가기
설치 코드를 손으로 쓰지 않아도 됩니다. 에디터 지면에서 모드와 테마를 원하는 대로 맞춰 놓고 아래로 내리면, 그 설정이 그대로 적힌 설치 코드가 있습니다. 기본값과 같은 옵션은 적히지 않으므로 무엇이 특별한 설정인지 한눈에 보입니다.
문서까지 복사를 누르면 지금 화면에 쓴 내용이 snapshot 옵션으로 함께 들어갑니다. 템플릿을 열어 뼈대를 확인한 다음 그 뼈대째로 가져가고 싶을 때 쓰면 됩니다.
옵션 전체
Typdit.create(element, options) 의 두 번째 인자로 넘깁니다. 전부 생략 가능하고, 표기한 값이 기본값입니다.
| 옵션 | 설명 |
|---|---|
snapshot | 초기 문서(JSON 스냅샷, 아래 포맷 절 참조). 없으면 빈 문서로 시작 |
theme: 'light' | 'dark' | 에디터 테마(기본 light) |
mode: 'notion' | 노션 스타일 프리셋: 툴바를 숨기고 블록 핸들을 켭니다(아래 절 참조). 기본 classic |
handles: true | 블록 핸들 개별 토글(mode 프리셋보다 우선) |
writingMode: 'paper' | 글쓰기 모드. plain, focus, book, paper, essay, script, techdoc 중 하나이거나 프리셋 객체. 조판과 계량, 목차, 각주, 대본 흐름이 함께 바뀝니다 |
writingMode: { id, extends } | 내장 모드 위에 몇 칸만 덮어써 새 모드를 만듭니다. extends 를 빼면 아무것도 켜지지 않은 바닥에서 시작합니다 |
writingLabels | 모드 UI 문구 교체(계량 이름, 목차, 각주, 목표). 부분 지정 가능하고 지정하지 않은 항목은 영어 기본값 |
writingSlashLabels | 모드 전용 슬래시 항목 문구(각주, 대본 블록) |
handleLabels | 블록 핸들 문구 교체(부분 지정 가능) |
toolbar: false | 상단 툴바 숨김(기본 표시) |
menus: false | 슬래시 커맨드·선택 버블 메뉴 끄기(기본 켬) |
markdown: false | # 제목, - 리스트 같은 마크다운 입력 단축 끄기(기본 켬) |
typography: false | 굽은따옴표·말줄임표 자동 치환 끄기(기본 켬) |
placeholder | 빈 문서에 표시할 힌트 문구 |
autofocus: true | 생성 직후 에디터에 포커스(기본 꺼짐) |
toolbarLabels | 툴바 문구 교체(부분 지정 가능, 아래 절 참조) |
licenseKey | Plus 라이선스 키. 도메인 검증 후 워터마크 제거 |
onChange(snapshot) | 내용이 바뀔 때마다 최신 스냅샷을 전달 |
onRequestImage() | 이미지 업로더 훅. {src, alt?} 를 resolve 하면 삽입, null 은 취소. 없으면 툴바가 URL 입력으로 폴백 |
onRequestFile() | 파일 첨부 훅. {src, name?, size?} 반환. 이 훅을 줄 때만 파일 버튼이 렌더됨 |
onRequestVideo() | 영상 파일 업로드 훅. {src} 반환. 지정하면 영상 입력 행에 업로드 버튼이 추가됨(링크 입력은 항상 가능) |
노션 스타일 모드
mode: 'notion' 은 툴바를 숨기고 블록 핸들을 켭니다. 블록에 마우스를 올리면 왼쪽에 추가(+)와 메뉴(⋮⋮) 버튼이 나타나고, 메뉴에서 위, 아래 이동, 복제, 삭제, 블록 전환을 할 수 있습니다. 서식은 슬래시 커맨드와 선택 버블 메뉴를 그대로 사용합니다.
툴바가 없으면 이미지, 영상, 파일 삽입 항목은 슬래시 메뉴에서도 빠집니다(삽입 UI가 툴바 소유라서). 핸들과 첨부가 둘 다 필요하면 toolbar: true 를 함께 넘기면 됩니다. 핸들과 툴바는 공존합니다.
Typdit.create(el, {
mode: 'notion',
handleLabels: { moveUp: '위로 이동', moveDown: '아래로 이동', delete: '삭제' },
});인스턴스 메서드
Typdit.create() 가 돌려주는 핸들로 문서를 읽고 씁니다. 저장은 전적으로 호스트 사이트의 몫입니다.
| 메서드 | 설명 |
|---|---|
getSnapshot() | 현재 문서를 JSON 스냅샷으로 반환 |
setSnapshot(json) | 스냅샷으로 문서 전체를 교체(불러오기) |
getText() | 서식 없는 순수 텍스트 반환(검색 색인·글자 수 세기용) |
focus() | 에디터에 포커스 이동 |
destroy() | 에디터 제거·리스너 해제. SPA 에서 화면을 떠날 때 호출 |
version | SDK 버전 문자열(현재 1.0.0) |
스냅샷 포맷
스냅샷은 그대로 저장하고 그대로 되돌려주면 되는 평면 JSON 입니다. text 의 줄 하나가 blocks 의 항목 하나와 1:1 로 대응하고, marks 는 text 기준 오프셋 구간입니다. 모르는 블록, 마크 타입은 불러올 때 조용히 건너뛰므로, 새 버전이 만든 스냅샷을 구버전이 열어도 깨지지 않습니다.
{
"version": 1,
"text": "제목\n첫 문단입니다.",
"blocks": [
{ "type": "heading", "attrs": { "level": 1 } },
{ "type": "paragraph" }
],
"marks": [
{ "type": "bold", "from": 3, "to": 7 }
]
}자동 저장 붙이기
onChange 는 타건마다 불립니다. 서버 저장은 디바운스를 걸어 손을 뗀 뒤 한 번만 보내는 것이 일반적입니다.
let timer;
const editor = Typdit.create(el, {
onChange: (snapshot) => {
clearTimeout(timer);
timer = setTimeout(() => {
fetch('/api/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(snapshot),
});
}, 800);
},
});이미지·파일 업로드 연동
에디터는 파일을 직접 업로드하지 않습니다. 훅이 파일 선택과 업로드를 맡고 URL 만 돌려주면, 삽입과 렌더는 에디터가 합니다. 훅 없이도 이미지는 URL 입력으로 동작하고, 파일 버튼은 onRequestFile 을 줄 때만 나타납니다.
Typdit.create(el, {
onRequestImage: async () => {
const file = await pickFile('image/*'); // 파일 선택 UI는 호스트 구현
if (!file) return null; // null = 취소
const src = await uploadToMyServer(file); // 업로드도 호스트 구현
return { src, alt: file.name };
},
onRequestFile: async () => {
const file = await pickFile('*/*');
if (!file) return null;
const src = await uploadToMyServer(file);
return { src, name: file.name, size: file.size };
},
onRequestVideo: async () => {
const file = await pickFile('video/*');
return file ? { src: await uploadToMyServer(file) } : null;
},
});툴바 문구 바꾸기
기본 문구는 영어입니다. toolbarLabels 에 바꿀 키만 부분적으로 넘기면 나머지는 기본값이 유지됩니다. 키 전체: text, quote, code, divider, table, image, video, file, upload, font, fontDefault, color, highlight, none, alignLeft, alignCenter, alignRight, imageUrl, videoUrl, link, linkApply, linkRemove, undo, redo.
Typdit.create(el, {
toolbarLabels: {
text: '본문', quote: '인용', code: '코드', divider: '구분선',
image: '이미지', video: '영상', link: '링크',
undo: '되돌리기', redo: '다시 실행',
},
});React 에서 쓰기
ref 로 컨테이너를 잡고 마운트 시 생성, 언마운트 시 destroy() 를 호출합니다.
import { useEffect, useRef } from 'react';
function TypditEditor({ onChange }) {
const ref = useRef(null);
useEffect(() => {
const editor = window.Typdit.create(ref.current, { onChange });
return () => editor.destroy();
}, []);
return <div ref={ref} />;
}Vue 에서 쓰기
onMounted 에서 생성하고 onBeforeUnmount 에서 destroy() 를 호출합니다.
<template><div ref="host"></div></template>
<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue';
const host = ref(null);
let editor;
onMounted(() => { editor = window.Typdit.create(host.value, {}); });
onBeforeUnmount(() => editor?.destroy());
</script>SSR 프레임워크(Next.js 등)에서 쓰기
스크립트는 브라우저에서만 실행됩니다. 서버 렌더 중에는 window 가 없으므로 클라이언트 전용 컴포넌트에서 스크립트를 로드한 뒤 생성하세요.
'use client';
import { useEffect, useRef } from 'react';
export default function Editor() {
const ref = useRef(null);
useEffect(() => {
let editor;
const s = document.createElement('script');
s.src = 'https://typdit.com/embed/v1.js';
s.onload = () => { editor = window.Typdit.create(ref.current, {}); };
document.head.appendChild(s);
return () => editor?.destroy();
}, []);
return <div ref={ref} />;
}한 페이지에 여러 에디터
Typdit.create() 호출마다 독립 인스턴스가 만들어지므로 한 페이지에 몇 개든 둘 수 있습니다. 스타일은 첫 생성 때 style 태그 하나로만 주입되고, 각 인스턴스는 자기 핸들의 destroy() 로 개별 제거합니다.
라이선스와 워터마크
- 키 없이도 에디터는 전부 동작하며, 하단에 작은 워터마크가 붙습니다.
- Plus 구독자는 에디터 지면의 임베드 카드에서 도메인별 키를 발급합니다. 키는 등록한 도메인과 그 서브도메인에서 유효합니다(*.example.com 표기도 같은 의미).
- 키의 만료는 발급 시점의 구독 만료와 같습니다. 구독을 연장했다면 키를 다시 발급하세요.
- 검증에 실패하거나 네트워크가 막혀 있어도 에디터는 차단되지 않습니다. 워터마크가 남을 뿐, 에러도 나지 않습니다.
네트워크와 CSP
- 문서 내용은 어디로도 전송되지 않습니다. 에디터는 콘텐츠에 대해 네트워크 요청을 하지 않고, 저장은 전적으로 호스트 사이트가 합니다.
- 유일한 요청은 licenseKey 를 지정했을 때의 검증 1회입니다(firestore.googleapis.com 읽기 전용).
- CSP 를 쓰는 사이트는 script-src 에 typdit.com 을, 키를 쓴다면 connect-src 에 firestore.googleapis.com 을 허용합니다. 본문에 넣는 이미지·영상은 별개로, 그 출처를 img-src·frame-src 에서 열어야 보입니다(유튜브 임베드는 youtube-nocookie.com).
- 스타일은 td- 접두사 클래스로 스코프된 style 태그 하나로 주입되어 호스트 CSS 와 섞이지 않습니다.
버전 정책
/embed/v1.js 는 메이저 버전이 고정된 URL 입니다. v1 URL 안에서는 하위 호환이 유지되고, 호환이 깨지는 변경은 새 URL(v2)로 나갑니다. 현재 로드된 버전은 런타임에 Typdit.version 으로 확인할 수 있습니다.