Skip to content
ФреймворкJavaScript

Marks API: строчное форматирование по всему выделению

Range-aware операции со строчными метками для инлайн-инструментов форматирования. Там, где selection.findParentTag смотрит только на якорный узел выделения, api.marks работает со ВСЕМ диапазоном: has отвечает «покрыт ли каждый текстовый узел выделения», apply и remove разрезают частично покрытые обёртки на границах диапазона, обновляют полностью покрывающие обёртки на месте и восстанавливают выделение, а apply и remove расширяют диапазон на замыкающий пробел, который браузеры исключают из выделения по двойному клику. Метка описывается декларативно через MarkSpec (tag, aliasTags, className, attributes, style); aliasTags позволяет унаследованным вариантам тега (например, <b> рядом со <strong>, <em> рядом с <i>) считаться ТОЙ ЖЕ меткой, при этом новые обёртки всегда используют канонический тег. Строковые значения статичны и входят в идентичность метки; значения-функции вычисляются из state, переданного в apply/toggle, и сознательно ИСКЛЮЧЕНЫ из идентичности — поэтому палитра цветов это ОДНА метка, обновляющаяся на месте, а не N взаимно отменяющих друг друга. Две спецификации с одинаковыми tag, classNames и статичными атрибутами принадлежат одной семье и компонуются на одном элементе — например, цвет текста и цвет фона на одном <mark>. Каждый метод по умолчанию берёт первый диапазон живого выделения. Экспорт ядра markSanitizerConfig(spec) выводит правило санитайзера для метки: тег попадает в allowlist, необъявленные style-свойства и классы вычищаются, объявленные атрибуты сохраняются, а значения-функции учитываются по имени свойства — динамические значения не теряются при сохранении. createReactInlineTool в React-адаптере применяет тот же вывод автоматически, когда инструмент объявляет спецификацию mark.

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

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

Методы ниже вызываются на редакторе, созданном через 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');

Методы

marks.has(spec, range?)

boolean

Whether every text node in the range is inside a wrapper matching the spec — whitespace-only text nodes are ignored, and at a collapsed caret the caret's ancestors are checked. Unlike selection.findParentTag (anchor node only), a selection that only partially carries the mark reports false.

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

Range-aware замена проверки активности через findParentTag: выделение, лишь частично несущее метку, даёт false — иконка в тулбаре не соврёт.

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
specMarkSpecОбязательныйDeclarative mark description: tag plus optional className, attributes, and style.
rangeRangecurrent selectionRange to check; defaults to the live selection's first range.
TypeScript
const highlight = { tag: 'span', className: 'my-highlight' };

// True only when the WHOLE selection is covered — a half-highlighted
// selection reports false, so a toolbar icon cannot lie
const active = editor.marks.has(highlight);

marks.find(spec, from?)

HTMLElement | null

Nearest ancestor element matching the spec, starting from the given node (or the current selection's start container). Matching respects the full spec — tag, classNames, and static attribute/style values — not just the tag name.

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

Поиск предка по спецификации — как findParentTag, но с учётом classNames и статичных атрибутов/стилей, а не только тега.

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
specMarkSpecОбязательныйDeclarative mark description: tag plus optional className, attributes, and style.
fromNodeselection start containerNode to start the upward search from; defaults to the current selection's start container.
TypeScript
const wrapper = editor.marks.find({ tag: 'mark' });

if (wrapper) {
  editor.selection.expandToTag(wrapper);
}

marks.read(spec, range?)

MarkSnapshot | null

Read the current values of the spec's declared properties from the wrapper at the range start. Returns null when the range is not inside a matching wrapper. The snapshot carries the matched element plus its declared style properties and attributes — unset and transparent-valued style properties are omitted.

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

Стройте UI инструмента от документа: предвыберите текущий цвет в палитре до того, как пользователь его сменит.

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
specMarkSpecОбязательныйDeclarative mark description: tag plus optional className, attributes, and style.
rangeRangecurrent selectionRange to read from; defaults to the live selection's first range.
TypeScript
const colorMark = {
  tag: 'mark',
  style: { color: (state) => state.color },
};

// Preselect the current colour in a picker UI
const snapshot = editor.marks.read(colorMark);
const current = snapshot?.style['color']; // e.g. 'rgb(37, 99, 235)'

marks.apply(spec, state?, range?)

HTMLElement[]

Wrap the range in the mark, or update matching wrappers in place. Splits partially-covered same-family wrappers at the range boundaries, extends the range over trailing whitespace browsers exclude from double-click selections, and leaves the new contents selected. Returns the created or updated wrapper elements.

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

Идемпотентен в рамках семьи: повторное применение с новым state обновляет ТУ ЖЕ обёртку на месте — потому палитра остаётся одной меткой, а не вложенными отменами.

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
specMarkSpecОбязательныйDeclarative mark description: tag plus optional className, attributes, and style.
stateStateValue passed to the spec's function-form attribute/style properties.
rangeRangecurrent selectionRange to format; defaults to the live selection's first range.
TypeScript
editor.marks.apply(colorMark, { color: '#d97706' });

// Same identity → the SAME wrapper is updated in place, not nested
editor.marks.apply(colorMark, { color: '#2563eb' });

marks.remove(spec, range?)

HTMLElement[]

Remove the spec's declared properties and classes from wrappers in the range, unwrapping wrappers left bare. Partially-covered wrappers are split so text outside the range keeps its formatting, and the selection is restored. Returns the wrappers that survived because they still carry other properties.

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

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

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
specMarkSpecОбязательныйDeclarative mark description: tag plus optional className, attributes, and style.
rangeRangecurrent selectionRange to deformat; defaults to the live selection's first range.
TypeScript
editor.marks.remove(colorMark);
// A <mark> that also carried a background-colour spec survives
// with only the colour stripped

marks.toggle(spec, state?, range?)

boolean

remove when the range already carries the mark, apply otherwise. Returns the resulting state: true when the mark is now applied.

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

Однострочник для простых инструментов-переключателей; в паре с has() для активного состояния.

Параметры

ПараметрТипОбязательныйПо умолчаниюОписание
specMarkSpecОбязательныйDeclarative mark description: tag plus optional className, attributes, and style.
stateStateValue passed to the spec's function-form attribute/style properties.
rangeRangecurrent selectionRange to toggle; defaults to the live selection's first range.
TypeScript
const highlight = { tag: 'span', className: 'my-highlight' };

const nowApplied = editor.marks.toggle(highlight);
TypeScript
// One spec = one mark. Static values (tag, className, attribute/style
// strings) form the mark's identity; function-form values do not.
const textColor = {
  tag: 'mark',
  style: { color: (state) => state.color },
};
const bgColor = {
  tag: 'mark',
  style: { 'background-color': (state) => state.color },
};

// Range-aware: splits partially-covered wrappers at the boundaries,
// updates fully-covering wrappers in place, restores the selection
editor.marks.apply(textColor, { color: '#2563eb' });

// Same family (same tag, no conflicting statics) → composes on ONE element
editor.marks.apply(bgColor, { color: '#fef3c7' });
// → <mark style="color: #2563eb; background-color: #fef3c7">…</mark>

// Derive the sanitizer rule the mark produces for a vanilla inline tool
import { markSanitizerConfig } from '@bloklabs/core';

class TextColorTool {
  static get sanitize() {
    return markSanitizerConfig(textColor);
  }
}