Перейти к содержимому
ФреймворкJavaScript

Blocks API: вставка, перенос и удаление блоков

Управление блоками в редакторе — создание, удаление, обновление и переупорядочивание контента.

Как получить экземпляр редактора

Методы ниже вызываются на редакторе, созданном через new Blok(). Они доступны после того, как разрешится editor.isReady.

TypeScript
// 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');

Методы

blocks.clear()

Promise<void>

Удалить все блоки из редактора.

Когда использовать

То же, что clear() верхнего уровня, но на модуле blocks. Используйте, когда уже есть ссылка на editor.blocks.

TypeScript
await editor.blocks.clear();
// All content removed; editor keeps one empty paragraph

blocks.render(data)

Promise<void>

Отрисовать переданные JSON-данные как блоки, заменив текущий документ. Устойчив к эху: когда входящий документ структурно равен текущему сохранённому содержимому (`time`/`version` игнорируются), вызов оказывается no-op с сохранением курсора — круговому пути `data → render → onSave → setState → data` не нужна дедупликация на стороне потребителя. Принимает нестрогий формат (`LooseOutputData`); редактор делает глубокую копию данных, поэтому переданный объект никогда не мутируется и не удерживается — замороженное состояние хранилища (Redux, Immer) можно передавать напрямую.

Когда использовать

Заменяет весь документ. Повторная передача собственного вывода редактора — no-op с сохранением курсора, дедупликация на стороне потребителя не нужна. Чтобы добавить блоки без очистки, используйте insert() или insertMany().

TypeScript
const data = {
  blocks: [
    { id: '1', type: 'paragraph', data: { text: 'Hello World' } },
    { id: '2', type: 'header', data: { text: 'Title', level: 1 } }
  ]
};
await editor.blocks.render(data);

blocks.renderFromHTML(data)

Promise<void>

Отрисовать HTML-строку как блоки, преобразовав её в формат блоков.

Когда использовать

Удобно для импорта старого HTML, но результат зависит от обработчиков вставки инструментов — JSON через render() сохраняет больше точности, когда источник под контролем.

TypeScript
const html = '<h1>Title</h1><p>Hello World</p>';
await editor.blocks.renderFromHTML(html);
// HTML is converted to appropriate blocks

blocks.importMarkdown(md, options?)

Promise<OutputData>

Преобразовать Markdown-строку в блоки и отрисовать их, ЗАМЕНИВ текущий документ, — внутри вызывается `blocks.render()`. Конвертер загружается лениво при первом вызове, а OutputData, которым резолвится промис, — это отрисованный документ. `options` — это `MarkdownImportConfig` (сопоставление инструментов, переключатель GFM, расширения micromark/mdast). Для аддитивной вставки используйте `markdownToBlocks()` из отдельного подпути `@bloklabs/core/markdown` вместе с `blocks.insertMany()`.

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
mdstringОбязательныйИсходная Markdown-строка.
optionsMarkdownImportConfigundefinedСопоставление инструментов, переключатель GFM и расширения micromark/mdast.
TypeScript
const data = await editor.blocks.importMarkdown('# Title\n\n- one\n- two');
// The whole document is replaced; data is the rendered OutputData

blocks.exportMarkdown()

Promise<string>

Сериализовать текущий документ в Markdown — исходящий близнец `importMarkdown`. Блоки читаются через Saver, поэтому вывод отражает сохранённый (провалидированный) документ, а не сырой DOM, и промис резолвится в '', когда сохранять нечего. Блоки, принадлежащие ячейке таблицы, сериализуются внутри pipe-таблицы, а не повторяются как отдельные строки. То, что Markdown выразить не может, деградирует: `colspan`/`rowspan` таблицы и столбцы заголовков отбрасываются, а таблица без строки заголовков получает пустую строку заголовка, поскольку GFM её требует.

TypeScript
const md = await editor.blocks.exportMarkdown();
// → '# Title\n\n- one\n- two'

blocks.delete(index?, setCaret?)

Promise<void>

Удалить блок по указанному индексу или текущий блок, если индекс не передан.

Когда использовать

Без индекса удаляет блок в фокусе. Удаление последнего оставшегося блока автоматически вставляет свежий пустой параграф, поэтому редактор никогда не остаётся без цели для курсора.

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
indexnumbercurrent block indexИндекс блока для удаления.
setCaretbooleantrueПеремещать ли курсор на уцелевший блок после удаления; передайте false, чтобы не отбирать курсор у пользователя при программном удалении.
TypeScript
// Delete current block
await editor.blocks.delete();

// Delete block at index 0
await editor.blocks.delete(0);

// Delete without moving the user's caret (programmatic deletion)
await editor.blocks.delete(0, false);

blocks.move(toIndex, fromIndex?)

void

Перемещает блок на новую позицию. Если fromIndex не передан, перемещает текущий блок.

Когда использовать

toIndex — это индекс после удаления: при перемещении вперёд вычтите 1, иначе блок встанет на позицию дальше нужной.

TypeScript
// Move current block to top
editor.blocks.move(0);

// Move block from index 2 to index 0
editor.blocks.move(0, 2);

blocks.getBlockByIndex(index)

BlockAPI | undefined

Получить объект BlockAPI для блока по указанному индексу.

Когда использовать

Возвращает undefined для индексов вне диапазона — вызов метода на undefined приведёт к ошибке, поэтому сначала проверьте результат.

TypeScript
const block = editor.blocks.getBlockByIndex(0);
if (block) {
  console.log(block.id, block.name);
}

blocks.getById(id)

BlockAPI | null

Получить объект BlockAPI для блока с указанным ID.

Когда использовать

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

TypeScript
const block = editor.blocks.getById('block-123');
if (block) {
  await editor.blocks.update(block.id, { text: 'New content' });
}

blocks.getCurrentBlockIndex()

number

Получить индекс блока, который сейчас в фокусе.

Когда использовать

Отражает блок с курсором. Возвращает -1, когда ничего не в фокусе, — проверяйте перед использованием как индекс.

TypeScript
const index = editor.blocks.getCurrentBlockIndex();
console.log('Current block index:', index);

blocks.getBlockIndex(blockId)

number | undefined

Получить индекс блока по его ID.

Когда использовать

Узнаёт текущую позицию известного блока; сочетайте с getById(), когда есть id, а нужна позиция.

TypeScript
const index = editor.blocks.getBlockIndex('block-123');
if (index !== undefined) {
  console.log('Block is at index:', index);
}

blocks.getBlockByElement(element)

BlockAPI | undefined

Получить объект BlockAPI для блока, который содержит переданный HTML-элемент.

Когда использовать

Мост от DOM-узла (например, цели клика) обратно к его блоку — полезно в собственных обработчиках.

TypeScript
document.addEventListener('click', (e) => {
  const block = editor.blocks.getBlockByElement(e.target);
  if (block) {
    console.log('Clicked on block:', block.id);
  }
});

blocks.scrollToBlock(id)

void

Прокрутить страницу к блоку, выбрать его, мигнуть подсветкой прибытия и объявить переход вспомогательным технологиям — публичный аналог прокрутки по хешу URL при загрузке. No-op, когда в документе нет блока с таким id. Адаптеры фреймворков, монтирующиеся в отсоединённый холдер (React/Vue/Angular), отрисовывают засеянный контент до того, как он попадёт на страницу, поэтому загрузочная прокрутка по хешу откладывается; @bloklabs/react выполняет её автоматически, как только холдер подключается, — для глубоких ссылок вызывайте этот метод сами после готовности редактора.

TypeScript
editor.blocks.scrollToBlock(nodeId);

blocks.getChildren(parentId)

BlockAPI[]

Получить все дочерние блоки родительского блока-контейнера.

Когда использовать

Возвращает прямых потомков блока-контейнера (колонки, тоггла или базы данных). Пустой массив, если потомков нет.

TypeScript
const children = editor.blocks.getChildren('parent-block-id');
children.forEach(child => {
  console.log('Child:', child.id);
});

blocks.setBlockParent(blockId, parentId)

void

Сменить родителя блока: обновляет `parentId` блока и `contentIds` родителя через единственную в ядре точку смены родителя. Передайте `null`, чтобы вернуть блок на корневой уровень. Неизвестный `blockId` — это no-op с записью предупреждения в лог; `parentId`, который сделал бы блок потомком самого себя, выбрасывает ошибку.

TypeScript
// Move a block into a container block
editor.blocks.setBlockParent('child-block-id', 'parent-block-id');

// Move it back to the root level
editor.blocks.setBlockParent('child-block-id', null);

blocks.insertInsideParent(parentId, insertIndex, childData?, toolName?)

BlockAPI

Вставить блок как дочерний для `parentId` атомарно: создание и назначение родителя объединяются в ОДИН шаг отмены, поэтому одно нажатие Cmd+Z убирает его полностью (предпочтительнее, чем `insert()` с последующей сменой родителя, где шага два). `insertIndex` — это ПЛОСКИЙ индекс документа, по которому должен появиться дочерний блок. `toolName` выбирает блочный инструмент дочернего блока и по умолчанию равен `config.defaultBlock`; инструмент, запрещённый внутри ячеек таблицы, понижается до блока по умолчанию, когда новый дочерний блок оказался бы внутри такой ячейки, а незарегистрированное имя выбрасывает ошибку до того, как что-либо будет записано. `childData` по умолчанию равен `{ text: '' }` для блока по умолчанию и `{}` — позволяя инструменту применить собственные значения по умолчанию — всякий раз, когда `toolName` называет другой инструмент.

TypeScript
const parentIndex = editor.blocks.getBlockIndex('parent-block-id') ?? 0;
const child = editor.blocks.insertInsideParent(
  'parent-block-id',
  parentIndex + 1,
  { text: 'Nested content' },
);

// A TYPED child in the same single undo entry — no insert-then-reparent
editor.blocks.insertInsideParent(
  'parent-block-id',
  parentIndex + 1,
  { text: 'Nested heading', level: 2 },
  'header',
);

blocks.getBlocksCount()

number

Получить общее количество блоков в редакторе.

Когда использовать

Считает все блоки, включая вложенные дочерние (колонки, тогглы, ячейки таблиц), а не только блоки верхнего уровня. Используйте для границ перед insert() или move() по индексу.

TypeScript
const count = editor.blocks.getBlocksCount();
console.log('Total blocks:', count);

blocks.insert(type?, data?, config?, index?, needToFocus?, replace?, id?, tunes?, origin?)

BlockAPI

Вставить новый блок с полным контролем над его свойствами и позицией.

Когда использовать

Все параметры необязательны — insert() даёт пустой параграф, либо передайте type/data/index для контроля. replace: true заменяет блок по индексу index.

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
typestringconfig.defaultBlockИмя инструмента для создания блока.
dataBlockToolData{}Начальные данные инструмента для нового блока.
configToolConfig{}Игнорируется — принимается только ради сохранения позиционной сигнатуры. Редактор связывает его с _config и никогда не читает, а опции вставки в block-manager не содержат поля config, поэтому созданный блок использует конфигурацию инструмента уровня редактора из config.tools. Передавайте undefined.
indexnumbercurrent block index + 1Позиция для вставки блока.
needToFocusbooleantrueПереводить ли фокус на вставленный блок. Передайте false, чтобы вставить блок, не перемещая курсор.
replacebooleanfalseЗаменить существующий блок по индексу вместо вставки рядом с ним.
idstringauto-generatedСобственный id для нового блока.
tunes{ [name: string]: BlockTuneData }undefinedНеобязательные данные тюнов, применяемые при создании, с ключами по имени тюна.
originBlockOrigin'api'Почему блок создаётся. Передаётся в конструктор инструмента как origin — именно так контейнерный инструмент отличает настоящее создание (можно засеять детей по умолчанию) от повторной материализации: загрузки документа, воспроизведения undo/redo или вставки (засевать нельзя). Передавайте 'user', когда вставка идёт из вашего собственного интерфейса — своей панели, тулбокса или горячей клавиши, — чтобы встроенные контейнеры Blok (column_list, column) и ваши собственные вели себя так же, как при вставке из штатного меню. Для программных вставок и перезагрузки данных параметр можно не передавать: значение по умолчанию 'api' тоже считается созданием, но никогда не выдаётся за жест пользователя.

Ошибки

  • Не указан type, и в конфигурации не задан defaultBlock.

    Could not insert Block. Tool name is not specified.

    Передайте явный type или задайте defaultBlock в конфигурации редактора.

  • Определённое имя инструмента не зарегистрировано в редакторе.

    Could not compose Block. Tool «<type>» not found.

    Зарегистрируйте инструмент в конфигурации tools редактора перед вставкой блока этого типа.

  • Передан replace: true, но по index нет блока.

    Could not replace Block at index <index>. Block not found.

    Проверьте index через blocks.getBlocksCount() перед вызовом с replace: true.

TypeScript
// Insert after the current block with the default type
const block = editor.blocks.insert();
// → BlockAPI { id: 'kP3xQ...', name: 'paragraph', ... }

// Insert paragraph with data at index 0
const block = editor.blocks.insert('paragraph', { text: 'Hello' }, undefined, 0);

// Insert with custom ID
const block = editor.blocks.insert('header', { text: 'Title' }, undefined, undefined, undefined, undefined, 'custom-id');

// From your own slash menu / toolbar: declare the user gesture so a container
// tool seeds its default children (see BlockOrigin on the tool contract)
const block = editor.blocks.insert('column_list', undefined, undefined, index, undefined, undefined, undefined, undefined, 'user');

blocks.insertMany(blocks, index?)

BlockAPI[]

Вставить несколько блоков за раз. Когда индекс не передан, блоки добавляются в конец документа.

Когда использовать

Массовая вставка за одну операцию (и один шаг отмены) — гораздо дешевле цикла из insert() для больших вставок или импорта.

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
blocksOutputBlockData[] | LooseOutputBlockData[]ОбязательныйБлоки для вставки. Принимаются нестрогие блоки — null в data становится {}, null/пустой id заменяется сгенерированным.
indexnumberend of documentПозиция для вставки. Когда не передана, по умолчанию блоки добавляются в конец документа.

Ошибки

  • Переданный index отрицательный.

    Index should be greater than or equal to 0

    Передайте index не меньше 0 либо не передавайте его, чтобы добавить в конец.

TypeScript
const blocksToInsert = [
  { id: '1', type: 'paragraph', data: { text: 'First' } },
  { id: '2', type: 'paragraph', data: { text: 'Second' } }
];
const inserted = editor.blocks.insertMany(blocksToInsert, 0);
console.log('Inserted:', inserted.length, 'blocks');

blocks.composeBlockData(toolName)

Promise<BlockToolData>

Создать пустые данные блока для указанного типа инструмента.

Когда использовать

Создаёт данные инструмента по умолчанию, ничего не вставляя — удобно, когда нужен корректный пустой payload для insert() или update().

Ошибки

  • toolName не является зарегистрированным инструментом.

    Block Tool with type "<toolName>" not found

    Сначала зарегистрируйте инструмент в конфигурации tools редактора.

TypeScript
const emptyData = await editor.blocks.composeBlockData('paragraph');
// Returns: { text: '' } or appropriate empty state for the tool

blocks.update(id, data?, tunes?)

Promise<BlockAPI>

Обновить данные и/или тюны блока. Когда инструмент блока реализует `setData(newData)` в своём прототипе — так делает каждый React/Vue/Angular-блок, а также встроенные инструменты заголовка, списка, кода, тоггла и таблицы, — обновление данных применяется НА МЕСТЕ: тот же экземпляр блока, тот же DOM-холдер, тот же смонтированный компонент продолжают жить, поэтому эфемерное состояние инструмента, принятые дочерние блоки и курсор сохраняются. Именно поэтому `update()` безопасно вызывать на каждое нажатие клавиши (переименование карточки, пока пользователь печатает). Инструменты без `setData`, инструмент, чей `setData` возвращает `false` (ему нужна другая форма DOM), и любой вызов, передающий `tunes`, откатываются к пересборке блока: новый экземпляр инструмента заменяет старый, а старый уничтожается.

Когда использовать

Изменяет data/tunes на месте, сохраняя id и тип блока. Чтобы сменить тип, используйте convert().

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
idstringОбязательныйId блока для обновления.
dataPartial<BlockToolData>undefinedЧастичные данные, объединяемые с существующими данными блока.
tunesRecord<string, BlockTuneData>undefinedДанные тюнов, объединяемые с существующими тюнами блока.

Ошибки

  • Блок с указанным id не существует.

    Block with id "<id>" not found

    Проверьте id через blocks.getById() перед вызовом update().

TypeScript
// Update block data
const block = await editor.blocks.update('block-123', { text: 'New text' });

// Update with tunes
const block = await editor.blocks.update('block-123', undefined, { alignment: 'center' });

// Guard against an id that no longer exists (e.g. the block was deleted
// concurrently) instead of letting the rejection surface unhandled
try {
  await editor.blocks.update(staleId, { text: 'New text' });
} catch {
  console.warn('Block was already removed, skipping update');
}

blocks.convert(id, newType, dataOverrides?)

Promise<BlockAPI>

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

Когда использовать

И исходный, и целевой инструменты должны объявлять conversionConfig, иначе вызов выбросит ошибку. По возможности сохраняет текст.

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
idstringОбязательныйId блока для преобразования. Его инструмент должен объявлять conversionConfig.export.
newTypestringОбязательныйИмя зарегистрированного инструмента для преобразования. Его инструмент должен объявлять conversionConfig.import.
dataOverridesBlockToolDataundefinedПоля данных для перезаписи в результирующем блоке после преобразования.

Ошибки

  • Блок с указанным id не существует.

    Block with id "<id>" not found

    Проверьте id через blocks.getById() перед вызовом convert().

  • newType не является зарегистрированным инструментом.

    Block Tool with type "<newType>" not found

    Зарегистрируйте целевой инструмент в конфигурации tools редактора.

  • У исходного инструмента нет conversionConfig.export, у целевого нет conversionConfig.import, либо нет ни того, ни другого.

    Conversion from "<sourceType>" to "<newType>" is not possible. <ToolName(s)> tool(s) should provide a "conversionConfig"

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

TypeScript
// Convert paragraph to header
const headerBlock = await editor.blocks.convert('block-123', 'header', { level: 2 });

// Convert with data overrides
const headerBlock = await editor.blocks.convert('block-123', 'header', { text: 'New Title', level: 1 });

// Not every pair of tools supports conversion — guard it rather than
// assuming the target type is always convertible
try {
  await editor.blocks.convert('block-123', 'table');
} catch (error) {
  console.warn('Conversion not supported between these tools:', error);
}

blocks.splitBlock(currentBlockId, currentBlockData, newBlockType, newBlockData, insertIndex)

BlockAPI

Атомарно разделить блок, обновив текущий блок и вставив новый. Обе операции объединяются в один шаг отмены.

Когда использовать

Используйте для разбиения с учётом курсора (например, Enter посреди текста); update и insert образуют один шаг отмены.

TypeScript
// Split a paragraph at cursor position
const newBlock = editor.blocks.splitBlock(
  'current-block-id',
  { text: 'First part' },
  'paragraph',
  { text: 'Second part' },
  1
);

blocks.stopBlockMutationWatching(index)

void

Остановить наблюдение за мутациями блока по указанному индексу. Используйте это, чтобы предотвратить ложные события block-changed при операциях замены блока.

Когда использовать

Нишевое: вызывайте перед программной заменой DOM блока, чтобы подавить ложные события block-changed; следующий рендер включит наблюдение снова.

TypeScript
// Replace a block without triggering change events
editor.blocks.stopBlockMutationWatching(0);
// Perform block replacement...
// Mutation observer will not fire for this block

blocks.startBlockMutationWatching(blockId)

void

Снова включить наблюдение за мутациями блока, ранее заглушённого через `stopBlockMutationWatching`. Метод принимает **id**, а не индекс, потому что вставки и замены между двумя вызовами сдвигают индексы; несуществующий id молча пропускается — блок, заменённый на месте, был создан с собственным наблюдателем.

TypeScript
const blockId = editor.blocks.getBlockByIndex(0)?.id;
editor.blocks.stopBlockMutationWatching(0);
// Perform block replacement...
if (blockId) {
  editor.blocks.startBlockMutationWatching(blockId);
}

blocks.transact(fn)

void

Объединить каждую операцию с блоками, выполненную внутри `fn`, в один шаг отмены. `fn` должна быть СИНХРОННОЙ — операции, попадающие туда после await, в группу уже не входят. Используйте это, чтобы структурные правки нельзя было отменить частично.

TypeScript
editor.blocks.transact(() => {
  editor.blocks.insert('paragraph', { text: 'One' });
  editor.blocks.insert('paragraph', { text: 'Two' });
});
// A single Cmd+Z removes both blocks

blocks.beginTransaction()

void

Открыть группу отмены, которая остаётся открытой через асинхронные границы. Каждая операция с блоками до `endTransaction()` попадает в один шаг отмены. Используйте это для жестов указателя, непрерывно меняющих документ (перетаскивание угла таблицы для добавления строк), где `transact()` не помогает, потому что оборачивает только синхронную функцию. Каждый вызов должен быть сопряжён с `endTransaction()`.

TypeScript
editor.blocks.beginTransaction();
// ... continuous mutations across async boundaries ...
editor.blocks.endTransaction();

blocks.endTransaction()

void

Закрыть группу отмены, открытую через `beginTransaction()`.

TypeScript
editor.blocks.beginTransaction();
// ... continuous mutations across async boundaries ...
editor.blocks.endTransaction();

blocks.transactWithoutCapture(fn)

void

Выполнить операции с блоками, ничего не записывая в историю отмен. Используйте это для автопочинки и нормализации (например, чтобы в пустой ячейке всегда был блок), через которые Cmd+Z никогда не должен проходить.

TypeScript
editor.blocks.transactWithoutCapture(() => {
  editor.blocks.insert('paragraph', {}, undefined, 0);
});
// Nothing was added to the undo history

blocks.setPointerDragActive(active)

void

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

TypeScript
editor.blocks.setPointerDragActive(true);
// ... run the drag gesture ...
editor.blocks.setPointerDragActive(false);

Свойства

СвойствоТипОписание
isSyncingFromYjsbooleanГеттер только для чтения — true, пока идёт операция синхронизации Yjs (отмена/повтор). Инструменты читают его, чтобы пропустить очистку, которая конфликтовала бы с состоянием отмены. Обратите внимание: это СВОЙСТВО на `editor.blocks`, в отличие от метода `isSyncingFromYjs()` React-хука.
isPointerDragActivebooleanГеттер только для чтения — true, пока активно перетаскивание указателем. Адаптеры фреймворков читают его, чтобы отложить программный `dispatchChange` посреди перетаскивания (ядро молча отбрасывает такое изменение) и отправить его повторно, когда перетаскивание закончится.