NABI NOTE
オープンソースの WYSIWYG エディタ
NABI NOTE は、書式、配置、表、アップロードなどの機能をコアから分離し、独立モジュールである wing として構成したオープンソースの WYSIWYG エディターです。必要な wing だけを選んでエディターを組み立てられ、自分で作った wing も接続できます。純粋な JavaScript で書かれているため、React や Vue のようなフレームワークでも使えます。ビルド環境のないページでは CDN ライブラリー ですぐに始めることもできます。独自の文書形式である NABI TREE は、ブラウザー DOM のない Node.js 環境でも HTML に変換できます。入力された文書は登録された規則に従って整理され、HTML を作るときはテキストと属性をエスケープし、リンクアドレスを検査します。ただし、自作 wing とサーバーから受け取ったデータは、アプリケーションのセキュリティポリシーに合わせて別途確認する必要があります。CSS 変数と rem ベースのレイアウトでテーマとサイズを調整でき、ダークモードと複数言語に合う書体設定も提供します。表ソート、ローカル履歴、AI を活用した文書作業も、1 つの文書モデルの上で続きます。
書体
ゴシック体(既定)・明朝体・等幅・筆記体——書体の系統ごとに文字体系別のフォントを積んであるので、どの言語を書いてもその系統の顔つきが保たれ、その系統に手書きの顔を持たない文字体系はブラウザ既定のフォントに落ちます。既定の書体はホストが決めます。
以下は多言語で示した書体です。
ゴシック体 · 无衬线 · 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 と入力してスペースか改行を打つと、ご覧のとおり自動で変換されます。
リンクの開き方
文書にはリンク先のアドレスだけが保存され、リンクをどう開くかは文書を表示するページが決めます。
添付リンク
画像以外のファイルをアップロードすると、下のようなファイル形式のリンクになります。
添付ファイル のように残ります。
配置
左揃え
中央揃え
右揃え
見出しも配置を変えられます。
リスト
箇条書き
空行で - を打ってスペースを押すと、その場で箇条書きになります。
箇条書きの項目です
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 |
価格はすべて数値なので、数値として並べ替えられます。
在庫は最後の行に文字があるため、文字として並べ替えられます。
区切り線
--- を入力して改行すると区切り線に変わります。
画像
幅は 30%〜100% まで調節でき、左・中央・右に寄せられます。
YouTube
アップロード
画像やファイルをエディタにドラッグしてみてください。
ここで使っているアップロードはモックアップで、設定でサーバーにつなげられます。
アップロードに失敗した場合、その画像やファイルはエディタから取り除かれます。
引用
空行で > を打ってスペースを押すと引用箱になります。
複数行にわたって使えます。
コード
空行で ``` を打ってスペースか改行を押すとコード箱になります。
```java のように言語を書いてスペースか改行を打つと、その言語が適用されたコード箱になります。
import { createNabiWith, defaultWings } from 'nabi-note'
const { nabi } = createNabiWith(defaultWings)
const html = nabi.getHtml()折りたたみ
折りたたみは見出しと中身でできています。
閉じた状態と開いた状態のどちらで保存するか指定できます。
ショートカット
Shift を素早く二回押すと、ツールバーに各機能のショートカットが出ます。
自動書式
例 | 操作キー | 結果 |
|---|---|---|
# | スペース | 見出し |
- | スペース | 箇条書き |
1. | スペース | 番号付きリスト |
[ ] · [x] | スペース | チェックリスト |
> | スペース | 引用 |
``` · ```ts | スペース · 改行 | コード箱 |
--- | 改行 | 区切り線 |
https://… | スペース · 改行 | リンク |
出力関数
関数 | 結果 |
|---|---|
getHtml() | HTML |
getJson() | JSON |
DOM のない環境に対応
JSON から HTML に変換するとき DOM は要りません。
サーバー(Node.js)でも、保存しておいた NABI TREE を読み、同じ規則で HTML を作れます。
モバイルに優しい
モバイル UI — レスポンシブでモバイル UI に対応します。
モバイルキーボード補正 — キーボードが出たらその高さを補正します。
可変サイズ — サイズはすべて rem 単位で作られています。
多言語 — 多言語に対応します。
カスタマイズ
自作のwing — 必要な機能があれば自分で作って使えます。
自作の 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'
// The box that asks — without it the core answers "no" to everything (plug in your own)
const ask = { message: (t: string) => alert(t), confirm: (t: string) => confirm(t) }
const selected = [
...defaultWings,
]
const { nabi, registry } = createNabiWith(selected, { ask })
// Bring your own highlighter (Prism, highlight.js, Shiki, …) — the wing stays as it is
// and only the attach is swapped (`makeCodeAttach` ships from the same door)
// { ...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,
// your upload goes here — report progress with task.onProgress(0–100)
uploader: async (task) => ({ uri: 'https://cdn.example/uploaded' }),
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: 'ja', name: () => 'note' })
const history = mountLocalHistory({ nabi, storage: browserHistoryStorage(window) })
const diff = mountDiffWing({ nabi, registry, surface: content, locale: 'ja' })
// The locale also sets the direction — Arabic and Urdu run right to left
mountSurface({ nabi, registry, root: content, locale: 'ja', fileSink: upload.take })
const settle = watchSettle(document, { surface: content })
const shared = { nabi, registry, surface: content, settle, locale: 'ja' }
// A tool that needs a panel comes back to the host — without this the button is dead
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: 'ja', 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 })
// The preview and fullscreen buttons — they stand their own box at the end of the row
mountViewTools({ ...shared, root, container: document.querySelector('#toolbar')! })
// on every change — hook up your own code here
// nabi.onChange(() => user_callback(nabi.getHtml()))