@olddevs/ui 4.3.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/dist/editor/RichTextEditor.d.ts +11 -4
- package/dist/editor/RichTextEditor.d.ts.map +1 -1
- package/dist/editor/RichTextEditor.js +124 -60
- package/dist/editor/RichTextEditor.js.map +1 -1
- package/dist/editor/RichTextView.d.ts +2 -1
- package/dist/editor/RichTextView.d.ts.map +1 -1
- package/dist/editor/RichTextView.js.map +1 -1
- package/dist/editor/core/containers.d.ts +2 -6
- package/dist/editor/core/containers.d.ts.map +1 -1
- package/dist/editor/core/containers.js +2 -1
- package/dist/editor/core/containers.js.map +1 -1
- package/dist/editor/core/customTool.d.ts +8 -2
- package/dist/editor/core/customTool.d.ts.map +1 -1
- package/dist/editor/core/customTool.js.map +1 -1
- package/dist/editor/core/customToolEngine.d.ts +9 -1
- package/dist/editor/core/customToolEngine.d.ts.map +1 -1
- package/dist/editor/core/customToolEngine.js +12 -0
- package/dist/editor/core/customToolEngine.js.map +1 -1
- package/dist/editor/core/extensions.d.ts.map +1 -1
- package/dist/editor/core/extensions.js +1 -1
- package/dist/editor/core/extensions.js.map +1 -1
- package/dist/editor/core/handle.d.ts +18 -5
- package/dist/editor/core/handle.d.ts.map +1 -1
- package/dist/editor/core/handle.js +2 -13
- package/dist/editor/core/handle.js.map +1 -1
- package/dist/editor/core/handleWatchers.d.ts +36 -0
- package/dist/editor/core/handleWatchers.d.ts.map +1 -0
- package/dist/editor/core/handleWatchers.js +29 -0
- package/dist/editor/core/handleWatchers.js.map +1 -0
- package/dist/editor/core/nodeNames.d.ts +7 -0
- package/dist/editor/core/nodeNames.d.ts.map +1 -0
- package/dist/editor/core/nodeNames.js +2 -0
- package/dist/editor/core/nodeNames.js.map +1 -0
- package/dist/editor/core/schema.d.ts.map +1 -1
- package/dist/editor/core/schema.js +4 -2
- package/dist/editor/core/schema.js.map +1 -1
- package/dist/editor/core/toolCommands.d.ts +78 -0
- package/dist/editor/core/toolCommands.d.ts.map +1 -0
- package/dist/editor/core/toolCommands.js +2 -0
- package/dist/editor/core/toolCommands.js.map +1 -0
- package/dist/editor/core/toolDefinition.d.ts +139 -0
- package/dist/editor/core/toolDefinition.d.ts.map +1 -0
- package/dist/editor/core/toolDefinition.js +28 -0
- package/dist/editor/core/toolDefinition.js.map +1 -0
- package/dist/editor/core/toolDescriptor.d.ts +71 -2
- package/dist/editor/core/toolDescriptor.d.ts.map +1 -1
- package/dist/editor/core/toolDescriptor.js +6 -2
- package/dist/editor/core/toolDescriptor.js.map +1 -1
- package/dist/editor/core/toolOptions.d.ts +157 -0
- package/dist/editor/core/toolOptions.d.ts.map +1 -0
- package/dist/editor/core/toolOptions.js +21 -0
- package/dist/editor/core/toolOptions.js.map +1 -0
- package/dist/editor/core/toolState.d.ts +46 -0
- package/dist/editor/core/toolState.d.ts.map +1 -0
- package/dist/editor/core/toolState.js +2 -0
- package/dist/editor/core/toolState.js.map +1 -0
- package/dist/editor/core/tools.d.ts +10 -3
- package/dist/editor/core/tools.d.ts.map +1 -1
- package/dist/editor/core/tools.js +2 -1
- package/dist/editor/core/tools.js.map +1 -1
- package/dist/editor/core/viewIsland.d.ts.map +1 -1
- package/dist/editor/core/viewIsland.js +2 -1
- package/dist/editor/core/viewIsland.js.map +1 -1
- package/dist/editor/features/builtins.d.ts +9 -4
- package/dist/editor/features/builtins.d.ts.map +1 -1
- package/dist/editor/features/builtins.js +36 -7
- package/dist/editor/features/builtins.js.map +1 -1
- package/dist/editor/features/search/SearchPanel.d.ts.map +1 -1
- package/dist/editor/features/search/SearchPanel.js +26 -12
- package/dist/editor/features/search/SearchPanel.js.map +1 -1
- package/dist/editor/features/search/revealMatch.d.ts +16 -0
- package/dist/editor/features/search/revealMatch.d.ts.map +1 -1
- package/dist/editor/features/search/revealMatch.js +22 -0
- package/dist/editor/features/search/revealMatch.js.map +1 -1
- package/dist/editor/features/search/searchPlugin.d.ts +8 -3
- package/dist/editor/features/search/searchPlugin.d.ts.map +1 -1
- package/dist/editor/features/search/searchPlugin.js +17 -6
- package/dist/editor/features/search/searchPlugin.js.map +1 -1
- package/dist/editor/features/search/searchSnapshot.d.ts +14 -0
- package/dist/editor/features/search/searchSnapshot.d.ts.map +1 -0
- package/dist/editor/features/search/searchSnapshot.js +17 -0
- package/dist/editor/features/search/searchSnapshot.js.map +1 -0
- package/dist/editor/features/search/searchTool.d.ts +3 -0
- package/dist/editor/features/search/searchTool.d.ts.map +1 -1
- package/dist/editor/features/search/searchTool.js +30 -1
- package/dist/editor/features/search/searchTool.js.map +1 -1
- package/dist/editor/features/toc/RichTextToc.d.ts +91 -0
- package/dist/editor/features/toc/RichTextToc.d.ts.map +1 -0
- package/dist/editor/features/toc/RichTextToc.js +417 -0
- package/dist/editor/features/toc/RichTextToc.js.map +1 -0
- package/dist/editor/features/toc/documentHeadings.d.ts +30 -0
- package/dist/editor/features/toc/documentHeadings.d.ts.map +1 -0
- package/dist/editor/features/toc/documentHeadings.js +20 -0
- package/dist/editor/features/toc/documentHeadings.js.map +1 -0
- package/dist/editor/features/toc/headings.d.ts +35 -0
- package/dist/editor/features/toc/headings.d.ts.map +1 -0
- package/dist/editor/features/toc/headings.js +27 -0
- package/dist/editor/features/toc/headings.js.map +1 -0
- package/dist/editor/features/toc/stickyOffset.d.ts +3 -0
- package/dist/editor/features/toc/stickyOffset.d.ts.map +1 -0
- package/dist/editor/features/toc/stickyOffset.js +14 -0
- package/dist/editor/features/toc/stickyOffset.js.map +1 -0
- package/dist/editor/features/toc/tocSnapshot.d.ts +10 -0
- package/dist/editor/features/toc/tocSnapshot.d.ts.map +1 -0
- package/dist/editor/features/toc/tocSnapshot.js +8 -0
- package/dist/editor/features/toc/tocSnapshot.js.map +1 -0
- package/dist/editor/features/toc/tocTool.d.ts +6 -0
- package/dist/editor/features/toc/tocTool.d.ts.map +1 -0
- package/dist/editor/features/toc/tocTool.js +21 -0
- package/dist/editor/features/toc/tocTool.js.map +1 -0
- package/dist/editor/hooks/useRichTextToolState.d.ts +43 -0
- package/dist/editor/hooks/useRichTextToolState.d.ts.map +1 -0
- package/dist/editor/hooks/useRichTextToolState.js +77 -0
- package/dist/editor/hooks/useRichTextToolState.js.map +1 -0
- package/dist/editor/index.d.ts +10 -0
- package/dist/editor/index.d.ts.map +1 -1
- package/dist/editor/index.js +4 -0
- package/dist/editor/index.js.map +1 -1
- package/dist/i18n/defaults.d.ts.map +1 -1
- package/dist/i18n/defaults.js +1 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/messages.d.ts +5 -0
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/locales/ru.d.ts.map +1 -1
- package/dist/locales/ru.js +1 -0
- package/dist/locales/ru.js.map +1 -1
- package/docs/api/editor-extension.en.md +223 -23
- package/docs/api/editor-extension.md +224 -22
- package/docs/api/messages.en.md +743 -0
- package/docs/api/messages.md +1 -0
- package/docs/api/migration-notes.en.md +36 -1
- package/docs/api/migration-notes.md +35 -0
- package/docs/api/utilities.en.md +233 -0
- package/docs/components/content/rich-text-toc.en.md +132 -0
- package/docs/components/content/rich-text-toc.md +131 -0
- package/docs/components/index.en.md +3 -2
- package/docs/components/index.md +2 -1
- package/docs/components/inputs/rich-text-editor.en.md +277 -43
- package/docs/components/inputs/rich-text-editor.md +247 -11
- package/docs/conventions.en.md +318 -0
- package/docs/getting-started.en.md +7 -7
- package/docs/getting-started.md +6 -6
- package/docs/glossary.en.md +52 -47
- package/docs/glossary.md +51 -46
- package/docs/index.en.md +53 -0
- package/llms.txt +1 -0
- package/package.json +1 -1
- package/src/editor/RichTextEditor.tsx +197 -38
- package/src/editor/RichTextView.tsx +2 -1
- package/src/editor/core/containers.ts +8 -6
- package/src/editor/core/customTool.ts +8 -2
- package/src/editor/core/customToolEngine.ts +20 -1
- package/src/editor/core/extensions.ts +2 -1
- package/src/editor/core/handle.ts +26 -23
- package/src/editor/core/handleWatchers.ts +72 -0
- package/src/editor/core/nodeNames.ts +7 -0
- package/src/editor/core/schema.ts +5 -2
- package/src/editor/core/toolCommands.ts +85 -0
- package/src/editor/core/toolDefinition.ts +208 -0
- package/src/editor/core/toolDescriptor.ts +94 -3
- package/src/editor/core/toolOptions.ts +211 -0
- package/src/editor/core/toolState.ts +56 -0
- package/src/editor/core/tools.ts +12 -3
- package/src/editor/core/viewIsland.ts +5 -4
- package/src/editor/features/builtins.ts +72 -13
- package/src/editor/features/search/SearchPanel.tsx +38 -14
- package/src/editor/features/search/revealMatch.ts +45 -0
- package/src/editor/features/search/searchPlugin.ts +40 -11
- package/src/editor/features/search/searchSnapshot.ts +29 -0
- package/src/editor/features/search/searchTool.ts +51 -1
- package/src/editor/features/toc/RichTextToc.tsx +810 -0
- package/src/editor/features/toc/documentHeadings.ts +59 -0
- package/src/editor/features/toc/headings.ts +77 -0
- package/src/editor/features/toc/stickyOffset.ts +21 -0
- package/src/editor/features/toc/tocSnapshot.ts +24 -0
- package/src/editor/features/toc/tocTool.ts +41 -0
- package/src/editor/hooks/useRichTextToolState.ts +153 -0
- package/src/editor/index.ts +21 -0
- package/src/i18n/defaults.ts +1 -0
- package/src/i18n/messages.ts +5 -0
- package/src/locales/ru.ts +1 -0
|
@@ -8,13 +8,16 @@ source: src/editor/index.ts
|
|
|
8
8
|
|
|
9
9
|
## Кратко для AI
|
|
10
10
|
|
|
11
|
-
Карта того, чем продукт расширяет `RichTextEditor` из `@olddevs/ui/editor`, и граница стабильности этого API. Единица расширения — **свой Инструмент** (`CustomTool`): описание одной возможности Редактора, которое передаётся в `tools` вперемешку с именами кита. Он несёт Блоки и Метки с правилами Схемы (это данные), Часть для движка (расширения Tiptap, сразу или Загрузчиком) и UI-записи: кнопки, Пункты вставки, Действия над Блоком, Карточки у текста. Есть и свой Символ-триггер (`trigger`): `@` для упоминаний открывает тот же список, что `/`. Код экрана управляет полем через Хэндл (`ref`) и хук `useRichTextEditorState`, узнаёт о Готовности и Фокусе поля колбэками `onReady`, `onFocus` и `onBlur`, кладёт своё в Строку статуса (`status`) и сообщает об ошибке командой `showNotice`. Тот же массив `tools` понимают серверная очистка (`sanitizeRichText`, `richTextSchema`) и `RichTextView` с Островами. Всё перечисленное подчиняется semver кита с первого выпуска, HTML-соглашение заморожено, мажор Tiptap — мажор кита (раздел «Стабильность»). Подробности каждого пункта живут на странице [RichTextEditor](../components/inputs/rich-text-editor.md); здесь — карта, граница контракта и рецепты.
|
|
11
|
+
Карта того, чем продукт расширяет `RichTextEditor` из `@olddevs/ui/editor`, и граница стабильности этого API. Единица расширения — **свой Инструмент** (`CustomTool`): описание одной возможности Редактора, которое передаётся в `tools` вперемешку с именами кита. Он несёт Блоки и Метки с правилами Схемы (это данные), Часть для движка (расширения Tiptap, сразу или Загрузчиком) и UI-записи: кнопки, Пункты вставки, Действия над Блоком, Карточки у текста. Есть и свой Символ-триггер (`trigger`): `@` для упоминаний открывает тот же список, что `/`. Код экрана управляет полем через Хэндл (`ref`) и хук `useRichTextEditorState`, узнаёт о Готовности и Фокусе поля колбэками `onReady`, `onFocus` и `onBlur`, кладёт своё в Строку статуса (`status`) и сообщает об ошибке командой `showNotice`. У Инструмента три типизированные части, как `options`, `storage` и `commands` у расширения Tiptap: **Опции** (`tool(name, options)` у встроенных `search` и `toc`, `defineTool` у своего), **Состояние** (хук `useRichTextToolState`) и **Команды** (`handle.tool(name)`); их типы собраны в расширяемой карте `RichTextTools`, куда свой Инструмент дописывается через `declare module "@olddevs/ui/editor"`. Тот же массив `tools` понимают серверная очистка (`sanitizeRichText`, `richTextSchema`) и `RichTextView` с Островами. Всё перечисленное подчиняется semver кита с первого выпуска, HTML-соглашение заморожено, мажор Tiptap — мажор кита (раздел «Стабильность»). Подробности каждого пункта живут на странице [RichTextEditor](../components/inputs/rich-text-editor.md); здесь — карта, граница контракта и рецепты.
|
|
12
12
|
|
|
13
13
|
```tsx
|
|
14
14
|
import {
|
|
15
15
|
RICH_TEXT_PRESETS,
|
|
16
16
|
RichTextEditor,
|
|
17
|
+
defineTool,
|
|
18
|
+
tool,
|
|
17
19
|
useRichTextEditorState,
|
|
20
|
+
useRichTextToolState,
|
|
18
21
|
type CustomTool,
|
|
19
22
|
type RichTextEditorHandle,
|
|
20
23
|
} from "@olddevs/ui/editor";
|
|
@@ -28,12 +31,16 @@ import { sanitizeRichText } from "@olddevs/ui/editor/schema";
|
|
|
28
31
|
- Документу нужен свой Блок, Атом или своя Метка: Merge Tag, Embed, карточка товара, комментарий к фрагменту. Их Схема, HTML, Markdown и показ в `RichTextView` описываются одним Инструментом.
|
|
29
32
|
- Своя команда в интерфейсе Редактора: кнопка на Панели, Пункт вставки в «/» и «+», Действие над Блоком в меню Ручки Блока, Карточка у текста.
|
|
30
33
|
- Экран управляет полем: вставляет шаблон у каретки, рисует свою панель, сохраняет при потере фокуса, показывает «Сохранено» и число слов.
|
|
34
|
+
- Встроенный Инструмент нужно настроить (`toc` под шапку со своей переменной высоты, Поиск с включённым регистром) или узнать, что в нём происходит: счётчик совпадений в Строке статуса, внешняя навигация по заголовкам, кнопка «Найти» в шапке экрана.
|
|
35
|
+
- Свой Инструмент с Опциями, которому нужны Состояние и Команды для экрана: `defineTool` вместо ручной фабрики.
|
|
31
36
|
- Сервер хранит Документ со своими Блоками и должен чистить его тем же набором правил, что Редактор.
|
|
32
37
|
|
|
33
38
|
## Когда не использовать
|
|
34
39
|
|
|
35
40
|
- Нужное уже есть среди Инструментов кита ([«Инструменты и Пресеты»](../components/inputs/rich-text-editor.md#инструменты-и-пресеты)): передайте имя в `tools`, своё не пишите.
|
|
36
41
|
- Чтобы спрятать встроенную кнопку или пункт, сузьте `tools`. Подменить встроенный Инструмент по имени нельзя: выключите его и поставьте свой под своим именем.
|
|
42
|
+
- Опции есть только у `search` и `toc`; `tool("bold", …)` не компилируется. Опции остальных встроенных добавляются по одному, когда о них попросят. Поменять поведение встроенного Инструмента подменой нельзя (`extend()` как у Tiptap нет): описания встроенных наружу не выходят.
|
|
43
|
+
- Сменить Опции у смонтированного поля нельзя: они читаются один раз, при создании движка, вместе с `tools`. Перемонтируйте поле ключом.
|
|
37
44
|
- Произвольный React в строке Панели и в меню нельзя: кит рисует записи сам, React допускается только в содержимом поповера (кнопки, Пункта вставки, Действия над Блоком) и в Карточке у текста.
|
|
38
45
|
- Во вставку и drop пропом вмешаться нельзя: это делает Часть для движка своего Инструмента ([«Порядок вставки и drop»](../components/inputs/rich-text-editor.md#порядок-вставки-и-drop)).
|
|
39
46
|
- Чего нет, пока потребители об этом не попросили (ADR-0058, раздел 13): `insertImage` и HTML выделения в Хэндле, `onSelectionChange`, `onPaste`, `onDrop`, `onEmpty`, `onDestroy`, события истории, сигнал об удалённых картинках, запертые Блоки шаблона, вид записи «меню», Карточки и Строка статуса в `RichTextView`. До тех пор у экрана есть `ref.current.editor` и его события.
|
|
@@ -41,21 +48,26 @@ import { sanitizeRichText } from "@olddevs/ui/editor/schema";
|
|
|
41
48
|
|
|
42
49
|
## Карта API
|
|
43
50
|
|
|
44
|
-
| Что
|
|
45
|
-
|
|
|
46
|
-
| Свой Инструмент
|
|
47
|
-
| Правила Схемы и HTML-соглашение
|
|
48
|
-
| Кнопка
|
|
49
|
-
| Пункт вставки, Действие над Блоком
|
|
50
|
-
| Символ-триггер
|
|
51
|
-
| Карточка у текста
|
|
52
|
-
| Хэндл и своя панель
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
|
51
|
+
| Что | Где в API | Подробно |
|
|
52
|
+
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
53
|
+
| Свой Инструмент | `CustomTool` в `tools`; Пресеты — `RICH_TEXT_PRESETS` | [«Свой Инструмент»](../components/inputs/rich-text-editor.md#свой-инструмент) |
|
|
54
|
+
| Правила Схемы и HTML-соглашение | `blocks` (`CustomBlock`), `marks` (`CustomMark`), `CustomAttribute`, `PrefixedName` | [«Свой Инструмент»](../components/inputs/rich-text-editor.md#свой-инструмент) |
|
|
55
|
+
| Кнопка | `buttons` (`CustomToolButton`, `CustomToolPopover`, `CustomToolLabel`) | [«Кнопка своего Инструмента»](../components/inputs/rich-text-editor.md#кнопка-своего-инструмента) |
|
|
56
|
+
| Пункт вставки, Действие над Блоком | `insertItems` (`CustomInsertItem`), `blockActions` (`CustomBlockAction`) | [«Пункты вставки и Действия над Блоком»](../components/inputs/rich-text-editor.md#пункты-вставки-и-действия-над-блоком) |
|
|
57
|
+
| Символ-триггер | `trigger` (`CustomTrigger`, `TriggerItem`, `TriggerContext`) | [«Свой символ-триггер»](../components/inputs/rich-text-editor.md#свой-символ-триггер) |
|
|
58
|
+
| Карточка у текста | `textCards` (`CustomTextCard`), помощник `markRange` | [«Карточка у текста»](../components/inputs/rich-text-editor.md#карточка-у-текста) |
|
|
59
|
+
| Хэндл и своя панель | `RichTextEditorHandle`, `useRichTextEditorState(ref, selector)` | [«Хэндл (`ref`) и своя панель вне поля»](../components/inputs/rich-text-editor.md#хэндл-ref-и-своя-панель-вне-поля) |
|
|
60
|
+
| Опции встроенного Инструмента | `tool(name, options)` → `ConfiguredTool` для `search` и `toc`; `SearchToolOptions`, `TocToolOptions`, `TocLevel` | [«Опции встроенного Инструмента: `tool()`»](../components/inputs/rich-text-editor.md#опции-встроенного-инструмента-tool) |
|
|
61
|
+
| Свой Инструмент с Опциями, Состоянием, Командами | `defineTool({ name, defaults, create, state, commands })` → `ToolDefinition`, `tool(definition, options)` | [«Опции, Состояние и Команды»](editor-extension.md#опции-состояние-и-команды) |
|
|
62
|
+
| Типы Опций, Состояния и Команд | расширяемый интерфейс `RichTextTools` | [«Карта `RichTextTools` и declaration merging»](editor-extension.md#карта-richtexttools-и-declaration-merging) |
|
|
63
|
+
| Состояние Инструмента | `useRichTextToolState(ref, name, selector?)`, `SearchToolState`, `TocToolState`, `TocToolHeading` | [«Состояние Инструмента: `useRichTextToolState`»](../components/inputs/rich-text-editor.md#состояние-инструмента-userichtexttoolstate) |
|
|
64
|
+
| Команды Инструмента | `handle.tool(name)`, `SearchToolCommands`, `TocToolCommands` | [«Команды Инструмента: `handle.tool()`»](../components/inputs/rich-text-editor.md#команды-инструмента-handletool) |
|
|
65
|
+
| События | `onReady`, `onFocus`, `onBlur`; вставка и drop — Частью для движка | [«События»](../components/inputs/rich-text-editor.md#события) |
|
|
66
|
+
| Строка статуса и Сообщение поля | проп `status`; команда движка `showNotice`, тип `FieldNotice` | [«Строка статуса и Сообщение поля»](../components/inputs/rich-text-editor.md#строка-статуса-и-сообщение-поля) |
|
|
67
|
+
| Загрузчик, Готовность и Скелет | `engine: () => import(…)` | [«Загрузчик Части для движка»](../components/inputs/rich-text-editor.md#загрузчик-части-для-движка), [«Скелет»](../components/inputs/rich-text-editor.md#скелет) |
|
|
68
|
+
| Текст атома: `getText`, `maxLength`, пустота | `renderText` Части для движка | [«Текст атома»](../components/inputs/rich-text-editor.md#текст-атома) |
|
|
69
|
+
| Сервер, Markdown, Просмотр | `sanitizeRichText(html, DOMPurify, tools)`, `richTextSchema(tools)`, `RichTextView` | [«Вне Редактора»](../components/inputs/rich-text-editor.md#вне-редактора-сервер-markdown-и-просмотр), [RichTextView, «Свои Инструменты: третий аргумент»](../components/content/rich-text-view.md#свои-инструменты-третий-аргумент) |
|
|
70
|
+
| Остров своего Блока | `CustomBlock.island`, `CustomIslandProps`, проп `tools` у `RichTextView` | [RichTextView, «Остров своего Блока»](../components/content/rich-text-view.md#остров-своего-блока) |
|
|
59
71
|
|
|
60
72
|
## Свой Инструмент и HTML-соглашение
|
|
61
73
|
|
|
@@ -95,12 +107,197 @@ import { sanitizeRichText } from "@olddevs/ui/editor/schema";
|
|
|
95
107
|
|
|
96
108
|
## Хэндл (`ref`), события, Строка статуса
|
|
97
109
|
|
|
98
|
-
- **Хэндл** (`ref`) — вставка у каретки (`insertHTML`, `insertText` → `boolean`), чтение (`getText`, `getSelectedText`, `isEmpty`, `getHTML`, `getMarkdown`, `getJSON`), фокус (`focus("start" | "end" | "restore")`, `blur`), история (`undo`, `redo`, `canUndo`, `canRedo`), а ещё `clear`, `setMarkdown` и `setJSON` (JSON-конверт → `boolean`) и `openSearch({ replace? })`
|
|
110
|
+
- **Хэндл** (`ref`) — вставка у каретки (`insertHTML`, `insertText` → `boolean`), чтение (`getText`, `getSelectedText`, `isEmpty`, `getHTML`, `getMarkdown`, `getJSON`), фокус (`focus("start" | "end" | "restore")`, `blur`), история (`undo`, `redo`, `canUndo`, `canRedo`), а ещё `clear`, `setMarkdown` и `setJSON` (JSON-конверт → `boolean`) и `tool(name)` — Команды Инструмента (раздел «Опции, Состояние и Команды» ниже). `openSearch({ replace? })` устарел: он работает как `tool("search")?.open({ replace })`, один раз предупреждает в разработке и удаляется в 5.0. Вставка — Правка автора: шаг истории, `onChange` после задержки, предел `maxLength`; за пределом вставка не выполняется. До Готовности методы не бросают ошибок и ничего не меняют: `insertHTML` и `insertText` возвращают `false`. `editor` — сырой Tiptap для команд Меток, Блоков и своих Инструментов; его API следует мажору Tiptap.
|
|
99
111
|
- **`useRichTextEditorState(ref, selector)`** — состояние своей панели: перерисовка только при смене среза, `null` до движка и на сервере.
|
|
112
|
+
- **`useRichTextToolState(ref, name, selector?)`** — Состояние Инструмента: снимок данных, перерисовка только при его смене, `null` до монтажа, на сервере, после размонтирования Редактора и без Инструмента в наборе.
|
|
100
113
|
- **`onReady()`** — один раз за монтаж, когда наступила Готовность; **`onFocus()`** и **`onBlur()`** — о Фокусе поля целиком (Документ, Панель, поповеры кита, поле HTML); перед `onBlur` отложенный `onChange` выталкивается.
|
|
101
114
|
- **`status`** — React-узел экрана в строке под Документом, у начала; счётчик кита — в конце. Кит строку не озвучивает.
|
|
102
115
|
- **`showNotice({ tone: "status" | "error", text } | null)`** — команда движка: одно Сообщение поля, новое заменяет прежнее; `status` только озвучивается, `error` видна строкой `role="alert"` до следующей Правки автора. Тем же каналом кит сообщает о картинках.
|
|
103
116
|
|
|
117
|
+
## Опции, Состояние и Команды
|
|
118
|
+
|
|
119
|
+
У Инструмента три типизированные части. Это перенос `options`, `storage` и `commands` из Tiptap на контракт кита, а не движка (ADR-0069).
|
|
120
|
+
|
|
121
|
+
| Часть | Что это | Кто задаёт | Как пользуется экран |
|
|
122
|
+
| --------- | ----------------------------------- | ------------------------------------------------------------------------------------- | -------------------------------------------- |
|
|
123
|
+
| Опции | Что экран задаёт до создания движка | `tool(name, options)` у встроенных; `defaults` и `tool(definition, options)` у своего | — |
|
|
124
|
+
| Состояние | Снимок, который экран читает | Кит у встроенных; поле `state` у `defineTool` | `useRichTextToolState(ref, name, selector?)` |
|
|
125
|
+
| Команды | То, что экран вызывает | Кит у встроенных; поле `commands` у `defineTool` | `ref.current?.tool(name)?.команда()` |
|
|
126
|
+
|
|
127
|
+
Часть для движка по-прежнему следует мажору Tiptap (ADR-0058, п. 12.4). Три новые части от Tiptap не зависят: их типы и поведение подчиняются semver кита.
|
|
128
|
+
|
|
129
|
+
### `tool()`: встроенный Инструмент с Опциями
|
|
130
|
+
|
|
131
|
+
`tool(name, options)` возвращает Настроенный Инструмент — непрозрачную запись для `tools` (её поля читает только кит). Опции есть у `search` (стартовые флажки) и `toc` (`offsetVar`, `levels`, `listWidth`); их значения и пример — на странице [RichTextEditor](../components/inputs/rich-text-editor.md#опции-встроенного-инструмента-tool). Запись включает Инструмент и без имени в `tools`, а имя из Пресета ей уступает. Две записи `tool()` с одним именем — ошибка набора.
|
|
132
|
+
|
|
133
|
+
`tool(definition, options)` настраивает тем же способом определение `defineTool`; Опции берутся из его `defaults`.
|
|
134
|
+
|
|
135
|
+
### `defineTool()`: свой Инструмент с Опциями
|
|
136
|
+
|
|
137
|
+
`defineTool({ name, defaults, create, state?, commands? })` возвращает определение — элемент `tools`, который работает и без `tool()` (с умолчаниями из `defaults`), и с ним.
|
|
138
|
+
|
|
139
|
+
- **`defaults`** — полный набор Опций. Тип Опций выводится из него, второй раз его объявлять не нужно. Литерал расширяется до своего типа (`tone: "info"` станет `string`): нужен union-тип — укажите тип значения, `tone: "info" as "info" | "warning"`.
|
|
140
|
+
- **`create(options)`** отдаёт тело описания — ту же форму, что `CustomTool`, без имени (`CustomToolBody`): Часть для движка, правила Схемы, UI-записи. Зовётся один раз на запись `tools` (определение само или объект, который вернул `tool(definition, options)`): кит запоминает результат по паре «определение и объект Опций записи». Всё, что замкнуто в теле `create` (счётчик, кэш), поэтому общее у всех Редакторов с этой записью. Запись `tool(…)`, созданная в теле компонента, на каждом рендере новая, и Остров в `RichTextView` на каждом рендере перемонтируется: выносите набор `tools` из рендера, в константу модуля или в `useMemo`. Загрузчик Части для движка держите константой модуля: кэш загрузчика живёт на самой функции, и новая функция на каждый вызов `create` загружала бы Часть заново для каждой записи.
|
|
141
|
+
- **`state({ editor, options })`** — Состояние для `useRichTextToolState`. Её зовут при каждой транзакции и при каждом чтении хука, поэтому она должна быть быстрой и без побочных эффектов, а результат — данные без DOM-узлов. Примитивы, массивы и простые объекты сравниваются по значению, поэтому экран перерисовывается, только когда снимок изменился. `Date`, `Map`, `Set` и экземпляры классов сравниваются по ссылке: `state`, который создаёт их заново на каждый вызов, перерисует экран на каждой транзакции. Отдавайте простые данные (`date.toISOString()`) или ту же ссылку, пока данные не изменились. Нет `state` — у Инструмента нет Состояния, и хук отдаёт `null`.
|
|
142
|
+
- **`commands({ editor, options })`** — Команды для `handle.tool(name)`. Их зовут при каждом обращении к Хэндлу: только обёртки над движком, без подписок. Команда возвращает `boolean`, как у встроенных. Нет `commands` — `handle.tool(name)` отдаёт `null`.
|
|
143
|
+
- **Слияние Опций.** Вложенные простые объекты сливаются с умолчаниями, массивы и функции заменяются целиком — как `configure` у Tiptap v3. Значение `undefined` не в счёт: остаётся то, что было. Объект `defaults` не меняется, объект Опций общий для `create`, `state` и `commands`: читайте его, но не меняйте.
|
|
144
|
+
- **Опции читаются один раз**, при создании движка, вместе с набором. Чтобы сменить их, перемонтируйте Редактор ключом (`key`). Значения Опций своего Инструмента проверяет его автор: кит этого не делает.
|
|
145
|
+
- **Все правила своего Инструмента действуют** (ADR-0058): Префикс потребителя в имени, уникальность имени в `tools` (определение дважды, с `tool()` и без, — тоже ошибка набора), проверки набора, Загрузчик Части для движка. `defineTool` — сахар над той же формой `CustomTool`: отдельного пути сборки у него нет. Обычный объект `CustomTool` в `tools` работает как раньше, без Опций, Состояния и Команд.
|
|
146
|
+
- **Сервер.** `defineTool` и `tool()` живут в модуле без Tiptap и React в рантайме (Tiptap там — только типы), поэтому набор `tools` собирается в серверном модуле, рядом с остальной конфигурацией экрана. Определение содержит функции, как и `CustomTool`, поэтому клиентскому компоненту оно отдаётся вместе с модулем (импортом), а не пропом через границу RSC. Настроенный встроенный Инструмент состоит из данных: при данных в опциях его можно передать и пропом.
|
|
147
|
+
|
|
148
|
+
### Карта `RichTextTools` и declaration merging
|
|
149
|
+
|
|
150
|
+
Типы всех трёх частей собраны в интерфейсе `RichTextTools`, как `Storage` и `Commands` у Tiptap: ключ — имя Инструмента, значение — `{ options; state; commands }`. Кит объявляет в нём `search` и `toc`, свой Инструмент потребитель дописывает сам:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
declare module "@olddevs/ui/editor" {
|
|
154
|
+
interface RichTextTools {
|
|
155
|
+
acme_note: {
|
|
156
|
+
options: { tone: "info" | "warning" };
|
|
157
|
+
state: { count: number };
|
|
158
|
+
commands: { clear(): boolean };
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- Из карты выводятся тип снимка в `useRichTextToolState`, тип Команд в `handle.tool()` и Опции `tool()` встроенного. Опции своего Инструмента `tool()` берёт из `defaults` его определения, поэтому поле `options` в карте описывает Опции для читателей карты (`RichTextTools["acme_note"]["options"]`), а `tool()` его не использует.
|
|
165
|
+
- **Типизация добровольна.** Инструмент, не дописанный в карту, работает: его Состояние и Команды имеют тип `unknown`.
|
|
166
|
+
- Файл с `declare module` должен быть модулем (в нём есть `import` или `export`); иначе объявление не дополняет карту, а заводит свой модуль. Положите его рядом с определением Инструмента.
|
|
167
|
+
- Карту не стоит путать с типизацией движка. Кто типизирует Часть для движка через `declare module "@tiptap/core"` (`Commands`, `Storage`), продолжает так делать: это слой движка и его мажор, а `RichTextTools` — слой кита.
|
|
168
|
+
|
|
169
|
+
### Сквозной пример
|
|
170
|
+
|
|
171
|
+
Один набор `tools` с настройками и свой Инструмент в серверно-безопасном модуле:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
// editor-tools.ts: ни Tiptap, ни React в рантайме, модуль годится и для сервера.
|
|
175
|
+
import type { Editor } from "@tiptap/core";
|
|
176
|
+
import { RICH_TEXT_PRESETS, defineTool, tool } from "@olddevs/ui/editor";
|
|
177
|
+
import { MentionIcon } from "@olddevs/ui/icons";
|
|
178
|
+
|
|
179
|
+
// Загрузчик — одна функция на модуль: промис кэшируется на ней.
|
|
180
|
+
const loadEngine = () => import("./mention-engine.js").then((module) => module.engine);
|
|
181
|
+
|
|
182
|
+
function countMentions(editor: Editor): number {
|
|
183
|
+
let count = 0;
|
|
184
|
+
editor.state.doc.descendants((node) => {
|
|
185
|
+
if (node.type.name === "acme_mention") count += 1;
|
|
186
|
+
});
|
|
187
|
+
return count;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export const mention = defineTool({
|
|
191
|
+
name: "acme_mention",
|
|
192
|
+
// Умолчания: из них выводится тип Опций.
|
|
193
|
+
defaults: { labels: { insert: "Mention" }, limit: 5 },
|
|
194
|
+
create: (options) => ({
|
|
195
|
+
engine: loadEngine,
|
|
196
|
+
blocks: {
|
|
197
|
+
acme_mention: {
|
|
198
|
+
kind: "inline",
|
|
199
|
+
attributes: { acme_id: { kind: "pattern", pattern: "[0-9]+" } },
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
insertItems: [
|
|
203
|
+
{
|
|
204
|
+
section: "insert",
|
|
205
|
+
label: options.labels.insert,
|
|
206
|
+
icon: MentionIcon,
|
|
207
|
+
run: (chain) => chain.insertContent({ type: "acme_mention", attrs: { acme_id: null } }),
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
}),
|
|
211
|
+
// Состояние: данные, которые экран читает хуком.
|
|
212
|
+
state: ({ editor }) => ({ count: countMentions(editor) }),
|
|
213
|
+
// Команды: тонкие обёртки над движком, возвращают boolean.
|
|
214
|
+
commands: ({ editor, options }) => ({
|
|
215
|
+
insert: (id: number) =>
|
|
216
|
+
countMentions(editor) < options.limit &&
|
|
217
|
+
editor.commands.insertContent({ type: "acme_mention", attrs: { acme_id: String(id) } }),
|
|
218
|
+
}),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Типы Состояния и Команд для `useRichTextToolState` и `handle.tool()`.
|
|
222
|
+
declare module "@olddevs/ui/editor" {
|
|
223
|
+
interface RichTextTools {
|
|
224
|
+
acme_mention: {
|
|
225
|
+
options: { labels: { insert: string }; limit: number };
|
|
226
|
+
state: { count: number };
|
|
227
|
+
commands: { insert(id: number): boolean };
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Набор собирается один раз на модуль: `tools` читается при создании движка.
|
|
233
|
+
export const EDITOR_TOOLS = [
|
|
234
|
+
...RICH_TEXT_PRESETS.full,
|
|
235
|
+
tool("search", { matchCase: true }),
|
|
236
|
+
tool("toc", { offsetVar: "--header-h", levels: [2, 3] }),
|
|
237
|
+
tool(mention, { limit: 10, labels: { insert: "Отметить коллегу" } }),
|
|
238
|
+
];
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Экран читает Состояние всех трёх Инструментов и зовёт их Команды:
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
"use client";
|
|
245
|
+
import { useRef } from "react";
|
|
246
|
+
import { RichTextEditor, useRichTextToolState } from "@olddevs/ui/editor";
|
|
247
|
+
import type { RichTextEditorHandle } from "@olddevs/ui/editor";
|
|
248
|
+
import { EDITOR_TOOLS } from "./editor-tools";
|
|
249
|
+
|
|
250
|
+
export function ArticleBody({ html }: { html: string }) {
|
|
251
|
+
const ref = useRef<RichTextEditorHandle>(null);
|
|
252
|
+
const search = useRichTextToolState(ref, "search");
|
|
253
|
+
const toc = useRichTextToolState(ref, "toc");
|
|
254
|
+
const mentions = useRichTextToolState(ref, "acme_mention", (state) => state.count);
|
|
255
|
+
|
|
256
|
+
return (
|
|
257
|
+
<>
|
|
258
|
+
<nav aria-label="Разделы">
|
|
259
|
+
{toc?.headings.map((heading) => (
|
|
260
|
+
<button
|
|
261
|
+
key={heading.index}
|
|
262
|
+
type="button"
|
|
263
|
+
aria-current={heading.index === toc.current ? "location" : undefined}
|
|
264
|
+
onClick={() => ref.current?.tool("toc")?.jumpTo(heading.index)}
|
|
265
|
+
>
|
|
266
|
+
{heading.text}
|
|
267
|
+
</button>
|
|
268
|
+
))}
|
|
269
|
+
</nav>
|
|
270
|
+
<button type="button" onClick={() => ref.current?.tool("search")?.open()}>
|
|
271
|
+
Найти
|
|
272
|
+
</button>
|
|
273
|
+
<button
|
|
274
|
+
type="button"
|
|
275
|
+
// Клик по кнопке вне поля не должен уводить фокус из Документа.
|
|
276
|
+
onMouseDown={(event) => event.preventDefault()}
|
|
277
|
+
onClick={() => ref.current?.tool("acme_mention")?.insert(1)}
|
|
278
|
+
>
|
|
279
|
+
Упомянуть ({mentions ?? 0})
|
|
280
|
+
</button>
|
|
281
|
+
<RichTextEditor
|
|
282
|
+
ref={ref}
|
|
283
|
+
aria-label="Текст статьи"
|
|
284
|
+
tools={EDITOR_TOOLS}
|
|
285
|
+
defaultValue={html}
|
|
286
|
+
status={
|
|
287
|
+
search?.open && search.query ? (
|
|
288
|
+
<span>
|
|
289
|
+
{search.total ? `${search.current + 1} из ${search.total}` : "Нет совпадений"}
|
|
290
|
+
</span>
|
|
291
|
+
) : null
|
|
292
|
+
}
|
|
293
|
+
/>
|
|
294
|
+
</>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Что здесь важно. Запись `tool("toc", …)` включила Оглавление без строки `"toc"`, а `tool("search", …)` заменила Поиск из Пресета. `useRichTextToolState(ref, "acme_mention", …)` и `tool("acme_mention")` типизированы благодаря `declare module`; без него вернулся бы `unknown`. Опции `mention` в `EDITOR_TOOLS` — `limit: 10` вместо умолчания 5. Они читаются один раз, при создании движка, и `insert` видит именно их.
|
|
300
|
+
|
|
104
301
|
## Загрузчики, Готовность и Скелет
|
|
105
302
|
|
|
106
303
|
Часть для движка можно отдать Загрузчиком — `engine: () => import("./engine.js").then((m) => m.engine)`: модуль с описанием тогда импортирует и сервер, а Tiptap уходит в отдельный чанк. Загрузка стартует с первого рендера в браузере, промис кэшируется на функции Загрузчика (поэтому Загрузчик — одна функция на модуль). **Готовность** — движок создан и Загрузчики всех включённых Инструментов, своих и тяжёлых встроенных (`codeBlock`, `table`), отработали. До неё поле — **Скелет**: рамка, место Панели и Документ, очищенный Схемой, без ввода и без прыжка высоты; на сервере — рамка без Документа. Сбой Загрузчика — ошибка поля с «Повторить», Документ остаётся виден. Подробно — [«Загрузчик Части для движка»](../components/inputs/rich-text-editor.md#загрузчик-части-для-движка) и [«Скелет»](../components/inputs/rich-text-editor.md#скелет).
|
|
@@ -120,10 +317,11 @@ import { sanitizeRichText } from "@olddevs/ui/editor/schema";
|
|
|
120
317
|
- описание своего Инструмента (`CustomTool` и его поля, в том числе `trigger`), UI-записи и их Отведённые места, правила Схемы (`CustomBlock`, `CustomMark`, `CustomAttribute`), Признаки Блока;
|
|
121
318
|
- HTML-соглашение (`data-block`, `data-mark`, `data-<префикс>_<имя>`);
|
|
122
319
|
- методы Хэндла, `useRichTextEditorState`, колбэки `onReady`, `onFocus`, `onBlur`, проп `status`, команда `showNotice` и тип `FieldNotice`;
|
|
320
|
+
- Опции, Состояние и Команды (ADR-0069): карта `RichTextTools`, `tool()`, `defineTool()` и его типы (`ToolDefinition`, `ConfiguredTool`), `useRichTextToolState`, `handle.tool()`, а также поля Опций, Состояния и Команд `search` и `toc`;
|
|
123
321
|
- третий аргумент `sanitizeRichText` и `richTextSchema`, проп `tools` и Остров у `RichTextView`, `markRange`;
|
|
124
322
|
- имена встроенных Инструментов (`ToolName`) и сам факт Пресетов-массивов `RICH_TEXT_PRESETS` — но не их состав (ниже).
|
|
125
323
|
|
|
126
|
-
**Не входят:** DOM и классы кита вокруг Отведённого места; id встроенных кнопок и пунктов; внутреннее описание встроенных Инструментов (надмножество публичной формы) — наружу оно не экспортируется; точное место записи внутри поверхности; вид Скелета; имена файлов и чанков в `dist`, как любой путь внутри пакета ([«Публичный контракт»](../conventions.md#публичный-контракт)). API сырого `editor` и Части для движка — это API Tiptap, он следует мажору движка.
|
|
324
|
+
**Не входят:** DOM и классы кита вокруг Отведённого места; id встроенных кнопок и пунктов; внутреннее описание встроенных Инструментов (надмножество публичной формы) — наружу оно не экспортируется; точное место записи внутри поверхности; вид Скелета; форма Настроенного Инструмента изнутри (запись непрозрачна, её поля читает кит); момент прокрутки после Команд Поиска (она происходит чуть позже вызова); имена файлов и чанков в `dist`, как любой путь внутри пакета ([«Публичный контракт»](../conventions.md#публичный-контракт)). API сырого `editor` и Части для движка — это API Tiptap, он следует мажору движка.
|
|
127
325
|
|
|
128
326
|
**Сохранённый Документ не ломают ни minor, ни patch.** HTML-соглашение заморожено: `data-block` и `data-mark` расширяются только добавлением. Если форму придётся сменить, это мажор, и до следующего мажора кит читает обе формы — и разбором движка, и санитайзером.
|
|
129
327
|
|
|
@@ -140,6 +338,8 @@ import { sanitizeRichText } from "@olddevs/ui/editor/schema";
|
|
|
140
338
|
| Удаление Инструмента из Пресета, переименование Инструмента | major |
|
|
141
339
|
| Удаление публичного имени | major, после `@deprecated` в minor (ниже) |
|
|
142
340
|
| Новое необязательное поле, новый вид записи, метод Хэндла или колбэк | minor |
|
|
341
|
+
| Новое поле Опций или Состояния, новая Команда, опция у другого встроенного Инструмента | minor |
|
|
342
|
+
| Удаление поля Опций, Состояния или Команды, смена его смысла | major |
|
|
143
343
|
| Новый встроенный Инструмент в Пресете | minor, строка changeset выделена жирным |
|
|
144
344
|
| Сдвиг Отведённого места внутри поверхности | minor со строкой changeset |
|
|
145
345
|
| Подъём нижней границы Tiptap внутри мажора | minor |
|
|
@@ -149,7 +349,7 @@ import { sanitizeRichText } from "@olddevs/ui/editor/schema";
|
|
|
149
349
|
- **Мажор Tiptap — мажор кита.** Кит поддерживает один мажор Tiptap за раз; движка нет в манифесте кита, и диапазон версий (`^3.31.4`) держит потребитель по странице [RichTextEditor](../components/inputs/rich-text-editor.md#установка).
|
|
150
350
|
- **Пресет** — «то, что кит считает полным набором», а не замороженный список. Кому нужен стабильный состав, передаёт точный список имён.
|
|
151
351
|
- **Ужесточение очистки** выходит заметкой в [migration-notes](migration-notes.md): что теперь срезается и как найти такие Документы. Ослабление очистки — отдельное решение, а не вопрос версии.
|
|
152
|
-
- **Сначала пометка, потом удаление.** Замена выходит в minor, старое получает `@deprecated` и одно предупреждение в разработке, удаляется следующим мажором.
|
|
352
|
+
- **Сначала пометка, потом удаление.** Замена выходит в minor, старое получает `@deprecated` и одно предупреждение в разработке, удаляется следующим мажором. Так помечен `handle.openSearch()`: замена — `handle.tool("search")?.open()`, удаление — в 5.0, запись в [migration-notes](migration-notes.md).
|
|
153
353
|
|
|
154
354
|
## Рецепты
|
|
155
355
|
|
|
@@ -296,6 +496,8 @@ export const EDITOR_TOOLS = [
|
|
|
296
496
|
|
|
297
497
|
Пустая подпись не роняет поле: в разработке — `console.error` с именем Инструмента и записи, а в имя и подсказку встаёт имя Инструмента.
|
|
298
498
|
|
|
499
|
+
Эту же фабрику без ручного слияния опций даёт `defineTool`: умолчания в `defaults`, настройка — `tool(definition, options)` (раздел «Опции, Состояние и Команды»). Фабрика остаётся рабочим вариантом для Инструмента без Состояния и Команд.
|
|
500
|
+
|
|
299
501
|
### `modulepreload` для ленивых чанков
|
|
300
502
|
|
|
301
503
|
Предзагрузки кит не публикует. Чанки Частей для движка (своих Загрузчиков и встроенных `codeBlock` и `table` в `full`) начинают грузиться с первого рендера Редактора, и это время закрывает Скелет. Чтобы чанк пришёл вместе со страницей, поставьте на него `<link rel="modulepreload">` (в React 19 — `preloadModule(url)` из `react-dom`); URL с хешем берите из манифеста своего сборщика: имена чанков кита в контракт не входят. Пример — [RichTextEditor, «Загрузчик Части для движка»](../components/inputs/rich-text-editor.md#загрузчик-части-для-движка); цена сценариев «старт» и «полный» — [«Размер импорта»](../getting-started.md#размер-импорта).
|
|
@@ -303,6 +505,6 @@ export const EDITOR_TOOLS = [
|
|
|
303
505
|
## Ссылки
|
|
304
506
|
|
|
305
507
|
- Страницы: [RichTextEditor](../components/inputs/rich-text-editor.md), [RichTextView](../components/content/rich-text-view.md), [словарь Редактора](../glossary.md#редактор), [migration-notes](migration-notes.md).
|
|
306
|
-
- Решения: ADR-0058 — API расширения, ADR-0050 — Редактор.
|
|
307
|
-
- Исходник: вход `src/editor/index.ts`, публичная форма своего Инструмента `src/editor/core/customTool.ts`, проверка массива `tools` и проводка `src/editor/core/customToolEngine.ts`, Хэндл `src/editor/core/handle.ts`,
|
|
308
|
-
- Тесты: `tests/rich-text-editor-custom-tool.test.tsx` (описание, Схема, проверка массива `tools`), `tests/rich-text-editor-custom-button.test.tsx`, `tests/rich-text-editor-custom-ui.test.tsx` и `tests/rich-text-editor-text-card.test.tsx` (UI-записи), `tests/rich-text-editor-handle.test.tsx` и `tests/rich-text-editor-handle-ssr.test.tsx` (Хэндл и хук), `tests/rich-text-editor-events.test.tsx` (колбэки, порядок вставки и drop), `tests/rich-text-editor-notice.test.tsx` (Строка статуса и Сообщение поля), `tests/rich-text-editor-loaders.test.tsx`, `tests/rich-text-editor-skeleton.test.tsx` и `tests/rich-text-editor-lazy-builtins.test.tsx` (Загрузчики, Скелет, ленивые встроенные), `tests/rich-text-editor-atom-text.test.tsx` (Текст атома), `tests/rich-text-editor-custom-markdown.test.tsx`, `tests/editor-sanitize-tools.test.tsx`, `tests/rich-text-view-island.test.tsx`, `tests/editor-entry.test.ts
|
|
508
|
+
- Решения: ADR-0058 — API расширения, ADR-0069 — Опции, Состояние и Команды Инструмента, ADR-0050 — Редактор.
|
|
509
|
+
- Исходник: вход `src/editor/index.ts`, публичная форма своего Инструмента `src/editor/core/customTool.ts`, проверка массива `tools` и проводка `src/editor/core/customToolEngine.ts`, Хэндл `src/editor/core/handle.ts`, хуки `src/editor/hooks/useRichTextEditorState.ts` и `src/editor/hooks/useRichTextToolState.ts`, Опции и карта `src/editor/core/toolOptions.ts`, типы Команд `src/editor/core/toolCommands.ts` и Состояния `src/editor/core/toolState.ts`, Сообщение поля `src/editor/core/fieldNotice.ts`, Схема и очистка `src/editor/core/schema.ts`, Острова `src/editor/core/viewIsland.ts`.
|
|
510
|
+
- Тесты: `tests/rich-text-editor-custom-tool.test.tsx` (описание, Схема, проверка массива `tools`), `tests/rich-text-editor-custom-button.test.tsx`, `tests/rich-text-editor-custom-ui.test.tsx` и `tests/rich-text-editor-text-card.test.tsx` (UI-записи), `tests/rich-text-editor-handle.test.tsx` и `tests/rich-text-editor-handle-ssr.test.tsx` (Хэндл и хук), `tests/rich-text-editor-events.test.tsx` (колбэки, порядок вставки и drop), `tests/rich-text-editor-notice.test.tsx` (Строка статуса и Сообщение поля), `tests/rich-text-editor-loaders.test.tsx`, `tests/rich-text-editor-skeleton.test.tsx` и `tests/rich-text-editor-lazy-builtins.test.tsx` (Загрузчики, Скелет, ленивые встроенные), `tests/rich-text-editor-atom-text.test.tsx` (Текст атома), `tests/rich-text-editor-custom-markdown.test.tsx`, `tests/editor-sanitize-tools.test.tsx`, `tests/rich-text-view-island.test.tsx`, `tests/editor-entry.test.ts`, `tests/rich-text-editor-tool-options.test.tsx`, `tests/rich-text-editor-tool-state.test.tsx`, `tests/rich-text-editor-tool-commands.test.tsx` и `tests/rich-text-editor-define-tool.test.tsx` (Опции, Состояние, Команды, `defineTool`), `tests/editor-tool-options-types.test.ts`, `tests/editor-tool-state-types.test.ts`, `tests/editor-tool-commands-types.test.ts` и `tests/editor-define-tool-types.test.ts` (типы и карта); тестовые Инструменты — `tests/editor-test-tools.ts`.
|