Как создать собственный блочный инструмент
Создайте блок-инструмент с нуля: блок-выноску, которая отрисовывается, редактируется и сохраняется как любой встроенный блок.
Каждый встроенный блок реализует интерфейс BlockTool, и вы можете добавить свой. Здесь вы создадите блок Callout. Это редактируемая выноска: она сохраняет свой текст и загружает его обратно, как любой другой блок.
Напишите класс инструмента
Блок-инструменту нужны три вещи. Статический геттер toolbox добавляет его в меню /. Метод render() возвращает элемент блока. Метод save() возвращает данные для сохранения.
// 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 ?? '' };
}
}Зарегистрируйте его в редакторе
Передайте класс в конфигурацию tools. Ключ, который вы зададите, станет значением type блока в сохранённых данных.
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`
},
});Используйте и сохраните
Откройте меню /, выберите Callout, введите текст в выноску и вызовите save(). Ваш блок появится в результате рядом со встроенными.
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().
// 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 */} };
}
}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()`.