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. и нажмите пробел — получится нумерованный список.
Первое
Второе
Третье
Список задач
На пустой строке наберите [ ] или [x] и нажмите пробел — получится список задач.
Эта задача сделана.
А эта пока нет.
Таблица
В ячейках можно размещать несколько абзацев и блоков, выделяя содержимое цветом фона. Попробуйте изменить текст и изображения в таблице ниже.
Материалы | Рабочие заметки |
|---|---|
Изображение и описание в одной ячейке. | Свободная работа с абзацамиНажмите Enter для нового абзаца или Shift+Enter для переноса строки внутри того же абзаца. |
| Нажмите, чтобы увидеть советыВ ячейки также можно добавлять раскрывающиеся блоки, цитаты, код и видео. При объединении ячеек их содержимое сохраняется по порядку. |
Нажмите внутри ячейки, чтобы открыть выбор фона и инструменты строк и столбцов. Выберите несколько ячеек, чтобы одновременно изменить их цвет и выравнивание. Для размещения всей таблицы выберите «Выбрать таблицу», затем используйте инструменты выравнивания.
Таблицы с цветами или несколькими блоками сохраняются как 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
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('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')