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

Как создать собственный блочный инструмент

Создайте блок-инструмент с нуля: блок-выноску, которая отрисовывается, редактируется и сохраняется как любой встроенный блок.

Обновлено 8 сент. 2026 г.Редактировать на GitHub

Каждый встроенный блок реализует интерфейс BlockTool, и вы можете добавить свой. Здесь вы создадите блок Callout. Это редактируемая выноска: она сохраняет свой текст и загружает его обратно, как любой другой блок.

1

Напишите класс инструмента

Блок-инструменту нужны три вещи. Статический геттер toolbox добавляет его в меню /. Метод render() возвращает элемент блока. Метод save() возвращает данные для сохранения.

TypeScript
// callout-tool.ts
export class CalloutTool {
  private data: { text: string };

  // Shows the tool in the "/" menu. `section` groups the entry under a
  // labeled heading ('basic' | 'media' | 'database' | 'advanced'); omit it
  // to list the entry in the trailing unlabeled group.
  static get toolbox() {
    return { title: 'Callout', icon: '💡', section: 'basic' };
  }

  constructor({ data }: { data: { text?: string } }) {
    this.data = { text: data.text ?? '' };
  }

  // Return the element Blok mounts for this block.
  render() {
    const box = document.createElement('div');
    box.classList.add('callout');
    box.contentEditable = 'true';
    box.textContent = this.data.text;
    return box;
  }

  // Return the data Blok stores when the editor is saved.
  save(block: HTMLElement) {
    return { text: block.textContent ?? '' };
  }
}
2

Зарегистрируйте его в редакторе

Передайте класс в конфигурацию tools. Ключ, который вы зададите, станет значением type блока в сохранённых данных.

TypeScript
import { Blok } from '@bloklabs/core';
import { Paragraph } from '@bloklabs/core/tools';
import { CalloutTool } from './callout-tool';

const editor = new Blok({
  holder: 'editor',
  tools: {
    paragraph: Paragraph, // the default block every empty editor starts with
    callout: CalloutTool, // the key becomes the block's `type`
  },
});
3

Используйте и сохраните

Откройте меню /, выберите Callout, введите текст в выноску и вызовите save(). Ваш блок появится в результате рядом со встроенными.

TypeScript
const data = await editor.save();

// Your block round-trips exactly like a built-in one:
// {
//   id: 'x9k2f1',
//   type: 'callout',
//   data: { text: 'Heads up — this is a callout.' },
// }

Что дальше

Добавьте метод validate(), чтобы отбрасывать пустые блоки, меню настроек через renderSettings() или обработку вставки через onPaste. Интерфейс BlockTool покрывает весь жизненный цикл: rendered(), updated(), moved() и removed().

TypeScript
// callout-tool.ts (extended)
export class CalloutTool {
  // ...constructor unchanged; render() now keeps its element as `this.box`.

  // Opt into read-only mode. Without it, creating the editor with
  // `readOnly: true` — or calling `readOnly.toggle(true)` — throws
  // "To enable read-only mode all connected tools should support it".
  static isReadOnlySupported = true;

  save(block: HTMLElement) {
    return { text: block.textContent ?? '' };
  }

  // Drop empty callouts when the editor saves.
  validate(savedData: { text: string }) {
    return savedData.text.trim().length > 0;
  }

  // Optional: flip the DOM in place. If any registered tool lacks
  // setReadOnly(), every toggle re-runs a full save/clear/render cycle.
  setReadOnly(state: boolean) {
    this.box.contentEditable = String(!state);
  }
}

// text-color-tune.ts — a block tune adds a control to the settings menu.
export class TextColorTune {
  static isTune = true;

  render() {
    return { title: 'Text color', icon: '🎨', onActivate: () => {/* recolor */} };
  }
}
TypeScript
import { Blok } from '@bloklabs/core';
import { Paragraph } from '@bloklabs/core/tools';
import { CalloutTool } from './callout-tool';
import { TextColorTune } from './text-color-tune';

const editor = new Blok({
  holder: 'editor',
  tools: {
    paragraph: Paragraph, // the default block every empty editor starts with
    callout: { class: CalloutTool, tunes: ['textColor'] },
    // Register the tune as a tool so a block can list it by name.
    textColor: TextColorTune,
  },
});

validate() выполняется до того, как разрешится save(), поэтому пустая выноска не попадёт в результат.

Режим только для чтения подключается явно. Без static isReadOnlySupported = true создание редактора с readOnly: true или вызов readOnly.toggle(true) выбросит ошибку "To enable read-only mode all connected tools should support it". Добавьте сверху setReadOnly(state), и блок сможет переключаться на месте. Если хотя бы у одного зарегистрированного инструмента его нет, каждое переключение прогоняет полный цикл save/clear/render.

Тюны подключаются отдельно для каждого инструмента. Зарегистрируйте класс тюна как инструмент: это класс со static isTune = true. Затем укажите его имя в tunes: [...], и Blok добавит его в меню настроек блока.

Полный контракт инструмента смотрите в Tools API и загляните в BlockData за точной формой данных, которые возвращает ваш `save()`.