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] を打ってスペースを押すとチェックリストになります。
チェック済みの項目です。
まだチェックしていない項目です。
表
セルには複数の段落やブロックを入れ、背景色で内容を区別できます。下の表の文章と画像を直接編集してみてください。
資料 | 作業メモ |
|---|---|
画像と説明を同じセルに入れています。 | 段落も自由にEnterで段落を分け、Shift+Enterで同じ段落内に改行を入れてみてください。 |
| クリックして編集のヒントを表示セル内にも折りたたみ・引用・コード・動画を入れられます。 セルを結合しても、それぞれの内容は順番どおりに残ります。 |
セル内をクリックすると、背景色と行・列のツールが表示されます。複数のセルを選ぶと、色と配置をまとめて変更できます。表全体の位置は「表全体を選択」を押してから配置ツールで変えてみてください。
色や複数のブロックを含む表は、Markdownで保存するときもHTMLとして保持されます。
セルの背景色
対応しているセルの背景色をすべて並べました。各セルをクリックして色を変えるか、プロパティツールの「既定」を選んで背景色を消してみてください。
黄色 yellow | 黄緑 green | 水色 cyan |
ピンク pink | 紫 purple | オレンジ orange |
表の並べ替え
プレビューを押してから、在庫と価格の見出しを順に押してみてください。
型番 | 在庫 | 価格 | 重さ |
|---|---|---|---|
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% まで調節でき、左・中央・右に寄せられます。
フロート領域
ひと目で確認
文章・画像・表を入れられる小さな補助領域です。
配置 | 右 |
最大幅 | 40% |
本文の横に補足説明や小さな資料を置きたいときは、フロート領域を使ってみてください。本文の幅が広ければ、この文章が領域の横に回り込みます。領域内をクリックすると、同じエディターで文章を編集し、画像や表も挿入できます。
領域内にカーソルを置くとプロパティツールが開きます。左右の配置を切り替え、最大幅を20・30・40・50%から選んで本文に合う比率を探してみてください。表や画像を貼り付けても領域内に入ります。
選択した段落を領域で囲むこともできます。幅の横の解除ボタンを押すと、中の内容は本文に残ります。プレビューでは編集用の点線が消え、本文の幅が狭い場合や印刷時は1列で表示されます。
YouTube
アップロード
画像やファイルをエディタにドラッグしてみてください。
このデモではファイルを実際のサーバーに送信しません。アプリケーションにアップローダーを接続すると、サーバーに保存できます。
画像選択画面やアップロードを開いた後、カーソルだけを動かしても最初の位置に挿入されます。待機中に文書を編集すると挿入を中止し、お知らせを表示します。セル内で開始したアップロードの進捗表示と結果も、そのセル内に残ります。
引用
空行で > を打ってスペースを押すと引用箱になります。
複数行にわたって使えます。
コード
空行で ``` を打ってスペースか改行を押すとコード箱になります。
```java のように言語を書いてスペースか改行を打つと、その言語が適用されたコード箱になります。
import { createNabiWith, defaultWings } from 'nabi-note'
const { nabi } = createNabiWith(defaultWings)
const html = nabi.getHtml()折りたたみ
折りたたみは見出しと中身でできています。
閉じた状態と開いた状態のどちらで保存するか指定できます。
ツールバー
見出し・文字の書式・配置・リスト・挿入・文書のツールをグループにまとめています。PCではツールが展開され、モバイルではクイックツールと全ツールパレットを使えます。選択した表やフロートのプロパティはツールバーの下に表示されます。
ショートカット
Shiftをすばやく2回押すと、キーボードでツールを選べます。PCでは展開されたツールバー、モバイルでは全ツールパレットを操作します。Tabでグループを切り替え、方向キーでツールを選び、Enterで実行してみてください。
引用・コード・折りたたみなどのブロックの末尾でEnterをすばやく2回押すと、外へ出られます。連打間隔の既定値は180msです。
自動書式
例 | 操作キー | 結果 |
|---|---|---|
# | スペース | 見出し |
- | スペース | 箇条書き |
1. | スペース | 番号付きリスト |
[ ] · [x] | スペース | チェックリスト |
> | スペース | 引用 |
``` · ```ts | スペース · 改行 | コード箱 |
--- | 改行 | 区切り線 |
https://… | スペース · 改行 | リンク |
出力関数
関数 | 結果 |
|---|---|
getHtml() | HTML |
getJson() | JSON |
DOM のない環境に対応
NABI TREE から HTML に変換するとき DOM は要りません。
サーバー(Node.js)でも、保存した NABI TREE を読み込み、同じ規則で HTML を作成できます。
モバイルに優しい
モバイル UI — 画面の幅に合わせて編集ツールを配置します。
モバイルキーボード補正 — キーボードが開くと編集画面の高さを調整します。
柔軟なサイズ — 主要なレイアウトに rem 単位を使い、拡大・縮小に対応します。
多言語 — 言語や文章の方向に合ったロケールと書体を設定できます。
カスタマイズ
自作のwing — 必要な機能があれば、自分で作って接続できます。
自作の CSS — 主な色・角の丸み・余白を --nabi-* トークンで調整し、ライトモードとダークモードに合わせられます。
オープンソース — GitHub でソースコードを公開し、共同で開発しています。
ドキュメントを見る → nabi.saro.me
npm install nabi-noteimport {
createLocale, createNabiWith, mountSurface, mountToolbar,
mountContextToolbar, mountHints, watchSettle, makeTranslator,
defaultWings, mountUpload, mountUploadView, browserFileStore,
mountFile, mountViewTools, browserHistoryStorage, mountLocalHistory,
openHistoryPanel,
} from 'nabi-note'
import { mountDiffWing } from 'nabi-note/diff'
import { mountOutline } from 'nabi-note/viewer'
// 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 locale = createLocale('ja')
const { nabi, registry } = createNabiWith(selected, { ask, locale })
// 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, locale })
const upload = mountUpload({
nabi, root: content, locale,
// 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), locale, name: () => 'note' })
const history = mountLocalHistory({ nabi, storage: browserHistoryStorage(window) })
const diff = mountDiffWing({ nabi, registry, surface: content, locale })
// The locale also sets the direction — Arabic and Urdu run right to left
mountSurface({ nabi, registry, root: content, locale, fileSink: upload.take })
const trigger = document.createElement('button')
trigger.type = 'button'
trigger.textContent = makeTranslator(locale).t('outline')
trigger.style.cssText = 'position:fixed;right:1rem;bottom:1rem;z-index:1000'
root.append(trigger)
const outline = mountOutline(content, {
trigger, locale,
scrollContainer: () => root.classList.contains('is-fullscreen') ? root : window,
onChange: ({ items, activeIndex, available, open }) => {
trigger.hidden = !available
trigger.setAttribute("aria-expanded", String(open))
},
})
trigger.onclick = () => outline.toggle()
// Call outline.unmount() when removing the editor
const settle = watchSettle(document, { surface: content })
const shared = { nabi, registry, surface: content, settle, locale }
// 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, 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()))
// Call from your language selector. Content and editing history stay intact.
// locale.setLocale('ko')