Крылья
Гарнитура по умолчаниюОтступ


NABI NOTE

Редактор WYSIWYG с открытым кодом


NABI NOTE — редактор WYSIWYG с открытым кодом, в котором каждая крупная возможность — оформление, выравнивание, таблицы, загрузки и прочее — вынесена из ядра в отдельный модуль, называемый «wing», так что разработчик может дополнять его без всяких границ. Он написан на чистом Vanilla JS с прицелом на НОЛЬ зависимостей от фреймворков, поэтому одинаково ложится и в React, и во Vue, и куда угодно, а для проектов без сборки есть библиотека с CDN. У него свой формат JSON — NABI TREE, поэтому превращение HTML в текст и обратно можно подготовить там, где DOM нет вовсе (Node.js, SSR); а поскольку документ собирается заново из разрешённого словаря, а не латается, скрипты XSS перекрыты у корня без отдельной библиотеки очистки. В оформлении принят подход переменных CSS, так что фирменный цвет меняется легко, а вёрстка в rem сохраняет плавность мобильного интерфейса при любом масштабе; цвета под тёмную и светлую тему, маркеры и многоязычные шрифты уже на месте. Кроме того, доступны сортировка столбцов таблицы с учётом типа, локальная история и поддержка vibe coding.


Шрифт

Гротеск (по умолчанию), антиква, моноширинный и рукописный — в каждой семье шрифты сложены по системам письма, поэтому любой язык сохраняет лицо этой семьи; письменность, у которой в этой семье нет рукописного начертания, откатывается к шрифту браузера. Шрифт по умолчанию задаёт хост.


Ниже каждая семья показана на разных языках.


Без засечек · بلا زخارف · सैन्स सेरिफ़ · স্যান্স সেরিফ · سانس سیرف · Tanpa serif · بدون سریف · सॅन्स सेरिफ · Không chân · శాన్స్ సెరిఫ్ · Mara seref · Sans serif · Bila serif · சான்ஸ் செரிஃப் · ไร้เชิง · Senza grazie · 산세리프 · ゴシック体 · 无衬线 · Serifenlos · Sans empattement · Sin serifa · Sem serifa


С засечками · بزخارف · सेरिफ़ · সেরিফ · سیرف · Berserif · سریف · सेरिफ · Có chân · సెరిఫ్ · Mai seref · Serif · Yenye serif · செரிஃப் · มีเชิง · Con grazie · 세리프 · 明朝体 · 衬线 · Avec empattement · Con serifa · Com serifa


Моноширинный · ثابت العرض · मोनोस्पेस · মনোস্পেস · یکساں چوڑائی · Lebar tetap · تک‌فاصله · Đơn cách · మోనోస్పేస్ · Tazara ɗaya · Eş aralıklı · Nafasi moja · ஒற்றையகலம் · ความกว้างคงที่ · Monospaziato · Monospace · 고정폭 · 等幅 · 等宽 · Dicktengleich · Chasse fixe · Monoespaciada · Monoespaçada


Рукописный · خط اليد · हस्तलिपि · হস্তলিপি · رواں خط · Tulisan tangan · دست‌نویس · हस्ताक्षर · Chữ viết tay · చేతిరాత · Rubutun hannu · El yazısı · Mwandiko · கையெழுத்து · ลายมือ · Corsivo · Cursive · 필기체 · 筆記体 · 手写体 · Schreibschrift · Cursiva



Размер текста

Очень мелко

Мелко

Крупно

Очень крупно



Заголовок

На пустой строке наберите # и нажмите пробел — тут же станет заголовком.

H1

H2

H3

H4

H5
H6



Жирный · Курсив · Подчёркивание · Зачёркивание

Жирный курсив подчёркнутый зачёркнутый — вот пример.

Их можно и накладывать друг на друга.

Верхний и нижний индекс

Площадь — 3,5 м2, а сноска ставится вот так1.

Вода — это H2O.



Цвет текста · Маркер

Палитра подобрана так, чтобы одинаково читаться и в светлой, и в тёмной теме.

Цвет текста Зелёный · Коралловый · Фиолетовый · Янтарный · Синий

Маркер Жёлтый · Зелёный · Голубой · Розовый · Пурпурный · Оранжевый



Ссылка

Вставьте адрес — и получится ссылка.

Разрешены только http:// и https://; что-то вроде javascript: не пройдёт.

Например, наберите https://nabi.saro.me и нажмите пробел или Enter — превратится само, как здесь.

Открытие ссылок

По умолчанию ссылка того же происхождения открывается в этом окне, а любой другой сайт — в новом; правило задаётся при объявлении редактора.

Ссылка на вложение

Если загрузить не картинку, останется ссылка в виде файла, как ниже.

Вложение вот так она и остаётся.



Выравнивание

По левому краю

По центру

По правому краю

Заголовки тоже выравниваются.



Списки

Маркированный список

На пустой строке наберите - и нажмите пробел — тут же станет маркированным списком.

  • Это пункт списка

    • Tab / Shift+Tab делают отступ и снимают его.

Нумерованный список

На пустой строке наберите 1. и нажмите пробел — получится нумерованный список.

  1. Первое

  2. Второе

  3. Третье

Список задач

На пустой строке наберите [ ] или [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%, а сама она встаёт слева, по центру или справа.



YouTube



Загрузка

Перетащите картинку или файл прямо в редактор.

Загрузка в этом примере поддельная; одной настройкой она подключается к вашему серверу.

Если загрузка сорвётся, картинка или файл убирается из редактора.



Цитата

На пустой строке наберите > и нажмите пробел — получится блок цитаты.

Он может занимать несколько строк.



Код

На пустой строке наберите ``` и нажмите пробел или Enter — получится блок кода.

Напишите заодно и язык, например ```java, затем пробел или Enter — блок возьмёт этот язык.

import { createNabiWith, defaultWings } from 'nabi-note'

const { nabi } = createNabiWith(defaultWings)
const html = nabi.getHtml()



Сворачиваемый блок

Сворачиваемый блок состоит из заголовка и содержимого.

Вы сами решаете, сохранять его свёрнутым или развёрнутым.




Горячие клавиши

Нажмите Shift дважды подряд — и панель покажет сочетание для каждой возможности.



Автоформат

Пример

Клавиша

Результат

#

Пробел

Заголовок

-

Пробел

Маркированный список

1.

Пробел

Нумерованный список

[ ] · [x]

Пробел

Список задач

>

Пробел

Цитата

``` · ```ts

Пробел · Enter

Блок кода

---

Enter

Разделитель

https://…

Пробел · Enter

Ссылка



Функции вывода

Функция

Результат

getHtml()

HTML

getJson()

JSON



Работает без DOM

Чтобы превратить JSON в HTML, DOM не нужен.

Сервер (Node.js) читает сохранённое дерево наби как есть и собирает HTML, попутно отсекая XSS.


Дружелюбен к телефону

  • Мобильный вид — отзывчивая вёрстка держит мобильный вид.

  • Поправка на клавиатуру — когда клавиатура выезжает, её высота учитывается.

  • Гибкие размеры — все размеры записаны в rem.

  • Многоязычность — поддерживает несколько языков.


Настройка под себя

  • Своё wing — не хватает возможности — соберите её сами и подключите.

  • Свой CSS — цвета, скругления и отступы заданы через --nabi-*, тёмное и светлое — на ваше усмотрение.

  • Открытый код — открыт на GitHub.


Открыть документацию → nabi.saro.me

HTMLЗагрузка редактора…
JSONnabi-treeЗагрузка редактора…
Установка
npm install nabi-note
Код
import {
  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: 'ru', name: () => 'note' })
const history = mountLocalHistory({ nabi, storage: browserHistoryStorage(window) })
const diff = mountDiffWing({ nabi, registry, surface: content, locale: 'ru' })

// The locale also sets the direction — Arabic and Urdu run right to left
mountSurface({ nabi, registry, root: content, locale: 'ru', fileSink: upload.take })

const settle = watchSettle(document, { surface: content })
const shared = { nabi, registry, surface: content, settle, locale: 'ru' }
// 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: 'ru', 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()))