Класс Blok: создание и уничтожение редактора
Основной класс редактора, который инициализирует экземпляр редактора Blok и управляет им. Любое пространство имён, доступное инструменту через `api.*`, доступно и на самом экземпляре как `editor.*` — свойства ниже описывают ту же поверхность плюс пространства имён `width`, `placeholder`, `tokens` и `i18n`, которые класс объявляет сам.
Как получить экземпляр редактора
Методы ниже вызываются на редакторе, созданном через new Blok(). Они доступны после того, как разрешится editor.isReady.
// You already hold the instance returned by the constructor.
const editor = new Blok({ holder: 'editor' });
await editor.isReady;
// Call any API method on it.
editor.caret.setToLastBlock('end');Методы
save()
Promise<OutputData>Извлекает содержимое редактора в виде JSON-данных. Основной метод для сохранения контента.
Когда использовать
Вызывайте после await editor.isReady. Возвращённый JSON — ваш источник истины: сохраните его и передайте обратно в render().
Ошибки
Редактор находится в режиме только для чтения в момент вызова save().
Blok's content can not be saved in read-only mode
Вызвать
readOnly.set(false)перед сохранением либо сохранить данные из последнего вызоваonSave/ собственного зеркального состояния.
// Save editor content
const data = await editor.save();
console.log(data.blocks); // Array of block datarender(data)
Promise<void>Отображает содержимое редактора из ранее сохранённых JSON-данных. Принимает нестрогий формат (`LooseOutputData`) — значения `null` для `data`, `id` или `time` блока из backend-DTO нормализуются на границе.
Когда использовать
Загружает сохранённый контент и заменяет текущий документ. Чтобы добавить, а не заменить, используйте blocks.insertMany().
// Load saved content
const savedData = {
blocks: [
{ id: '1', type: 'paragraph', data: { text: 'Hello' } }
]
};
await editor.render(savedData);focus(atEnd?)
booleanУстанавливает фокус на редактор. Опционально позиционирует курсор в конце содержимого.
Когда использовать
Передайте true, чтобы поставить курсор в самый конец. Для конкретного блока или смещения используйте API caret.
// Focus at start
editor.focus();
// Focus at end
editor.focus(true);clear()
Promise<void>Удаляет всё содержимое редактора. Остаётся один пустой блок инструмента по умолчанию, поэтому редактор никогда не остаётся без блоков — при этом последующий save() всё равно вернёт `blocks: []`, так как пустой блок по умолчанию не проходит валидацию и отбрасывается из результата.
Когда использовать
Удаляет все блоки и оставляет один пустой параграф. Действие отменяемо — в отличие от destroy(), уничтожающего экземпляр.
// Clear all content
await editor.clear();destroy()
voidУничтожает экземпляр редактора и удаляет все DOM-элементы и обработчики событий.
Когда использовать
Вызывайте из хука размонтирования вашего фреймворка, чтобы не утекли слушатели. После этого экземпляр непригоден — создайте новый Blok.
// Clean up on component unmount
editor.destroy();handlers.set(handlers)
voidУстанавливает, заменяет или удаляет живые колбэки редактора — `onChange`, `onSave`, `onEnter`, `onSubmit`, `onBeforeRender`, `onAfterRender` — на месте, поэтому курсор, выделение, прокрутка и история отмен сохраняются. Затрагиваются только переданные ключи; ключ со значением `undefined` СНИМАЕТ обработчик. Это важно, потому что само наличие колбэка и есть семантика: `onSubmit` превращает Enter в «сериализовать и отправить» вместо разбиения блока, а `onSave` включает конвейер отслеживания изменений. Используйте метод, чтобы сделать колбэк реактивным без пересоздания редактора — адаптеры React, Vue и Angular вызывают этот сеттер за вас, когда prop, слушатель или колбэк из `[config]` появляется или исчезает.
Когда использовать
Затрагиваются только переданные ключи — ключ со значением undefined снимает обработчик, именно так режим «Enter отправляет» выключается обратно.
Параметры
| Параметр | Тип | Обязательный | По умолчанию | Описание |
|---|---|---|---|---|
handlers | LiveHandlers | Обязательный | — | Частичная карта живых колбэков. Пропущенные ключи остаются как есть; ключ со значением undefined снимает обработчик. |
// "Enter sends" while composing, default Enter while editing a draft
editor.handlers.set({
onSubmit: sendsOnEnter ? (data) => send(data) : undefined,
});
// Start mirroring content into your store, later stop again
editor.handlers.set({ onSave: (data) => store.set(data) });
editor.handlers.set({ onSave: undefined });whenAllReady(options?)
Promise<void>Статический метод — резолвится, когда каждый экземпляр Blok в заданной области завершил загрузку (его `isReady` завершился; отклонения тоже считаются завершением). Коллективный сигнал готовности для страниц с несколькими экземплярами — заменяет ручную агрегацию колбэков `onReady` по каждому экземпляру. Передайте `within` (Element), чтобы учитывать только экземпляры внутри вашего поддерева: посторонний редактор на странице больше не сможет держать проверку закрытой. Передайте `settleOn: 'rendered'`, чтобы готовность означала не завершение конструктора, а наличие контента в DOM — это покрывает и повторные рендеры через `render(data)`. Пустая область резолвится сразу. Экземпляры, созданные пока промис в ожидании, продлевают ожидание; созданные после его резолва не учитываются — вызовите метод снова или используйте `subscribeReady()` для живого сигнала.
Когда использовать
Вызывайте как Blok.whenAllReady() на классе, а не на экземпляре. Экземпляр, который ещё загружается и чья обёртка не добавлена в документ, учитывается в любой области — лишнее ожидание безопасно, недостаточное является ошибкой.
// A comments list: N read-only bodies + a composer.
// Wait only for the editors inside this list.
await Blok.whenAllReady({
within: listElement,
settleOn: 'rendered',
});
composer.focus();readyState(options?)
{ total: number; pending: number; ready: boolean }Статический метод — синхронный снимок готовности для области: сколько экземпляров попадает в `within`, сколько из них ещё не готовы на запрошенном уровне `settleOn` и готова ли область целиком. Пустая область возвращает `ready: true`, поэтому отдельная проверка «ждать нечего» не нужна.
Когда использовать
Достаточно дёшев, чтобы вызывать его на каждое уведомление из subscribeReady(): метод обходит зарегистрированные экземпляры и проверяет вложенность в DOM.
const { pending, ready } = Blok.readyState({ within: listElement });
if (!ready) {
showSkeleton(pending);
}subscribeReady(listener)
() => voidСтатический метод — подписка на изменения готовности всех экземпляров (создание, загрузка, смена состояния рендера, уничтожение); возвращает функцию отписки. Слушатель вызывается без аргументов: перечитайте `Blok.readyState(scope)`, когда он сработает. Подходит для `useSyncExternalStore` и других адаптеров хранилищ — живой сигнал вместо одноразовой защёлки.
Когда использовать
В фреймворках лучше использовать обёртки адаптеров: useBlokReady() в @bloklabs/react и @bloklabs/vue, injectBlokReady() в @bloklabs/angular — они оборачивают эту подписку и поиск области видимости.
const unsubscribe = Blok.subscribeReady(() => {
setReady(Blok.readyState({ within: listElement }).ready);
});
// later
unsubscribe();createSelector(attr, value?)
stringИменованный экспорт корня пакета (не член класса Blok) — строит CSS-селектор по значению `DATA_ATTR`. Без `value` получается селектор по наличию атрибута, с ним — селектор по равенству значения. Используйте его вместе с `Blok.DATA_ATTR` вместо совпадения по внутренним именам классов Blok, которые не входят в публичную поверхность.
import { DATA_ATTR, createSelector } from '@bloklabs/core';
createSelector(DATA_ATTR.element); // '[data-blok-element]'
document.querySelectorAll(createSelector(DATA_ATTR.selected, true));icons
stringИменованные экспорты подпути `@bloklabs/core/icons` (не члены класса Blok) — собственные глифы Blok в виде SVG-строк, по одной константе `Icon*` на глиф (`IconBold`, `IconPlus`, `IconTrash`, `IconWarning`, …). Поскольку это обычные строки, они подставляются прямо туда, где инструмент обязан отдать разметку: в иконку `static get toolbox()` инструмента и в элементы, которые возвращает `renderSettings()`. Подпуть поставляется со сгенерированным самодостаточным файлом деклараций (`types/icons.d.ts`), где перечислены все константы, поэтому справочник по полному набору — автодополнение в редакторе кода.
import { IconBold, IconPlus } from '@bloklabs/core/icons';
class Callout {
static get toolbox() {
return { title: 'Callout', icon: IconPlus };
}
renderSettings() {
return [{ icon: IconBold, title: 'Bold text', onActivate: () => this.toggleBold() }];
}
}Свойства
| Свойство | Тип | Описание |
|---|---|---|
DATA_ATTR | Record<DataAttrKey, DataAttrValue> | Именованный экспорт корня пакета (`import { DATA_ATTR } from '@bloklabs/core'`), а не свойство экземпляра редактора — стабильные имена атрибутов `data-blok-*`, которые Blok пишет в своём DOM. Это поддерживаемый способ запрашивать DOM редактора и писать тесты на стороне хоста вместо совпадения по внутренним именам классов. Рядом с ним экспортируются типы `DataAttrKey` / `DataAttrValue` и хелпер `createSelector()`. |
BLOK_FONT_SIZE_TOKENS | BlokFontSizeTokens | Именованный экспорт корня пакета (`import { BLOK_FONT_SIZE_TOKENS } from '@bloklabs/core'`), а не свойство экземпляра редактора — CSS-переменная, которую пишет каждый сценарий `style.fontSize`, в карте ровно той же формы, что и сама конфигурация (`BLOK_FONT_SIZE_TOKENS.paragraph`, `.heading[1]`, `.list.checklist`, `.bookmark.link`, …). Используйте её везде, где типографика задаётся не конфигурацией конструктора, а другим каналом — CSS-правилом для отдельной области или `editor.tokens.set({ [BLOK_FONT_SIZE_TOKENS.paragraph]: '18px' })` в рантайме, — чтобы имена CSS-переменных не приходилось копировать руками, а переименование становилось ошибкой компиляции, а не молча проходило впустую. |
version | string | Именованный экспорт корня пакета (`import { version } from '@bloklabs/core'`), а не свойство экземпляра редактора — версия работающего редактора, то же значение, что попадает в `OutputData.version`. |
PendingBlok | { isReady; isRendered; destroy(); theme; width; placeholder; tokens; i18n } | Тип, экспортируемый из корня пакета (`import type { PendingBlok } from '@bloklabs/core'`), а не свойство экземпляра редактора — поверхность, которая гарантированно существует синхронно между `new Blok(config)` и разрешением `isReady`. Модульные API Blok (`blocks`, `caret`, `history`, `readOnly`, …) создаются асинхронно, поэтому чтение их раньше вернёт `undefined`. `PendingBlok` объявляет только восемь перечисленных здесь членов, что превращает это окно в ошибку компиляции вместо `undefined` в рантайме: `const pending: PendingBlok = new Blok(config); const editor = await pending.isReady;`. |
isReady | Promise<Blok> | Promise, который разрешается готовым экземпляром редактора. Пространства имён API ниже (`blocks`, `caret`, `history`, `readOnly`, …) создаются асинхронно и равны `undefined`, пока он не разрешится — типизируйте ссылку, которой владеете в этот момент, как `PendingBlok`. |
isRendered | boolean | Синхронный флаг готовности рендера — true, когда текущая партия рендера попала в DOM (отражает атрибут `data-blok-rendered` на обёртке); false до первого рендера и во время повторного. Дополняет асинхронные `isReady`/`onReady`: не нужны await или колбэк, состояние монтирования можно опрашивать синхронно. |
blocks | Blocks | Модуль API для работы с блоками |
caret | Caret | Модуль API для управления курсором |
history | History | Модуль API истории |
saver | Saver | Модуль API сохранения |
toolbar | Toolbar | Модуль API панели инструментов |
inlineToolbar | InlineToolbar | Модуль API строчной панели |
tools | Tools | Модуль API инструментов |
uploader | Uploader | Модуль API загрузчика — загрузка ассетов с маршрутизацией по виду ассета |
events | Events | Модуль API событий |
listeners | Listeners | Модуль API слушателей |
notifier | Notifier | Модуль API уведомлений |
sanitizer | Sanitizer | Модуль API очистки |
selection | Selection | Модуль API выделения |
marks | Marks | Модуль API меток — range-aware операции со строчными метками |
styles | Styles | Модуль API стилей |
tooltip | Tooltip | Модуль API подсказок |
readOnly | ReadOnly | Модуль API режима только для чтения |
ui | Ui | Модуль API интерфейса |
theme | Theme | Модуль API темы |
width | Width | Модуль API ширины |
placeholder | Placeholder | Модуль API плейсхолдера |
tokens | Tokens | Модуль API токенов темы в рантайме |
i18n | EditorI18n | Модуль API i18n — всё, что инструмент получает через `api.i18n`, расширенное методом `update()` |
config | Readonly<Pick<BlokConfig, 'linkPaste' | 'link'>> | Доступное только для чтения представление отдельных настроек редактора: опции `link` и `linkPaste`, с которыми был создан этот экземпляр. Собственный строчный инструмент или инструмент ссылок читает политику ссылок хоста отсюда (как `api.config`), а не выводит её заново. |
rectangleSelection | { cancelActiveSelection(): void; isRectActivated(): boolean; clearSelection(): void; startSelection(pageX: number, pageY: number, shiftKey?: boolean): void; endSelection(): void } | Управление выделением перетаскиванием (рамкой); доступно также внутри инструмента как `api.rectangleSelection`. `startSelection(pageX, pageY, shiftKey?)` начинает рамку от координат страницы; `endSelection()` сбрасывает состояние перетаскивания и прячет оверлей; `isRectActivated()` сообщает, активна ли рамка сейчас; `clearSelection()` снимает флаг активности; `cancelActiveSelection()` прерывает начатое выделение (clear + end) — это то, что вызывает другая система выделения, например выделение ячеек таблицы, когда она забирает приоритет. |