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


NABI NOTE

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


NABI NOTE — WYSIWYG-редактор с открытым исходным кодом, в котором форматирование, выравнивание, таблицы, загрузка и другие функции отделены от ядра в самостоятельные модули — wings. Вы можете собрать редактор только из нужных wings и подключить собственные. Он написан на чистом JavaScript и подходит для фреймворков, например React и Vue. На страницах без среды сборки можно сразу начать с CDN-библиотеки. Собственный формат документа NABI TREE преобразуется в HTML даже в Node.js без DOM браузера. Входные документы нормализуются по зарегистрированным правилам; при создании HTML текст и атрибуты экранируются, а адреса ссылок проверяются. При этом собственные wings и данные с сервера необходимо отдельно проверять в соответствии с политикой безопасности приложения. Переменные CSS и разметка на основе rem позволяют менять тему и размер; также предусмотрены тёмный режим и шрифты для разных языков. Сортировка таблиц, локальная история и работа с документами с помощью ИИ используют одну модель документа.


Шрифт

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


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


산세리프 · ゴシック体 · 无衬线 · 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,5 м2, а сноска ставится вот так1.

Вода — это H2O.



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

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

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

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



Ссылка

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

Можно использовать адреса http:// и https://, а также пути на том же сайте, начинающиеся с . или /. Адреса вроде javascript: не допускаются.

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

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

Документ хранит только адрес ссылки; способ её открытия определяет страница, на которой показан документ.

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

Файлы, не являющиеся изображениями, остаются в виде ссылок на вложения, как в примере ниже.

Вложение: ссылка сохраняется в документе.



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

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

По центру

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

К заголовкам нельзя применять выравнивание.



Списки

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

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

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

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

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

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

  1. Первое

  2. Второе

  3. Третье

Список задач

На пустой строке наберите [ ] или [x] и нажмите пробел — получится список задач.

  • Эта задача сделана.

  • А эта пока нет.



Таблица

В ячейках можно размещать несколько абзацев и блоков, выделяя содержимое цветом фона. Попробуйте изменить текст и изображения в таблице ниже.

Материалы

Рабочие заметки

Изображение и описание в одной ячейке.

Свободная работа с абзацами

Нажмите Enter для нового абзаца или Shift+Enter для переноса строки внутри того же абзаца.

  • Изменить фон ячеек

  • Сделать отступ в списке клавишей Tab

  • Выбрать несколько ячеек и задать один цвет

Нажмите, чтобы увидеть советы

В ячейки также можно добавлять раскрывающиеся блоки, цитаты, код и видео.

При объединении ячеек их содержимое сохраняется по порядку.

Нажмите внутри ячейки, чтобы открыть выбор фона и инструменты строк и столбцов. Выберите несколько ячеек, чтобы одновременно изменить их цвет и выравнивание. Для размещения всей таблицы выберите «Выбрать таблицу», затем используйте инструменты выравнивания.

Таблицы с цветами или несколькими блоками сохраняются как HTML даже при сохранении в Markdown.

Цвета фона ячеек

Здесь собраны все поддерживаемые цвета фона. Нажмите ячейку, чтобы изменить цвет, или выберите «По умолчанию» в свойствах, чтобы убрать фон.

Жёлтый

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

Цене содержит только числовые значения, поэтому столбец сортируется по числам.

Остаток содержит и числа, и текст, поэтому столбец сортируется как строки.



Разделитель

Наберите --- и нажмите Enter — станет разделителем.




Картинка

Ширина берётся от 30% до 100%, а сама она встаёт слева, по центру или справа.



Плавающая область

Краткий обзор

Небольшая вспомогательная область для текста, изображений и таблиц.

Расположение

Справа

Максимальная ширина

40%

Используйте плавающую область для пояснений или небольших материалов рядом с основным текстом. В широком документе этот текст обтекает область сбоку. Нажмите внутри, чтобы редактировать текст и вставлять изображения или таблицы в том же редакторе.

Поместите курсор в область, чтобы открыть её свойства. Переключайтесь между левым и правым расположением и выбирайте максимальную ширину 20, 30, 40 или 50%, подходящую тексту. Вставленные таблицы и изображения также остаются внутри области.

Выделенные абзацы тоже можно заключить в область. Кнопка отмены области рядом с шириной оставляет содержимое в основном документе. В предпросмотре пунктирная рамка редактирования скрыта, а в узком документе и при печати всё идёт в один столбец.


YouTube



Загрузка

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

Эта демонстрация не отправляет файлы на настоящий сервер. Подключите загрузчик в приложении, чтобы сохранять их на сервере.

Если после открытия выбора изображения или запуска загрузки переместить только курсор, вставка произойдёт в исходной позиции. Редактирование документа во время ожидания отменяет вставку и показывает уведомление. Ход и результат загрузки, начатой в ячейке, остаются в ней.



Цитата

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

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



Код

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

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

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

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



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

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

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



Панель инструментов

Инструменты заголовков, форматирования, выравнивания, списков, вставки и документа собраны по группам. На компьютере они развёрнуты, а на мобильном доступны быстрые инструменты и полная палитра. Свойства выбранной таблицы или обтекаемой области появляются под панелью.


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

Быстро нажмите Shift дважды, чтобы выбирать инструменты с клавиатуры. На компьютере перемещайтесь по развёрнутой панели, на мобильном — по полной палитре. Tab меняет группу, стрелки выбирают инструмент, Enter запускает его.

В конце блока цитаты, кода или раскрывающегося блока быстро нажмите Enter дважды, чтобы выйти. Интервал двойного нажатия по умолчанию — 180ms.



Автоформат

Пример

Клавиша

Результат

#

Пробел

Заголовок

-

Пробел

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

1.

Пробел

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

[ ] · [x]

Пробел

Список задач

>

Пробел

Цитата

``` · ```ts

Пробел · Enter

Блок кода

---

Enter

Разделитель

https://…

Пробел · Enter

Ссылка



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

Функция

Результат

getHtml()

HTML

getJson()

JSON



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

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

Сервер (Node.js) также может прочитать сохранённый NABI TREE и создать HTML по тем же правилам.


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

  • Мобильный интерфейс — Располагает инструменты редактирования с учётом ширины экрана.

  • Адаптация к мобильной клавиатуре — Изменяет высоту области редактирования при открытии клавиатуры.

  • Гибкие размеры — Использует единицы rem в основной компоновке, чтобы учитывать увеличение и уменьшение масштаба.

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


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

  • Своё wing — если нужна новая функция, её можно создать и подключить самостоятельно.

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

  • Открытый код — исходный код опубликован на GitHub и разрабатывается совместно.


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

HTMLЗагрузка редактора…
JSONnabi-treeЗагрузка редактора…
Установка
npm install nabi-note
Код
import {
  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('ru')
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')