3.37.0 — Редактор, открытый для расширения
Статус: не ломающее изменение — minor по разделу «Стабильность»
API расширения Редактора: сохранённый HTML,
onChange, onUploadImage и focus() не меняются, прежний код собирается.
Заметка нужна потому, что пять изменений видны нынешним потребителям
@olddevs/ui/editor, а одно требует установки пакетов у pnpm. Новое API
(свой Инструмент, Хэндл, события, Строка статуса) действий не требует — оно на
странице API расширения.
1. Два новых peer-пакета
Цитата и разделитель берут Часть для движка из @tiptap/extension-blockquote и
@tiptap/extension-horizontal-rule — опциональных peer-зависимостей входа
@olddevs/ui/editor с тем же диапазоном ^3.31.4.
Кого затрагивает. Только pnpm (строгий node_modules) и установки с
--omit=optional: npm и yarn ставят оба пакета зависимостями
@tiptap/starter-kit, и импорт разрешается.
Действие до обновления.
pnpm add @tiptap/extension-blockquote @tiptap/extension-horizontal-ruleПолная команда — на странице RichTextEditor.
2. Разделитель не весит символ в maxLength
Что изменилось. Раньше счётчик подставлял пробел вместо листового узла, и
каждый <hr> отнимал у предела один символ. Теперь вес атома — длина его Текста
атома (renderText): разделитель весит 0, мягкий перенос — 1, картинка — длину
подписи. Счётчик совпадает с getText() без разделителей Блоков.
Кого затрагивает. Экраны с maxLength и разделителями в Документе. Документ
с N разделителями вмещает на N символов больше, а Документ, который раньше был
«превышен» на число разделителей, теперь укладывается в предел. Если сервер
проверяет длину тем же пределом и считает <hr> символом, он отвергнет то, что
Редактор пропустил.
До → после.
| Документ | Было | Стало |
|---|---|---|
<p>abc</p><hr><p>de</p> | 6 символов | 5 символов |
<p>a<br>b</p> | 3 | 3 |
Вставка за пределом срезает хвост по символам, а не по позициям; атом, который не поместился, уходит целиком вместе со всем, что после него.
Действие до обновления. Серверную проверку длины сверьте с правилом кита: текст Документа без разметки и разделителей Блоков, атом — длиной своего текста. Серверного счётчика у кита нет: вес своего атома зависит от его Части для движка.
3. Скелет до Готовности
Что изменилось. До Готовности Редактор показывает Скелет — рамку поля, место
Панели и Документ, очищенный Схемой, статичным HTML без ввода, — а не пустую
область: высота поля больше не прыгает. На сервере и до гидратации Скелет —
только рамка, Документ появляется после монтирования в браузере. В full блок
кода и таблица теперь приходят отдельным чанком, поэтому Готовность наступает
после их загрузки, и до неё виден Скелет (их Блоки — своей формой); basic эти
чанки не грузит и готов сразу. «Старт» подпути с движком — 312,3 → 286,6 кБ gzip,
«полный» — 327,7 кБ («Сколько стоит импорт»).
Кого затрагивает.
- Код, который берёт
ref.current.editorили зовётref.current.focus(),insertHTML()сразу после монтажа: вfullдвижок появляется позже, а до Готовностиeditor—nullи методы Хэндла тихие (не бросают). - Тесты потребителя с
full, которые ищутrole="textbox"синхронно сразу послеrender: редактируемая область появляется после загрузки чанков. У Скелета ролиtextboxнет. - Визуальные снимки страниц с Редактором: на месте пустой области — рамка и Документ.
До → после.
// было — фокус в эффекте монтажа: в `full` движка в этот момент ещё нет, вызов тихий
useEffect(() => ref.current?.focus(), []);
// стало — момент Готовности сообщает колбэк
<RichTextEditor ref={ref} onReady={() => ref.current?.focus()} />;// тест: ждать редактируемую область, а не брать её синхронно
expect(await screen.findByRole("textbox")).toBeInTheDocument();Действие до обновления. Найдите обращения к ref.current.editor и методам
Хэндла в эффектах монтажа и перенесите их в onReady; в тестах замените
getByRole("textbox") у Редактора с full на await findByRole. Чтобы чанки
пришли вместе со страницей, поставьте на них modulepreload
(рецепт).
4. Сообщения о картинках — на канале Сообщения поля
Что изменилось. «Загружается…», «загружен», отказ загрузки, «не картинка» и
«файлы не принимаются» идут тем же каналом, что новая команда движка
showNotice: одно место под полем, новое сообщение заменяет прежнее, ошибка
(role="alert") держится до следующей Правки автора. Тексты и роли прежние, но
живой регион role="status" теперь стоит в каждом Редакторе, а не только с
инструментом image.
Кого затрагивает. Тесты, которые проверяли отсутствие role="status" у
Редактора без image или считали живые регионы на странице, и экраны, которые
рисовали свою строку ошибки вставки рядом с полем.
Действие до обновления. Ищите регион по тексту сообщения, а не по одному
факту его наличия. Свою строку ошибки вставки и загрузки замените командой
ref.current?.editor?.commands.showNotice({ tone: "error", text }): две строки
role="alert" прозвучали бы дважды.
5. Тип Tool принимает описания своих Инструментов
Что изменилось. Tool — тип элемента tools — теперь ToolName | CustomTool;
имя встроенного Инструмента — новый тип ToolName. Прежний Tool был строкой
имени.
Кого затрагивает. TypeScript-код, который обращался с Tool как со строкой:
tool.startsWith(…), ключи Record<Tool, …>, сравнение в switch.
До → после.
// было
import type { Tool } from "@olddevs/ui/editor";
const labels: Record<Tool, string> = { … };
// стало
import type { ToolName } from "@olddevs/ui/editor";
const labels: Record<ToolName, string> = { … };Действие до обновления. tsc найдёт такие места сам; замените в них Tool на
ToolName. Массивы имён для tools менять не нужно.
Что ещё видно — действий не требует
- «Снять обёртку» в меню Ручки Блока стоит отдельной секцией Инструмента перед «Удалить»: свои Действия над Блоком встают туда же.
- Новые строки каталога
editor:toolsFailed,retry,calloutShown. Если вы передаёте свой полный каталог, добавьте их; частичный каталог берёт фолбэк кита.