NABI NOTE
오픈소스 WYSIWYG 에디터
나비 노트는 서식, 정렬, 표, 업로드 같은 기능을 코어에서 분리해 독립 모듈인 ‘날개’로 구성한 오픈소스 WYSIWYG 에디터입니다. 필요한 날개만 골라 편집기를 구성할 수 있고, 직접 만든 날개도 연결할 수 있습니다. 순수 JavaScript로 작성되어 React나 Vue 같은 프레임워크에서도 사용할 수 있습니다. 빌드 환경이 없는 페이지에서는 CDN 라이브러리를 이용해 바로 시작할 수도 있습니다. 자체 문서 형식인 NABI TREE는 브라우저 DOM이 없는 Node.js 환경에서도 HTML로 변환할 수 있습니다. 입력된 문서는 등록된 규칙에 따라 정리되며, HTML을 만들 때는 텍스트와 속성을 이스케이프하고 링크 주소를 검사합니다. 다만 직접 만든 날개와 서버에서 받은 데이터는 애플리케이션의 보안 정책에 맞게 별도로 검토해야 합니다. CSS 변수와 rem 기반 레이아웃으로 테마와 크기를 조절할 수 있으며, 다크 모드와 여러 언어에 맞는 서체 설정도 제공합니다. 표 정렬과 로컬 히스토리, AI를 활용한 문서 작업도 하나의 문서 모델 위에서 이어집니다.
서체
산세리프(기본), 세리프, 고정폭, 필기체마다 언어별 대체 글꼴을 마련해 두었습니다. 선택한 서체에 맞는 글꼴이 없으면 브라우저의 기본 글꼴로 표시됩니다. 편집기의 기본 서체는 호스트 페이지에서 정합니다.
각 서체가 여러 언어로 어떻게 보이는지 아래에서 확인할 수 있습니다.
산세리프 · ゴシック体 · 无衬线 · Serifenlos · Sans empattement · Sin serifa · Sem serifa · Без засечек · بلا زخارف · सैन्स सेरिफ़ · স্যান্স সেরিফ · سانس سیرف · Tanpa serif · بدون سریف · सॅन्स सेरिफ · Không chân · శాన్స్ సెరిఫ్ · Mara seref · Sans serif · Bila serif · சான்ஸ் செரிஃப் · ไร้เชิง · Senza grazie
세리프 · 明朝体 · 衬线 · Serif · Avec empattement · Con serifa · Com serifa · С засечками · بزخارف · सेरिफ़ · সেরিফ · سیرف · Berserif · سریف · सेरिफ · Có chân · సెరిఫ్ · Mai seref · Yenye serif · செரிஃப் · มีเชิง · Con grazie
고정폭 · 等幅 · 等宽 · Dicktengleich · Chasse fixe · Monoespaciada · Monoespaçada · Моноширинный · ثابت العرض · मोनोस्पेस · মনোস্পেস · یکساں چوڑائی · Lebar tetap · تکفاصله · Đơn cách · మోనోస్పేస్ · Tazara ɗaya · Eş aralıklı · Nafasi moja · ஒற்றையகலம் · ความกว้างคงที่ · Monospaziato · Monospace
필기체 · 筆記体 · 手写体 · Schreibschrift · Cursive · Cursiva · Рукописный · خط اليد · हस्तलिपि · হস্তলিপি · رواں خط · Tulisan tangan · دستنویس · हस्ताक्षर · Chữ viết tay · చేతిరాత · Rubutun hannu · El yazısı · Mwandiko · கையெழுத்து · ลายมือ · Corsivo
글자 크기
아주 작게
작게
크게
아주 크게
제목
빈 줄에 #을 입력하고 스페이스를 누르면 제목으로 바뀝니다.
H1
H2
H3
H4
H5
H6
굵게 · 기울임 · 밑줄 · 취소선
굵게 기울임 밑줄 취소선을 한 글자에 함께 적용할 수 있습니다.
겹쳐서도 사용할 수 있습니다.
첨자
넓이는 3.5m2이고, 각주는 이렇게 답니다.1
물은 H2O입니다.
글자색 · 형광펜
라이트 모드와 다크 모드에서 모두 읽기 쉬운 색으로 구성했습니다.
글자색은 초록 · 코랄 · 보라 · 호박 · 파랑
형광펜 노랑 · 연두 · 하늘 · 분홍 · 보라 · 주황
링크
주소를 입력하면 링크가 됩니다.
http:// 또는 https:// 주소뿐 아니라 .이나 /로 시작하는 같은 사이트의 경로도 링크에 사용할 수 있습니다. javascript: 같은 주소는 허용하지 않습니다.
예를 들어 https://nabi.saro.me를 입력하고 스페이스나 Enter를 누르면 자동으로 링크로 바뀝니다.
링크가 열리는 방식
문서에는 링크 주소만 저장되며, 링크를 여는 방식은 문서를 표시하는 페이지에서 정합니다.
첨부 링크
이미지가 아닌 파일은 아래와 같은 첨부 링크로 남습니다.
첨부 파일 링크가 문서에 저장됩니다.
정렬
왼쪽 정렬
가운데 정렬
오른쪽 정렬
제목의 정렬도 바꿀 수 있습니다.
목록
글머리 목록
빈 줄에 -를 입력하고 스페이스를 누르면 글머리 목록이 됩니다.
글머리 목록입니다.
Tab / Shift+Tab으로 들여쓰기와 내어쓰기를 할 수 있습니다.
번호 목록
빈 줄에 1.을 입력하고 스페이스를 누르면 번호 목록이 됩니다.
첫째
둘째
셋째
체크리스트
빈 줄에 [ ] 또는 [x]를 입력하고 스페이스를 누르면 체크리스트가 됩니다.
체크된 항목입니다.
아직 체크되지 않은 항목입니다.
표
툴바에서 표를 만든 뒤 행과 열을 추가하거나 삭제하고, 필요한 셀을 병합할 수 있습니다.
표 정렬
미리보기를 누르고 재고와 가격 머리글 칸을 차례로 눌러 보세요.
모델 | 재고 | 가격 | 무게 |
|---|---|---|---|
NB-7 | 1,200 | 349 | 1.2 kg |
NB-9 | 20,000 | 99 | 0.9 kg |
NB-12 | 3,500 | 1,299 | 1.4 kg |
NB-80 | 900 | 8,900 | 2.1 kg |
NB-100 | 미정 | 12,999 | 2.4 kg |
가격 열은 값이 모두 숫자이므로 숫자를 기준으로 정렬됩니다.
재고 열에는 숫자와 글자가 섞여 있어 문자열을 기준으로 정렬됩니다.
구분선
---를 입력하고 Enter를 누르면 구분선으로 바뀝니다.
이미지
이미지 크기는 30%에서 100%까지 조절할 수 있으며, 왼쪽·가운데·오른쪽으로 정렬할 수 있습니다.
유튜브
업로드
이미지나 파일을 편집기로 끌어다 놓아 보세요.
이 데모에서는 파일을 실제 서버로 전송하지 않습니다. 애플리케이션에 업로더를 연결하면 서버에 저장할 수 있습니다.
업로드에 실패한 항목은 문서에 추가되지 않습니다.
인용
빈 줄에 >를 입력하고 스페이스를 누르면 인용문이 됩니다.
여러 줄을 사용할 수 있습니다.
코드
빈 줄에 ```를 입력하고 스페이스나 Enter를 누르면 코드 상자가 됩니다.
```java처럼 언어 이름까지 입력하고 스페이스나 Enter를 누르면 해당 언어가 지정된 코드 상자로 바뀝니다.
import { createNabiWith, defaultWings } from 'nabi-note'
const { nabi } = createNabiWith(defaultWings)
const html = nabi.getHtml()접기
접기 블록은 제목과 내용으로 이루어집니다.
접힌 상태와 펼친 상태 중 원하는 상태로 저장할 수 있습니다.
단축키
Shift를 빠르게 두 번 누르면 툴바에 기능별 단축키가 표시됩니다.
자동 서식
예시 | 액션 키 | 설명 |
|---|---|---|
# | 공백 | 제목 |
- | 공백 | 글머리 목록 |
1. | 공백 | 번호 목록 |
[ ] · [x] | 공백 | 체크리스트 |
> | 공백 | 인용 |
``` · ```ts | 공백 · 엔터 | 코드 상자 |
--- | 엔터 | 구분선 |
https://… | 공백 · 엔터 | 링크 |
출력 함수
함수 | 결과 |
|---|---|
getHtml() | HTML |
getJson() | JSON |
DOM 없는 환경 지원
NABI TREE를 HTML로 변환할 때는 DOM이 필요하지 않습니다.
서버(Node.js)에서도 저장해 둔 NABI TREE를 읽어 같은 규칙으로 HTML을 만들 수 있습니다.
모바일 환경
모바일 UI — 화면 폭에 맞춰 편집 도구를 배치합니다.
모바일 키보드 보정 — 키보드가 열릴 때 편집 화면의 높이를 조절합니다.
유연한 크기 — 주요 레이아웃에 rem 단위를 사용해 확대와 축소에 맞춥니다.
다국어 — 언어와 글의 방향에 맞는 로케일과 서체를 설정할 수 있습니다.
사용자 정의
사용자 날개 — 필요한 기능이 있다면 직접 만들어 연결할 수 있습니다.
사용자 CSS — 주요 색상과 모서리, 간격을 --nabi-* 토큰으로 조절해 라이트 모드와 다크 모드에 맞출 수 있습니다.
오픈소스 — GitHub에서 소스 코드를 공개하고 함께 개발합니다.
문서 보기 → nabi.saro.me
npm install nabi-noteimport {
createNabiWith, mountSurface, mountToolbar, mountContextToolbar,
mountHints, watchSettle, defaultWings, mountUpload, mountUploadView,
browserFileStore, mountFile, parseNodes, mountViewTools,
browserHistoryStorage, mountLocalHistory, openHistoryPanel,
} from 'nabi-note'
import { mountDiffWing } from 'nabi-note/diff'
// 묻는 상자 — 안 주면 코어는 "아니오"로 답한다(제 상자를 물려도 된다)
const ask = { message: (t: string) => alert(t), confirm: (t: string) => confirm(t) }
const selected = [
...defaultWings,
]
const { nabi, registry } = createNabiWith(selected, { ask })
// 색칠은 호스트의 하이라이터가 한다 (Prism·highlight.js·Shiki…) — wing 은 그대로 두고
// 붙는 일(attach)만 갈아 낀다 — `makeCodeAttach` 도 같은 문에서 나온다
// { ...codeWing, attach: makeCodeAttach({ highlight: (source, lang) => [{ text: source }] }) }
const root = document.querySelector('.nabi')!
const content = document.querySelector('.nabi-content')!
const view = mountUploadView({ nabi, surface: content })
const upload = mountUpload({
nabi, root: content,
// 여기에 서버로 올리는 코드 — 진행률은 task.onProgress(0~100)
uploader: async (task) => ({ uri: 'https://example.com/uploaded-file' }),
extensions: ['png', 'jpg', 'pdf'], maxFileSize: 10 * 1024 * 1024,
onStart: (tasks) => view.start(tasks),
onProgress: (id, percent) => view.progress(id, percent),
onSettle: () => view.settle(),
onDone: () => view.done(),
})
const file = mountFile({ nabi, registry, store: browserFileStore(document), parse: parseNodes, locale: 'ko', name: () => 'note' })
const history = mountLocalHistory({ nabi, storage: browserHistoryStorage(window) })
const diff = mountDiffWing({ nabi, registry, surface: content, locale: 'ko' })
// locale 이 글의 방향도 정한다 — 아랍어·우르두면 오른쪽에서 왼쪽으로 선다
mountSurface({ nabi, registry, root: content, locale: 'ko', fileSink: upload.take })
const settle = watchSettle(document, { surface: content })
const shared = { nabi, registry, surface: content, settle, locale: 'ko' }
// 판이 필요한 도구는 호스트로 돌아온다 — 이 줄이 없으면 단추가 무반응이다
const toolbar = mountToolbar({
...shared, root: document.querySelector('#toolbar')!,
file,
onFiles: upload.take,
onHost: (w) => {
if (w === 'diff') { diff.open(); return }
if (w !== 'localHistory') return
openHistoryPanel({
history, surface: content, locale: 'ko', sessionId: history.sessionId,
render: (record) => createNabiWith(selected, { doc: JSON.parse(record.body) }).nabi.getHtml(),
})
},
})
const context = mountContextToolbar({ ...shared, root: document.querySelector('#context')! })
mountHints({ toolbar, context, root, surface: content })
// 미리보기·전체화면 두 단추 — 툴바 줄의 끝에 제 상자를 세워 앉는다
mountViewTools({ ...shared, root, container: document.querySelector('#toolbar')! })
// 값이 바뀔 때마다 — 여기에 당신의 코드를 건다
// nabi.onChange(() => user_callback(nabi.getHtml()))