Olddevs UI3.38.0
Справочник

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>33

Вставка за пределом срезает хвост по символам, а не по позициям; атом, который не поместился, уходит целиком вместе со всем, что после него.

Действие до обновления. Серверную проверку длины сверьте с правилом кита: текст Документа без разметки и разделителей Блоков, атом — длиной своего текста. Серверного счётчика у кита нет: вес своего атома зависит от его Части для движка.

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. Если вы передаёте свой полный каталог, добавьте их; частичный каталог берёт фолбэк кита.

На этой странице