Marks API: строчное форматирование по всему выделению
Range-aware операции со строчными метками для строчных инструментов форматирования. Там, где selection.findParentTag смотрит только на два граничных узла выделения (anchor и focus) и их предков, 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.
Как получить экземпляр редактора
Методы ниже вызываются на редакторе, созданном через 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');Методы
marks.has(spec, range?)
booleanНаходится ли каждый текстовый узел диапазона внутри обёртки, совпадающей со спецификацией, — узлы только из пробелов игнорируются, а при схлопнутом курсоре проверяются предки курсора. В отличие от selection.findParentTag (который смотрит только на граничные узлы выделения и их предков), выделение, лишь частично несущее метку, даёт false.
Когда использовать
Range-aware замена проверки активности через findParentTag: выделение, лишь частично несущее метку, даёт false — иконка на строчной панели не соврёт.
Параметры
| Параметр | Тип | Обязательный | По умолчанию | Описание |
|---|---|---|---|---|
spec | MarkSpec | Обязательный | — | Декларативное описание метки: тег плюс необязательные className, attributes и style. |
range | Range | — | current selection | Диапазон для проверки; по умолчанию — первый диапазон живого выделения. |
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Ближайший элемент-предок, совпадающий со спецификацией, начиная с заданного узла (или со стартового контейнера текущего выделения). Совпадение учитывает всю спецификацию — тег, classNames и статичные значения атрибутов/стилей, — а не только имя тега.
Когда использовать
Поиск предка по спецификации — как findParentTag, но с учётом classNames и статичных атрибутов/стилей, а не только тега.
Параметры
| Параметр | Тип | Обязательный | По умолчанию | Описание |
|---|---|---|---|---|
spec | MarkSpec | Обязательный | — | Декларативное описание метки: тег плюс необязательные className, attributes и style. |
from | Node | — | selection start container | Узел, с которого начинается поиск вверх; по умолчанию — стартовый контейнер текущего выделения. |
const wrapper = editor.marks.find({ tag: 'mark' });
if (wrapper) {
editor.selection.expandToTag(wrapper);
}marks.read(spec, range?)
MarkSnapshot | nullПрочитать текущие значения объявленных в спецификации свойств из обёртки в начале диапазона. Возвращает null, когда диапазон не находится внутри подходящей обёртки. Снимок несёт найденный элемент, а также его объявленные style-свойства и атрибуты — незаданные и прозрачные по значению style-свойства опускаются.
Когда использовать
Стройте UI инструмента от документа: предвыберите текущий цвет в палитре до того, как пользователь его сменит.
Параметры
| Параметр | Тип | Обязательный | По умолчанию | Описание |
|---|---|---|---|---|
spec | MarkSpec | Обязательный | — | Декларативное описание метки: тег плюс необязательные className, attributes и style. |
range | Range | — | current selection | Диапазон, из которого читать; по умолчанию — первый диапазон живого выделения. |
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[]Обернуть диапазон меткой или обновить подходящие обёртки на месте. Разрезает частично покрытые обёртки той же семьи на границах диапазона, расширяет диапазон на замыкающий пробел, который браузеры исключают из выделения по двойному клику, и оставляет новое содержимое выделенным. Возвращает созданные или обновлённые элементы-обёртки. Схлопнутый диапазон (голый курсор, когда ничего не выделено) — пустая операция: apply возвращает пустой массив, не трогая ни DOM, ни выделение.
Когда использовать
Идемпотентен в рамках семьи: повторное применение с новым state обновляет ТУ ЖЕ обёртку на месте — потому палитра остаётся одной меткой, а не вложенными отменами.
Параметры
| Параметр | Тип | Обязательный | По умолчанию | Описание |
|---|---|---|---|---|
spec | MarkSpec | Обязательный | — | Декларативное описание метки: тег плюс необязательные className, attributes и style. |
state | State | — | — | Значение, передаваемое свойствам атрибутов/стилей спецификации, заданным в виде функции. |
range | Range | — | current selection | Диапазон для форматирования; по умолчанию — первый диапазон живого выделения. |
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[]Удалить объявленные в спецификации свойства и классы из обёрток в диапазоне, снимая обёртки, оставшиеся пустыми. Для несхлопнутого диапазона частично покрытые обёртки разрезаются, чтобы текст за пределами диапазона сохранил своё форматирование. Схлопнутый курсор вместо этого целиком берёт охватывающую его обёртку — разреза не происходит, и свойства спецификации снимаются со всей обёртки. Выделение восстанавливается и в том, и в другом случае. Возвращает обёртки, которые уцелели, потому что всё ещё несут другие свойства.
Когда использовать
Удаляются только свойства самой спецификации — обёртка, разделяемая с другой меткой семьи, выживает с остальными свойствами.
Параметры
| Параметр | Тип | Обязательный | По умолчанию | Описание |
|---|---|---|---|---|
spec | MarkSpec | Обязательный | — | Декларативное описание метки: тег плюс необязательные className, attributes и style. |
range | Range | — | current selection | Диапазон для снятия форматирования; по умолчанию — первый диапазон живого выделения. |
editor.marks.remove(colorMark);
// A <mark> that also carried a background-colour spec survives
// with only the colour strippedmarks.toggle(spec, state?, range?)
booleanremove, когда диапазон уже несёт метку, иначе apply. Возвращает получившееся состояние: true, когда метка теперь применена. При схлопнутом курсоре без уже имеющейся метки возвращает true, ничего не применив, — apply на схлопнутом диапазоне — пустая операция. Вызывайте toggle на несхлопнутом диапазоне или считайте возвращаемое значение намеренным состоянием, а не подтверждением.
Когда использовать
Однострочник для простых инструментов-переключателей; в паре с has() для активного состояния.
Параметры
| Параметр | Тип | Обязательный | По умолчанию | Описание |
|---|---|---|---|---|
spec | MarkSpec | Обязательный | — | Декларативное описание метки: тег плюс необязательные className, attributes и style. |
state | State | — | — | Значение, передаваемое свойствам атрибутов/стилей спецификации, заданным в виде функции. |
range | Range | — | current selection | Диапазон для переключения; по умолчанию — первый диапазон живого выделения. |
const highlight = { tag: 'span', className: 'my-highlight' };
const nowApplied = editor.marks.toggle(highlight);// 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);
}
}