@olddevs/ui 4.5.1 → 4.6.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 +18 -0
- package/dist/editor/RichTextEditor.d.ts +2 -2
- package/dist/editor/RichTextEditor.d.ts.map +1 -1
- package/dist/editor/RichTextEditor.js +3 -0
- package/dist/editor/RichTextEditor.js.map +1 -1
- package/dist/editor/core/blockTypes.d.ts +2 -8
- package/dist/editor/core/blockTypes.d.ts.map +1 -1
- package/dist/editor/core/blockTypes.js +11 -54
- package/dist/editor/core/blockTypes.js.map +1 -1
- package/dist/editor/core/containers.d.ts.map +1 -1
- package/dist/editor/core/containers.js +1 -0
- package/dist/editor/core/containers.js.map +1 -1
- package/dist/editor/core/customMarkdown.js +1 -1
- package/dist/editor/core/customMarkdown.js.map +1 -1
- package/dist/editor/core/customTool.d.ts +6 -6
- package/dist/editor/core/customTool.d.ts.map +1 -1
- package/dist/editor/core/customToolEngine.d.ts +2 -2
- package/dist/editor/core/extensions.d.ts +7 -2
- package/dist/editor/core/extensions.d.ts.map +1 -1
- package/dist/editor/core/extensions.js +96 -64
- package/dist/editor/core/extensions.js.map +1 -1
- package/dist/editor/core/fieldNotice.d.ts +1 -1
- package/dist/editor/core/insertItems.d.ts +5 -28
- package/dist/editor/core/insertItems.d.ts.map +1 -1
- package/dist/editor/core/insertItems.js +3 -94
- package/dist/editor/core/insertItems.js.map +1 -1
- package/dist/editor/core/insertMenuTool.d.ts +13 -0
- package/dist/editor/core/insertMenuTool.d.ts.map +1 -0
- package/dist/editor/core/insertMenuTool.js +20 -0
- package/dist/editor/core/insertMenuTool.js.map +1 -0
- package/dist/editor/core/schema.d.ts +20 -5
- package/dist/editor/core/schema.d.ts.map +1 -1
- package/dist/editor/core/schema.js +18 -3
- package/dist/editor/core/schema.js.map +1 -1
- package/dist/editor/core/shortcuts.d.ts +56 -5
- package/dist/editor/core/shortcuts.d.ts.map +1 -1
- package/dist/editor/core/shortcuts.js +91 -156
- package/dist/editor/core/shortcuts.js.map +1 -1
- package/dist/editor/core/skeleton.d.ts +1 -1
- package/dist/editor/core/skeleton.d.ts.map +1 -1
- package/dist/editor/core/skeleton.js +1 -0
- package/dist/editor/core/skeleton.js.map +1 -1
- package/dist/editor/core/toolButtons.d.ts +3 -3
- package/dist/editor/core/toolDefinition.d.ts +3 -3
- package/dist/editor/core/toolDescriptor.d.ts +49 -30
- package/dist/editor/core/toolDescriptor.d.ts.map +1 -1
- package/dist/editor/core/toolDescriptor.js +2 -5
- package/dist/editor/core/toolDescriptor.js.map +1 -1
- package/dist/editor/core/toolLoader.d.ts +1 -1
- package/dist/editor/core/toolLoader.d.ts.map +1 -1
- package/dist/editor/core/toolOptions.d.ts +19 -2
- package/dist/editor/core/toolOptions.d.ts.map +1 -1
- package/dist/editor/core/toolOptions.js.map +1 -1
- package/dist/editor/core/tools.d.ts +10 -2
- package/dist/editor/core/tools.d.ts.map +1 -1
- package/dist/editor/core/tools.js +1 -0
- package/dist/editor/core/tools.js.map +1 -1
- package/dist/editor/features/builtins.d.ts +6 -8
- package/dist/editor/features/builtins.d.ts.map +1 -1
- package/dist/editor/features/builtins.js +48 -21
- package/dist/editor/features/builtins.js.map +1 -1
- package/dist/editor/features/code/codeBlockTool.d.ts +5 -6
- package/dist/editor/features/code/codeBlockTool.d.ts.map +1 -1
- package/dist/editor/features/code/codeBlockTool.js +10 -1
- package/dist/editor/features/code/codeBlockTool.js.map +1 -1
- package/dist/editor/features/color/colorTools.d.ts +14 -0
- package/dist/editor/features/color/colorTools.d.ts.map +1 -0
- package/dist/editor/features/color/colorTools.js +39 -0
- package/dist/editor/features/color/colorTools.js.map +1 -0
- package/dist/editor/features/details/details.d.ts +68 -0
- package/dist/editor/features/details/details.d.ts.map +1 -0
- package/dist/editor/features/details/details.js +237 -0
- package/dist/editor/features/details/details.js.map +1 -0
- package/dist/editor/features/details/detailsMarkdown.d.ts +17 -0
- package/dist/editor/features/details/detailsMarkdown.d.ts.map +1 -0
- package/dist/editor/features/details/detailsMarkdown.js +84 -0
- package/dist/editor/features/details/detailsMarkdown.js.map +1 -0
- package/dist/editor/features/details/detailsTool.d.ts +16 -0
- package/dist/editor/features/details/detailsTool.d.ts.map +1 -0
- package/dist/editor/features/details/detailsTool.js +39 -0
- package/dist/editor/features/details/detailsTool.js.map +1 -0
- package/dist/editor/features/emoji/emojiTool.d.ts +1 -1
- package/dist/editor/features/heading/headingTool.d.ts +18 -0
- package/dist/editor/features/heading/headingTool.d.ts.map +1 -0
- package/dist/editor/features/heading/headingTool.js +59 -0
- package/dist/editor/features/heading/headingTool.js.map +1 -0
- package/dist/editor/features/history/historyTool.d.ts +15 -0
- package/dist/editor/features/history/historyTool.d.ts.map +1 -0
- package/dist/editor/features/history/historyTool.js +28 -0
- package/dist/editor/features/history/historyTool.js.map +1 -0
- package/dist/editor/features/image/imageTool.d.ts +19 -0
- package/dist/editor/features/image/imageTool.d.ts.map +1 -0
- package/dist/editor/features/image/imageTool.js +28 -0
- package/dist/editor/features/image/imageTool.js.map +1 -0
- package/dist/editor/features/layout/layoutTool.d.ts +11 -0
- package/dist/editor/features/layout/layoutTool.d.ts.map +1 -0
- package/dist/editor/features/layout/layoutTool.js +20 -0
- package/dist/editor/features/layout/layoutTool.js.map +1 -0
- package/dist/editor/features/lists/listTools.d.ts +15 -0
- package/dist/editor/features/lists/listTools.d.ts.map +1 -0
- package/dist/editor/features/lists/listTools.js +78 -0
- package/dist/editor/features/lists/listTools.js.map +1 -0
- package/dist/editor/features/marks/markTools.d.ts +16 -0
- package/dist/editor/features/marks/markTools.d.ts.map +1 -0
- package/dist/editor/features/marks/markTools.js +43 -0
- package/dist/editor/features/marks/markTools.js.map +1 -0
- package/dist/editor/features/search/searchTool.d.ts +1 -1
- package/dist/editor/features/table/tableTool.d.ts +3 -3
- package/dist/editor/features/wrapper/wrapperTool.d.ts +11 -0
- package/dist/editor/features/wrapper/wrapperTool.d.ts.map +1 -0
- package/dist/editor/features/wrapper/wrapperTool.js +32 -0
- package/dist/editor/features/wrapper/wrapperTool.js.map +1 -0
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.d.ts.map +1 -1
- package/dist/editor/index.js.map +1 -1
- package/dist/editor/schema-entry.d.ts +1 -1
- package/dist/editor/styles/prose.d.ts.map +1 -1
- package/dist/editor/styles/prose.js +26 -0
- package/dist/editor/styles/prose.js.map +1 -1
- package/dist/editor/ui/EditorToolbar.d.ts.map +1 -1
- package/dist/editor/ui/EditorToolbar.js +5 -38
- package/dist/editor/ui/EditorToolbar.js.map +1 -1
- package/dist/editor/ui/SelectionBubble.d.ts.map +1 -1
- package/dist/editor/ui/SelectionBubble.js +1 -1
- package/dist/editor/ui/SelectionBubble.js.map +1 -1
- package/dist/editor/ui/ToolButtonView.d.ts.map +1 -1
- package/dist/editor/ui/ToolButtonView.js +1 -1
- package/dist/editor/ui/ToolButtonView.js.map +1 -1
- package/dist/i18n/defaults.d.ts.map +1 -1
- package/dist/i18n/defaults.js +3 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/messages.d.ts +16 -1
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/icons.d.ts +8 -1
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +1 -1
- package/dist/icons.js.map +1 -1
- package/dist/locales/ru.d.ts.map +1 -1
- package/dist/locales/ru.js +3 -0
- package/dist/locales/ru.js.map +1 -1
- package/docs/api/editor-extension.en.md +56 -56
- package/docs/api/editor-extension.md +55 -55
- package/docs/api/messages.en.md +4 -1
- package/docs/api/messages.md +3 -0
- package/docs/api/migration-notes.en.md +3 -3
- package/docs/api/migration-notes.md +2 -2
- package/docs/components/content/rich-text-view.en.md +18 -12
- package/docs/components/content/rich-text-view.md +17 -11
- package/docs/components/inputs/rich-text-editor.en.md +140 -87
- package/docs/components/inputs/rich-text-editor.md +139 -86
- package/docs/conventions.en.md +2 -2
- package/docs/conventions.md +1 -1
- package/docs/getting-started.en.md +9 -9
- package/docs/getting-started.md +8 -8
- package/docs/glossary.en.md +80 -52
- package/docs/glossary.md +79 -51
- package/docs/icons.en.md +4 -1
- package/docs/icons.md +3 -0
- package/package.json +2 -1
- package/src/editor/RichTextEditor.tsx +9 -6
- package/src/editor/core/atomText.ts +2 -2
- package/src/editor/core/blockTypes.ts +19 -83
- package/src/editor/core/containers.ts +4 -2
- package/src/editor/core/customMarkdown.ts +2 -2
- package/src/editor/core/customTool.ts +6 -6
- package/src/editor/core/customToolEngine.ts +9 -9
- package/src/editor/core/extensions.ts +165 -110
- package/src/editor/core/fieldNotice.ts +1 -1
- package/src/editor/core/handle.ts +2 -2
- package/src/editor/core/insertItems.ts +12 -158
- package/src/editor/core/insertMenuTool.ts +32 -0
- package/src/editor/core/jsonDocument.ts +1 -1
- package/src/editor/core/schema.ts +42 -7
- package/src/editor/core/shortcuts.ts +116 -177
- package/src/editor/core/skeleton.ts +4 -3
- package/src/editor/core/toolButtons.ts +3 -3
- package/src/editor/core/toolDefinition.ts +4 -4
- package/src/editor/core/toolDescriptor.ts +65 -52
- package/src/editor/core/toolLoader.ts +3 -3
- package/src/editor/core/toolOptions.ts +21 -2
- package/src/editor/core/tools.ts +10 -1
- package/src/editor/core/viewIsland.ts +1 -1
- package/src/editor/features/builtins.ts +76 -41
- package/src/editor/features/code/codeBlockTool.ts +16 -8
- package/src/editor/features/color/colorTools.ts +68 -0
- package/src/editor/features/details/details.ts +389 -0
- package/src/editor/features/details/detailsMarkdown.ts +158 -0
- package/src/editor/features/details/detailsTool.ts +69 -0
- package/src/editor/features/emoji/emojiTool.tsx +1 -1
- package/src/editor/features/heading/headingTool.ts +96 -0
- package/src/editor/features/history/historyTool.ts +42 -0
- package/src/editor/features/image/imageTool.ts +49 -0
- package/src/editor/features/layout/layoutTool.ts +40 -0
- package/src/editor/features/lists/listTools.ts +112 -0
- package/src/editor/features/marks/markTools.ts +78 -0
- package/src/editor/features/search/searchTool.ts +1 -1
- package/src/editor/features/table/tableTool.ts +3 -3
- package/src/editor/features/toc/tocTool.ts +1 -1
- package/src/editor/features/wrapper/wrapperTool.ts +52 -0
- package/src/editor/index.ts +1 -0
- package/src/editor/schema-entry.ts +1 -1
- package/src/editor/styles/prose.ts +49 -0
- package/src/editor/ui/EditorToolbar.tsx +7 -50
- package/src/editor/ui/SelectionBubble.tsx +2 -5
- package/src/editor/ui/ToolButtonView.tsx +2 -1
- package/src/i18n/defaults.ts +3 -0
- package/src/i18n/messages.ts +14 -1
- package/src/icons.ts +7 -0
- package/src/locales/ru.ts +3 -0
|
@@ -8,7 +8,7 @@ source: src/editor/index.ts
|
|
|
8
8
|
|
|
9
9
|
## Кратко для AI
|
|
10
10
|
|
|
11
|
-
Карта того, чем продукт расширяет `RichTextEditor` из `@olddevs/ui/editor`, и граница стабильности этого API. Единица расширения — **свой Инструмент** (`CustomTool`): описание одной возможности Редактора, которое передаётся в `tools` вперемешку с именами кита. Он несёт Блоки и Метки с правилами Схемы (это данные),
|
|
11
|
+
Карта того, чем продукт расширяет `RichTextEditor` из `@olddevs/ui/editor`, и граница стабильности этого API. Единица расширения — **свой Инструмент** (`CustomTool`): описание одной возможности Редактора, которое передаётся в `tools` вперемешку с именами кита. Он несёт Блоки и Метки с правилами Схемы (это данные), Extensions (расширения 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 {
|
|
@@ -39,39 +39,39 @@ import { sanitizeRichText } from "@olddevs/ui/editor/schema";
|
|
|
39
39
|
|
|
40
40
|
- Нужное уже есть среди Инструментов кита ([«Инструменты и Пресеты»](../components/inputs/rich-text-editor.md#инструменты-и-пресеты)): передайте имя в `tools`, своё не пишите.
|
|
41
41
|
- Чтобы спрятать встроенную кнопку или пункт, сузьте `tools`. Подменить встроенный Инструмент по имени нельзя: выключите его и поставьте свой под своим именем.
|
|
42
|
-
- Опции есть только у `search` и `
|
|
42
|
+
- Опции есть только у `search`, `toc` и `details`; `tool("bold", …)` не компилируется. Опции остальных встроенных добавляются по одному, когда о них попросят. Поменять поведение встроенного Инструмента подменой нельзя (`extend()` как у Tiptap нет): описания встроенных наружу не выходят.
|
|
43
43
|
- Сменить Опции у смонтированного поля нельзя: они читаются один раз, при создании движка, вместе с `tools`. Перемонтируйте поле ключом.
|
|
44
44
|
- Произвольный React в строке Панели и в меню нельзя: кит рисует записи сам, React допускается только в содержимом поповера (кнопки, Пункта вставки, Действия над Блоком) и в Карточке у текста.
|
|
45
|
-
- Во вставку и drop пропом вмешаться нельзя: это
|
|
45
|
+
- Во вставку и drop пропом вмешаться нельзя: это делают Extensions своего Инструмента ([«Порядок вставки и drop»](../components/inputs/rich-text-editor.md#порядок-вставки-и-drop)).
|
|
46
46
|
- Чего нет, пока потребители об этом не попросили (ADR-0058, раздел 13): `insertImage` и HTML выделения в Хэндле, `onSelectionChange`, `onPaste`, `onDrop`, `onEmpty`, `onDestroy`, события истории, сигнал об удалённых картинках, запертые Блоки шаблона, вид записи «меню», Карточки и Строка статуса в `RichTextView`. До тех пор у экрана есть `ref.current.editor` и его события.
|
|
47
47
|
- Свои символы-триггеры вроде `@` в этот перечень больше не входят: они поддержаны полем `trigger` Инструмента (ADR-0064, раздел «Символ-триггер» ниже).
|
|
48
48
|
|
|
49
49
|
## Карта API
|
|
50
50
|
|
|
51
|
-
| Что | Где в API
|
|
52
|
-
| ------------------------------------------------ |
|
|
53
|
-
| Свой Инструмент | `CustomTool` в `tools`; Пресеты — `RICH_TEXT_PRESETS`
|
|
54
|
-
| Правила Схемы и HTML-соглашение | `blocks` (`CustomBlock`), `marks` (`CustomMark`), `CustomAttribute`, `PrefixedName`
|
|
55
|
-
| Кнопка | `buttons` (`CustomToolButton`, `CustomToolPopover`, `CustomToolLabel`)
|
|
56
|
-
| Пункт вставки, Действие над Блоком | `insertItems` (`CustomInsertItem`), `blockActions` (`CustomBlockAction`)
|
|
57
|
-
| Символ-триггер | `trigger` (`CustomTrigger`, `TriggerItem`, `TriggerContext`)
|
|
58
|
-
| Карточка у текста | `textCards` (`CustomTextCard`), помощник `markRange`
|
|
59
|
-
| Хэндл и своя панель | `RichTextEditorHandle`, `useRichTextEditorState(ref, selector)`
|
|
60
|
-
| Опции встроенного Инструмента | `tool(name, options)` → `ConfiguredTool` для `search` и `
|
|
61
|
-
| Свой Инструмент с Опциями, Состоянием, Командами | `defineTool({ name, defaults, create, state, commands })` → `ToolDefinition`, `tool(definition, options)`
|
|
62
|
-
| Типы Опций, Состояния и Команд | расширяемый интерфейс `RichTextTools`
|
|
63
|
-
| Состояние Инструмента | `useRichTextToolState(ref, name, selector?)`, `SearchToolState`, `TocToolState`, `TocToolHeading`
|
|
64
|
-
| Команды Инструмента | `handle.tool(name)`, `SearchToolCommands`, `TocToolCommands`
|
|
65
|
-
| События | `onReady`, `onFocus`, `onBlur`; вставка и drop —
|
|
66
|
-
| Строка статуса и Сообщение поля | проп `status`; команда движка `showNotice`, тип `FieldNotice`
|
|
67
|
-
| Загрузчик, Готовность и Скелет | `engine: () => import(…)`
|
|
68
|
-
| Текст атома: `getText`, `maxLength`, пустота | `renderText`
|
|
69
|
-
| Сервер, Markdown, Просмотр | `sanitizeRichText(html, DOMPurify, tools)`, `richTextSchema(tools)`, `RichTextView`
|
|
70
|
-
| Остров своего Блока | `CustomBlock.island`, `CustomIslandProps`, проп `tools` у `RichTextView`
|
|
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` и `details`; `SearchToolOptions`, `TocToolOptions`, `TocLevel`, `DetailsToolOptions` | [«Опции встроенного Инструмента: `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 — через Extensions | [«События»](../components/inputs/rich-text-editor.md#события) |
|
|
66
|
+
| Строка статуса и Сообщение поля | проп `status`; команда движка `showNotice`, тип `FieldNotice` | [«Строка статуса и Сообщение поля»](../components/inputs/rich-text-editor.md#строка-статуса-и-сообщение-поля) |
|
|
67
|
+
| Загрузчик, Готовность и Скелет | `engine: () => import(…)` | [«Загрузчик Extensions»](../components/inputs/rich-text-editor.md#загрузчик-extensions), [«Скелет»](../components/inputs/rich-text-editor.md#скелет) |
|
|
68
|
+
| Текст атома: `getText`, `maxLength`, пустота | `renderText` из Extensions | [«Текст атома»](../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#остров-своего-блока) |
|
|
71
71
|
|
|
72
72
|
## Свой Инструмент и HTML-соглашение
|
|
73
73
|
|
|
74
|
-
Описание одно, внутри две части. **Правила Схемы** — обычные данные без Tiptap: их читают Редактор, серверная очистка и Просмотр.
|
|
74
|
+
Описание одно, внутри две части. **Правила Схемы** — обычные данные без Tiptap: их читают Редактор, серверная очистка и Просмотр. **Extensions** — расширения Tiptap целиком (Node, Mark, Extension, плагины ProseMirror, React-вид узла): только поведение, а разбор и вывод HTML ставит кит. Обязательны имя, Extensions и правило Схемы для каждого их Node и Mark; остальное — по умолчанию: обычный Блок без Признаков, выгрузка в Markdown его содержимым, ритм абзаца в Просмотре, без UI-записей. Инструмент может не нести Блока вовсе — например, только фильтр вставки.
|
|
75
75
|
|
|
76
76
|
Всё своё называется с Префиксом потребителя через подчёркивание: Инструмент `acme_mergeTag`, атрибут `acme_size`. В именах кита подчёркивания нет, поэтому своё и встроенное не пересекаются ни в массиве `tools`, ни в сохранённом Документе. HTML своего пишет и читает кит:
|
|
77
77
|
|
|
@@ -82,7 +82,7 @@ import { sanitizeRichText } from "@olddevs/ui/editor/schema";
|
|
|
82
82
|
| Метка | `<span data-mark="acme_comment">…</span>` |
|
|
83
83
|
| Атрибут | `data-acme_size="m"` — значение по правилу: список, шаблон или URL |
|
|
84
84
|
|
|
85
|
-
Массив `tools` проверяется один раз, при создании движка (с Загрузчиками — когда они отработали). Имя без префикса, совпадение имён, Node или Mark в
|
|
85
|
+
Массив `tools` проверяется один раз, при создании движка (с Загрузчиками — когда они отработали). Имя без префикса, совпадение имён, Node или Mark в Extensions без правила Схемы (и наоборот), запись без обязательного поля дают ошибку поля «Редактор не запустился», а не тихая потеря Блоков при первом сохранении. Неизвестная **строка** в `tools` по-прежнему отбрасывается молча.
|
|
86
86
|
|
|
87
87
|
## UI-записи
|
|
88
88
|
|
|
@@ -98,7 +98,7 @@ import { sanitizeRichText } from "@olddevs/ui/editor/schema";
|
|
|
98
98
|
- **Подпись** — строка или функция, получающая `{ locale }` (локаль `UiProvider`); каталог сообщений `UiProvider` для сторонних Инструментов не расширяется, переопределение — опциями фабрики Инструмента (рецепт ниже).
|
|
99
99
|
- **Иконка** обязательна у кнопки, пункта и Действия: компонент с контрактом `IconProps` из `@olddevs/ui/icons` или свой.
|
|
100
100
|
- **Поповер** — у кнопки, Пункта вставки и Действия над Блоком ровно одно из `run` и `popover`. Содержимое — функция, получающая `{ editor, close }` (у Действия — ещё `pos` Блока), рамку держит кит: слой оверлея, Стекло всплывающих поверхностей Редактора, стек Escape, имя (подпись записи), возврат фокуса и выделения в Документ. Карточка у текста лежит на том же Стекле, поэтому сплошной фон содержимому не нужен: он закроет подложку. При включённой в системе настройке прозрачности или контраста подложка и так станет сплошной. Поповер встаёт у кнопки (щелчок), у каретки (сочетание кнопки, пункт из «/», «+», «Добавить ›», «Формат ›» и его сочетание) или у Блока (Действие из меню Ручки и его сочетание).
|
|
101
|
-
- **Сочетание** (`shortcut`) — единственный источник: кит вешает клавишу и пишет её в подсказку, `aria-keyshortcuts` и Справочник сочетаний.
|
|
101
|
+
- **Сочетание** (`shortcut`) — единственный источник: кит вешает клавишу и пишет её в подсказку, `aria-keyshortcuts` и Справочник сочетаний. Клавиши, которые вешают Extensions, в справочник не попадают.
|
|
102
102
|
- **Выключенный Инструмент** уносит свои записи отовсюду.
|
|
103
103
|
|
|
104
104
|
## Символ-триггер
|
|
@@ -124,11 +124,11 @@ import { sanitizeRichText } from "@olddevs/ui/editor/schema";
|
|
|
124
124
|
| Состояние | Снимок, который экран читает | Кит у встроенных; поле `state` у `defineTool` | `useRichTextToolState(ref, name, selector?)` |
|
|
125
125
|
| Команды | То, что экран вызывает | Кит у встроенных; поле `commands` у `defineTool` | `ref.current?.tool(name)?.команда()` |
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
Extensions по-прежнему следуют мажору Tiptap (ADR-0058, п. 12.4). Три новые части от Tiptap не зависят: их типы и поведение подчиняются semver кита.
|
|
128
128
|
|
|
129
129
|
### `tool()`: встроенный Инструмент с Опциями
|
|
130
130
|
|
|
131
|
-
`tool(name, options)` возвращает Настроенный Инструмент — непрозрачную запись для `tools` (её поля читает только кит). Опции есть у `search` (стартовые флажки)
|
|
131
|
+
`tool(name, options)` возвращает Настроенный Инструмент — непрозрачную запись для `tools` (её поля читает только кит). Опции есть у `search` (стартовые флажки), `toc` (`offsetVar`, `levels`, `listWidth`) и `details` (`persist`: пишется ли открытость сворачиваемого Блока в Документ); их значения и пример — на странице [RichTextEditor](../components/inputs/rich-text-editor.md#опции-встроенного-инструмента-tool). Запись включает Инструмент и без имени в `tools`, а имя из Пресета ей уступает. Две записи `tool()` с одним именем — ошибка набора.
|
|
132
132
|
|
|
133
133
|
`tool(definition, options)` настраивает тем же способом определение `defineTool`; Опции берутся из его `defaults`.
|
|
134
134
|
|
|
@@ -137,17 +137,17 @@ import { sanitizeRichText } from "@olddevs/ui/editor/schema";
|
|
|
137
137
|
`defineTool({ name, defaults, create, state?, commands? })` возвращает определение — элемент `tools`, который работает и без `tool()` (с умолчаниями из `defaults`), и с ним.
|
|
138
138
|
|
|
139
139
|
- **`defaults`** — полный набор Опций. Тип Опций выводится из него, второй раз его объявлять не нужно. Литерал расширяется до своего типа (`tone: "info"` станет `string`): нужен union-тип — укажите тип значения, `tone: "info" as "info" | "warning"`.
|
|
140
|
-
- **`create(options)`** отдаёт тело описания — ту же форму, что `CustomTool`, без имени (`CustomToolBody`):
|
|
140
|
+
- **`create(options)`** отдаёт тело описания — ту же форму, что `CustomTool`, без имени (`CustomToolBody`): Extensions, правила Схемы, UI-записи. Зовётся один раз на запись `tools` (определение само или объект, который вернул `tool(definition, options)`): кит запоминает результат по паре «определение и объект Опций записи». Всё, что замкнуто в теле `create` (счётчик, кэш), поэтому общее у всех Редакторов с этой записью. Запись `tool(…)`, созданная в теле компонента, на каждом рендере новая, и Остров в `RichTextView` на каждом рендере перемонтируется: выносите набор `tools` из рендера, в константу модуля или в `useMemo`. Загрузчик Extensions держите константой модуля: кэш загрузчика живёт на самой функции, и новая функция на каждый вызов `create` загружала бы Extensions заново для каждой записи.
|
|
141
141
|
- **`state({ editor, options })`** — Состояние для `useRichTextToolState`. Её зовут при каждой транзакции и при каждом чтении хука, поэтому она должна быть быстрой и без побочных эффектов, а результат — данные без DOM-узлов. Примитивы, массивы и простые объекты сравниваются по значению, поэтому экран перерисовывается, только когда снимок изменился. `Date`, `Map`, `Set` и экземпляры классов сравниваются по ссылке: `state`, который создаёт их заново на каждый вызов, перерисует экран на каждой транзакции. Отдавайте простые данные (`date.toISOString()`) или ту же ссылку, пока данные не изменились. Нет `state` — у Инструмента нет Состояния, и хук отдаёт `null`.
|
|
142
142
|
- **`commands({ editor, options })`** — Команды для `handle.tool(name)`. Их зовут при каждом обращении к Хэндлу: только обёртки над движком, без подписок. Команда возвращает `boolean`, как у встроенных. Нет `commands` — `handle.tool(name)` отдаёт `null`.
|
|
143
143
|
- **Слияние Опций.** Вложенные простые объекты сливаются с умолчаниями, массивы и функции заменяются целиком — как `configure` у Tiptap v3. Значение `undefined` не в счёт: остаётся то, что было. Объект `defaults` не меняется, объект Опций общий для `create`, `state` и `commands`: читайте его, но не меняйте.
|
|
144
144
|
- **Опции читаются один раз**, при создании движка, вместе с набором. Чтобы сменить их, перемонтируйте Редактор ключом (`key`). Значения Опций своего Инструмента проверяет его автор: кит этого не делает.
|
|
145
|
-
- **Все правила своего Инструмента действуют** (ADR-0058): Префикс потребителя в имени, уникальность имени в `tools` (определение дважды, с `tool()` и без, — тоже ошибка набора), проверки набора, Загрузчик
|
|
145
|
+
- **Все правила своего Инструмента действуют** (ADR-0058): Префикс потребителя в имени, уникальность имени в `tools` (определение дважды, с `tool()` и без, — тоже ошибка набора), проверки набора, Загрузчик Extensions. `defineTool` — сахар над той же формой `CustomTool`: отдельного пути сборки у него нет. Обычный объект `CustomTool` в `tools` работает как раньше, без Опций, Состояния и Команд.
|
|
146
146
|
- **Сервер.** `defineTool` и `tool()` живут в модуле без Tiptap и React в рантайме (Tiptap там — только типы), поэтому набор `tools` собирается в серверном модуле, рядом с остальной конфигурацией экрана. Определение содержит функции, как и `CustomTool`, поэтому клиентскому компоненту оно отдаётся вместе с модулем (импортом), а не пропом через границу RSC. Настроенный встроенный Инструмент состоит из данных: при данных в опциях его можно передать и пропом.
|
|
147
147
|
|
|
148
148
|
### Карта `RichTextTools` и declaration merging
|
|
149
149
|
|
|
150
|
-
Типы всех трёх частей собраны в интерфейсе `RichTextTools`, как `Storage` и `Commands` у Tiptap: ключ — имя Инструмента, значение — `{ options; state; commands }`. Кит объявляет в нём `search` и `
|
|
150
|
+
Типы всех трёх частей собраны в интерфейсе `RichTextTools`, как `Storage` и `Commands` у Tiptap: ключ — имя Инструмента, значение — `{ options; state; commands }`. Кит объявляет в нём `search`, `toc` и `details` (у последнего только `options`), свой Инструмент потребитель дописывает сам:
|
|
151
151
|
|
|
152
152
|
```ts
|
|
153
153
|
declare module "@olddevs/ui/editor" {
|
|
@@ -164,7 +164,7 @@ declare module "@olddevs/ui/editor" {
|
|
|
164
164
|
- Из карты выводятся тип снимка в `useRichTextToolState`, тип Команд в `handle.tool()` и Опции `tool()` встроенного. Опции своего Инструмента `tool()` берёт из `defaults` его определения, поэтому поле `options` в карте описывает Опции для читателей карты (`RichTextTools["acme_note"]["options"]`), а `tool()` его не использует.
|
|
165
165
|
- **Типизация добровольна.** Инструмент, не дописанный в карту, работает: его Состояние и Команды имеют тип `unknown`.
|
|
166
166
|
- Файл с `declare module` должен быть модулем (в нём есть `import` или `export`); иначе объявление не дополняет карту, а заводит свой модуль. Положите его рядом с определением Инструмента.
|
|
167
|
-
- Карту не стоит путать с типизацией движка. Кто типизирует
|
|
167
|
+
- Карту не стоит путать с типизацией движка. Кто типизирует Extensions через `declare module "@tiptap/core"` (`Commands`, `Storage`), продолжает так делать: это слой движка и его мажор, а `RichTextTools` — слой кита.
|
|
168
168
|
|
|
169
169
|
### Сквозной пример
|
|
170
170
|
|
|
@@ -300,12 +300,12 @@ export function ArticleBody({ html }: { html: string }) {
|
|
|
300
300
|
|
|
301
301
|
## Загрузчики, Готовность и Скелет
|
|
302
302
|
|
|
303
|
-
|
|
303
|
+
Extensions можно отдать Загрузчиком — `engine: () => import("./engine.js").then((m) => m.engine)`: модуль с описанием тогда импортирует и сервер, а Tiptap уходит в отдельный чанк. Загрузка стартует с первого рендера в браузере, промис кэшируется на функции Загрузчика (поэтому Загрузчик — одна функция на модуль). **Готовность** — движок создан и Загрузчики всех включённых Инструментов, своих и тяжёлых встроенных (`codeBlock`, `table`), отработали. До неё поле — **Скелет**: рамка, место Панели и Документ, очищенный Схемой, без ввода и без прыжка высоты; на сервере — рамка без Документа. Сбой Загрузчика — ошибка поля с «Повторить», Документ остаётся виден. Подробно — [«Загрузчик Extensions»](../components/inputs/rich-text-editor.md#загрузчик-extensions) и [«Скелет»](../components/inputs/rich-text-editor.md#скелет).
|
|
304
304
|
|
|
305
305
|
## Сервер, Markdown, Просмотр и Остров
|
|
306
306
|
|
|
307
307
|
- **Сервер** чистит Документ тем же массивом `tools`: `sanitizeRichText(html, DOMPurify, tools)`, правила данными — `richTextSchema(tools)`. Оба есть и в лёгком `@olddevs/ui/editor/schema` без Tiptap и React — вместе с `markRange`: описание своего Инструмента с Карточкой у текста собирается и в модуле сервера. Имена Инструментов кита в `tools` белый список не сужают: очистка пропускает всё, что пропускает кит. Без третьего аргумента очистка прежняя, и свои Блоки срезаются.
|
|
308
|
-
- **Markdown** — правила в
|
|
308
|
+
- **Markdown** — правила в Extensions (`renderMarkdown`, `markdownTokenizer`, `parseMarkdown`). Без них Блок выгружается содержимым, Атом — Текстом атома, и текст не теряется.
|
|
309
309
|
- **`RichTextView`** показывает свой Блок очищенным HTML с ритмом абзаца; вид — ваш CSS по `data-block`. **Остров** — клиентский компонент в поле `island` Блока: оживает после монтирования и только если такой Блок есть в Документе; `RichTextView` для этого получает тот же `tools`.
|
|
310
310
|
|
|
311
311
|
## Стабильность
|
|
@@ -317,34 +317,34 @@ export function ArticleBody({ html }: { html: string }) {
|
|
|
317
317
|
- описание своего Инструмента (`CustomTool` и его поля, в том числе `trigger`), UI-записи и их Отведённые места, правила Схемы (`CustomBlock`, `CustomMark`, `CustomAttribute`), Признаки Блока;
|
|
318
318
|
- HTML-соглашение (`data-block`, `data-mark`, `data-<префикс>_<имя>`);
|
|
319
319
|
- методы Хэндла, `useRichTextEditorState`, колбэки `onReady`, `onFocus`, `onBlur`, проп `status`, команда `showNotice` и тип `FieldNotice`;
|
|
320
|
-
- Опции, Состояние и Команды (ADR-0069): карта `RichTextTools`, `tool()`, `defineTool()` и его типы (`ToolDefinition`, `ConfiguredTool`), `useRichTextToolState`, `handle.tool()`, а также поля Опций, Состояния и Команд `search` и `toc`;
|
|
320
|
+
- Опции, Состояние и Команды (ADR-0069): карта `RichTextTools`, `tool()`, `defineTool()` и его типы (`ToolDefinition`, `ConfiguredTool`), `useRichTextToolState`, `handle.tool()`, а также поля Опций, Состояния и Команд `search` и `toc` и Опции `details`;
|
|
321
321
|
- третий аргумент `sanitizeRichText` и `richTextSchema`, проп `tools` и Остров у `RichTextView`, `markRange`;
|
|
322
322
|
- имена встроенных Инструментов (`ToolName`) и сам факт Пресетов-массивов `RICH_TEXT_PRESETS` — но не их состав (ниже).
|
|
323
323
|
|
|
324
|
-
**Не входят:** DOM и классы кита вокруг Отведённого места; id встроенных кнопок и пунктов; внутреннее описание встроенных Инструментов (надмножество публичной формы) — наружу оно не экспортируется; точное место записи внутри поверхности; вид Скелета; форма Настроенного Инструмента изнутри (запись непрозрачна, её поля читает кит); момент прокрутки после Команд Поиска (она происходит чуть позже вызова); имена файлов и чанков в `dist`, как любой путь внутри пакета ([«Публичный контракт»](../conventions.md#публичный-контракт)). API сырого `editor` и
|
|
324
|
+
**Не входят:** DOM и классы кита вокруг Отведённого места; id встроенных кнопок и пунктов; внутреннее описание встроенных Инструментов (надмножество публичной формы) — наружу оно не экспортируется; точное место записи внутри поверхности; вид Скелета; форма Настроенного Инструмента изнутри (запись непрозрачна, её поля читает кит); момент прокрутки после Команд Поиска (она происходит чуть позже вызова); имена файлов и чанков в `dist`, как любой путь внутри пакета ([«Публичный контракт»](../conventions.md#публичный-контракт)). API сырого `editor` и Extensions — это API Tiptap, он следует мажору движка.
|
|
325
325
|
|
|
326
326
|
**Сохранённый Документ не ломают ни minor, ни patch.** HTML-соглашение заморожено: `data-block` и `data-mark` расширяются только добавлением. Если форму придётся сменить, это мажор, и до следующего мажора кит читает обе формы — и разбором движка, и санитайзером.
|
|
327
327
|
|
|
328
328
|
**Ломающим считается нарушение контракта, а не сдвиг пикселей:**
|
|
329
329
|
|
|
330
|
-
| Изменение
|
|
331
|
-
|
|
|
332
|
-
| Новое обязательное поле описания
|
|
333
|
-
| Новый Признак Блока или значение по умолчанию, меняющее поведение уже описанных Блоков
|
|
334
|
-
| Отказ от «одно Отведённое место на поверхность» или «свои записи в порядке `tools`»
|
|
335
|
-
| Перенос вида записи на другую поверхность
|
|
336
|
-
| Смена формы HTML-соглашения
|
|
337
|
-
| Новый мажор Tiptap — даже без правок в коде кита: ломаются
|
|
338
|
-
| Удаление Инструмента из Пресета, переименование Инструмента
|
|
339
|
-
| Удаление публичного имени
|
|
340
|
-
| Новое необязательное поле, новый вид записи, метод Хэндла или колбэк
|
|
341
|
-
| Новое поле Опций или Состояния, новая Команда, опция у другого встроенного Инструмента
|
|
342
|
-
| Удаление поля Опций, Состояния или Команды, смена его смысла
|
|
343
|
-
| Новый встроенный Инструмент в Пресете
|
|
344
|
-
| Сдвиг Отведённого места внутри поверхности
|
|
345
|
-
| Подъём нижней границы Tiptap внутри мажора
|
|
346
|
-
| Ужесточение очистки ради безопасности
|
|
347
|
-
| Вид Скелета, DOM и классы вокруг Отведённого места
|
|
330
|
+
| Изменение | Версия |
|
|
331
|
+
| -------------------------------------------------------------------------------------- | -------------------------------------------- |
|
|
332
|
+
| Новое обязательное поле описания | major |
|
|
333
|
+
| Новый Признак Блока или значение по умолчанию, меняющее поведение уже описанных Блоков | major |
|
|
334
|
+
| Отказ от «одно Отведённое место на поверхность» или «свои записи в порядке `tools`» | major |
|
|
335
|
+
| Перенос вида записи на другую поверхность | major |
|
|
336
|
+
| Смена формы HTML-соглашения | major, и до следующего мажора читаются обе |
|
|
337
|
+
| Новый мажор Tiptap — даже без правок в коде кита: ломаются Extensions у потребителя | major |
|
|
338
|
+
| Удаление Инструмента из Пресета, переименование Инструмента | major |
|
|
339
|
+
| Удаление публичного имени | major, после `@deprecated` в minor (ниже) |
|
|
340
|
+
| Новое необязательное поле, новый вид записи, метод Хэндла или колбэк | minor |
|
|
341
|
+
| Новое поле Опций или Состояния, новая Команда, опция у другого встроенного Инструмента | minor |
|
|
342
|
+
| Удаление поля Опций, Состояния или Команды, смена его смысла | major |
|
|
343
|
+
| Новый встроенный Инструмент в Пресете | minor, строка changeset выделена жирным |
|
|
344
|
+
| Сдвиг Отведённого места внутри поверхности | minor со строкой changeset |
|
|
345
|
+
| Подъём нижней границы Tiptap внутри мажора | minor |
|
|
346
|
+
| Ужесточение очистки ради безопасности | patch или minor с заметкой в migration-notes |
|
|
347
|
+
| Вид Скелета, DOM и классы вокруг Отведённого места | вне контракта |
|
|
348
348
|
|
|
349
349
|
- **Мажор Tiptap — мажор кита.** Кит поддерживает один мажор Tiptap за раз; движка нет в манифесте кита, и диапазон версий (`^3.31.4`) держит потребитель по странице [RichTextEditor](../components/inputs/rich-text-editor.md#установка).
|
|
350
350
|
- **Пресет** — «то, что кит считает полным набором», а не замороженный список. Кому нужен стабильный состав, передаёт точный список имён.
|
|
@@ -500,7 +500,7 @@ export const EDITOR_TOOLS = [
|
|
|
500
500
|
|
|
501
501
|
### `modulepreload` для ленивых чанков
|
|
502
502
|
|
|
503
|
-
Предзагрузки кит не публикует. Чанки
|
|
503
|
+
Предзагрузки кит не публикует. Чанки Extensions (своих Загрузчиков и встроенных `codeBlock` и `table` в `full`) начинают грузиться с первого рендера Редактора, и это время закрывает Скелет. Чтобы чанк пришёл вместе со страницей, поставьте на него `<link rel="modulepreload">` (в React 19 — `preloadModule(url)` из `react-dom`); URL с хешем берите из манифеста своего сборщика: имена чанков кита в контракт не входят. Пример — [RichTextEditor, «Загрузчик Extensions»](../components/inputs/rich-text-editor.md#загрузчик-extensions); цена сценариев «старт» и «полный» — [«Размер импорта»](../getting-started.md#размер-импорта).
|
|
504
504
|
|
|
505
505
|
## Ссылки
|
|
506
506
|
|
package/docs/api/messages.en.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
sourceHash:
|
|
2
|
+
sourceHash: 9e3157586a55
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Message catalog `UiMessages`
|
|
@@ -276,6 +276,9 @@ The Editor's strings ([rich-text-editor.md](../components/inputs/rich-text-edito
|
|
|
276
276
|
| `wrapperToneName.neutral` | string | Important | Важно |
|
|
277
277
|
| `wrapperToneName.warning` | string | Warning | Внимание |
|
|
278
278
|
| `wrapperToneName.danger` | string | Caution | Осторожно |
|
|
279
|
+
| `details` | string | Collapsible block | Сворачиваемый блок |
|
|
280
|
+
| `unwrapDetails` | string | Remove collapsing | Снять сворачивание |
|
|
281
|
+
| `detailsToggle` | function | — | — |
|
|
279
282
|
| `emoji` | string | Emoji | Эмодзи |
|
|
280
283
|
| `emojiGrid` | string | Choose an emoji | Выберите эмодзи |
|
|
281
284
|
| `emojiCategory.favorites` | string | Favorites | Избранное |
|
package/docs/api/messages.md
CHANGED
|
@@ -278,6 +278,9 @@
|
|
|
278
278
|
| `wrapperToneName.neutral` | строка | Important | Важно |
|
|
279
279
|
| `wrapperToneName.warning` | строка | Warning | Внимание |
|
|
280
280
|
| `wrapperToneName.danger` | строка | Caution | Осторожно |
|
|
281
|
+
| `details` | строка | Collapsible block | Сворачиваемый блок |
|
|
282
|
+
| `unwrapDetails` | строка | Remove collapsing | Снять сворачивание |
|
|
283
|
+
| `detailsToggle` | функция | — | — |
|
|
281
284
|
| `emoji` | строка | Emoji | Эмодзи |
|
|
282
285
|
| `emojiGrid` | строка | Choose an emoji | Выберите эмодзи |
|
|
283
286
|
| `emojiCategory.favorites` | строка | Favorites | Избранное |
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
category: api
|
|
3
3
|
status: stable
|
|
4
4
|
source: src/index.ts
|
|
5
|
-
sourceHash:
|
|
5
|
+
sourceHash: 19d3eaf4181f
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Compatibility and migration notes
|
|
@@ -287,7 +287,7 @@ of `@olddevs/ui/editor`, and one requires installing packages with pnpm. The new
|
|
|
287
287
|
|
|
288
288
|
### 1. Two new peer packages
|
|
289
289
|
|
|
290
|
-
The blockquote and the divider take their
|
|
290
|
+
The blockquote and the divider take their Extensions from `@tiptap/extension-blockquote` and
|
|
291
291
|
`@tiptap/extension-horizontal-rule` — optional peer dependencies of the
|
|
292
292
|
`@olddevs/ui/editor` entry point with the same `^3.31.4` range (since 3.46.0 the engine
|
|
293
293
|
packages are no longer in the kit manifest; see the entry above).
|
|
@@ -329,7 +329,7 @@ did not fit goes away whole, together with everything after it.
|
|
|
329
329
|
|
|
330
330
|
**Action before updating.** Check the server-side length validation against the kit's rule:
|
|
331
331
|
the Document's text without markup and Block separators, an Atom by the length of its text.
|
|
332
|
-
The kit has no server-side counter: the weight of a custom Atom depends on its
|
|
332
|
+
The kit has no server-side counter: the weight of a custom Atom depends on its Extensions.
|
|
333
333
|
|
|
334
334
|
### 3. Skeleton before Readiness
|
|
335
335
|
|
|
@@ -287,7 +287,7 @@ Shift+F10 — меню ячейки. Новое: кнопки внутри яч
|
|
|
287
287
|
|
|
288
288
|
### 1. Два новых peer-пакета
|
|
289
289
|
|
|
290
|
-
Цитата и разделитель берут
|
|
290
|
+
Цитата и разделитель берут Extensions из `@tiptap/extension-blockquote` и
|
|
291
291
|
`@tiptap/extension-horizontal-rule` — опциональных peer-зависимостей входа
|
|
292
292
|
`@olddevs/ui/editor` с тем же диапазоном `^3.31.4` (с 3.46.0 пакеты движка из
|
|
293
293
|
манифеста кита убраны, см. запись выше).
|
|
@@ -329,7 +329,7 @@ pnpm add @tiptap/extension-blockquote @tiptap/extension-horizontal-rule
|
|
|
329
329
|
|
|
330
330
|
**Действие до обновления.** Серверную проверку длины сверьте с правилом кита:
|
|
331
331
|
текст Документа без разметки и разделителей Блоков, Атом — длиной своего текста.
|
|
332
|
-
Серверного счётчика у кита нет: вес своего Атома зависит от его
|
|
332
|
+
Серверного счётчика у кита нет: вес своего Атома зависит от его Extensions.
|
|
333
333
|
|
|
334
334
|
### 3. Скелет до Готовности
|
|
335
335
|
|
|
@@ -19,7 +19,7 @@ category: content
|
|
|
19
19
|
status: stable
|
|
20
20
|
source: src/editor/RichTextView.tsx
|
|
21
21
|
story: src/editor/RichTextView.stories.tsx
|
|
22
|
-
sourceHash:
|
|
22
|
+
sourceHash: bceb9f235ef0
|
|
23
23
|
---
|
|
24
24
|
|
|
25
25
|
# RichTextView
|
|
@@ -93,17 +93,17 @@ The allowlist of tags and attributes (the table mirrors `src/editor/core/schema.
|
|
|
93
93
|
|
|
94
94
|
<!-- allow-list -->
|
|
95
95
|
|
|
96
|
-
| What | Allowed
|
|
97
|
-
| ---------- |
|
|
98
|
-
| Blocks | `p`, `h1`, `h2`, `h3`, `h4`, `ul`, `ol`, `li`, `blockquote`, `hr`, `pre`, `table`, `tbody`, `tr`, `th`, `td`; `label`, `input`, `span` — only inside a checklist item; `div` — there too, plus the Layout and its columns (a Layout only at the top level, a column only directly inside it) and the Wrapper (at the top level and directly in a column, not in another Wrapper); `figure` — an image, with `img` and an optional `figcaption` inside (they do not exist outside `figure`)
|
|
99
|
-
| Marks | `strong`, `em`, `u`, `s`, `code`, `sup`, `sub`, `mark` (only with a color), `br`, `a`
|
|
100
|
-
| Attributes | `ol[start]` — only when numbering does not start at 1; `ul[data-type]`, `li[data-type]`, `li[data-checked]` — checklist; `input[type]`, `input[checked]` — its checkbox; `code[class]` — the code block language, only "language-python", "language-typescript", "language-javascript", "language-go", "language-html", "language-json", "language-yaml" or "language-markdown" (no language, no class); `span[data-color]`, `mark[data-color]` — text color and marker, only names from that tag's palette: text colors on `span`, markers on `mark`; `span[style]`, `mark[style]` — the author's custom color, only "color:#rrggbb" and "background-color:#rrggbb", and not together with "data-color"; `p[data-align]`, `h1[data-align]`, `h2[data-align]`, `h3[data-align]`, `h4[data-align]` — Block alignment, center or right; `th[colspan]`, `td[colspan]` — horizontal merge, an integer from 2 to 20; `th[rowspan]`, `td[rowspan]` — vertical, from 2 to 50; `th[data-align]`, `td[data-align]` — column alignment, center or right, on each of its cells; `a[href]` — only "http", "https" or "mailto"; `a[rel]` — always "noopener noreferrer"; `img[src]` — only absolute "http" or "https", no "data:" or "blob:"; `img[alt]` — always, empty for an image without a description; `img[data-width]` — 50, 75 or 100; `div[data-block]` — the Layout ("layout"), its column ("column") and the Wrapper ("wrapper"); `div[data-layout]` — the Layout variant, column shares in percent: "100", "50-50", "33-33-33", "25-25-25-25", "60-40" or "40-60"; `div[data-tone]` — the Wrapper tone, only on it: "neutral", "accent", "success", "warning" or "danger" (no tone, no attribute) |
|
|
96
|
+
| What | Allowed |
|
|
97
|
+
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
98
|
+
| Blocks | `p`, `h1`, `h2`, `h3`, `h4`, `ul`, `ol`, `li`, `blockquote`, `hr`, `pre`, `table`, `tbody`, `tr`, `th`, `td`; `label`, `input`, `span` — only inside a checklist item; `div` — there too, plus the Layout and its columns (a Layout only at the top level, a column only directly inside it) and the Wrapper (at the top level and directly in a column, not in another Wrapper); `details` — a collapsible Block, with `summary` and a content `div` inside it (both only directly in `details`; outside it the tags are removed); `figure` — an image, with `img` and an optional `figcaption` inside (they do not exist outside `figure`) |
|
|
99
|
+
| Marks | `strong`, `em`, `u`, `s`, `code`, `sup`, `sub`, `mark` (only with a color), `br`, `a` |
|
|
100
|
+
| Attributes | `ol[start]` — only when numbering does not start at 1; `ul[data-type]`, `li[data-type]`, `li[data-checked]` — checklist; `input[type]`, `input[checked]` — its checkbox; `code[class]` — the code block language, only "language-python", "language-typescript", "language-javascript", "language-go", "language-html", "language-json", "language-yaml" or "language-markdown" (no language, no class); `span[data-color]`, `mark[data-color]` — text color and marker, only names from that tag's palette: text colors on `span`, markers on `mark`; `span[style]`, `mark[style]` — the author's custom color, only "color:#rrggbb" and "background-color:#rrggbb", and not together with "data-color"; `p[data-align]`, `h1[data-align]`, `h2[data-align]`, `h3[data-align]`, `h4[data-align]` — Block alignment, center or right; `th[colspan]`, `td[colspan]` — horizontal merge, an integer from 2 to 20; `th[rowspan]`, `td[rowspan]` — vertical, from 2 to 50; `th[data-align]`, `td[data-align]` — column alignment, center or right, on each of its cells; `a[href]` — only "http", "https" or "mailto"; `a[rel]` — always "noopener noreferrer"; `img[src]` — only absolute "http" or "https", no "data:" or "blob:"; `img[alt]` — always, empty for an image without a description; `img[data-width]` — 50, 75 or 100; `div[data-block]` — the Layout ("layout"), its column ("column") and the Wrapper ("wrapper"); `div[data-layout]` — the Layout variant, column shares in percent: "100", "50-50", "33-33-33", "25-25-25-25", "60-40" or "40-60"; `div[data-tone]` — the Wrapper tone, only on it: "neutral", "accent", "success", "warning" or "danger" (no tone, no attribute); `div[data-type]` — the content wrapper of a collapsible Block, only the value "detailsContent" and only directly in `details`; `details[open]` — an open Block, only an empty "open" |
|
|
101
101
|
|
|
102
102
|
Tiptap writes a checklist item like this: `<li data-type="taskItem" data-checked="true"><label><input type="checkbox" checked><span></span></label><div><p>…</p></div></li>`. `sanitizeRichText` lets `input`, `label`, `span` and `div` through only inside such an item, and `input` only with `type="checkbox"`.
|
|
103
103
|
|
|
104
104
|
The Editor writes a table like this: `<table><tbody><tr><th><p>…</p></th></tr><tr><td colspan="2" data-align="center"><p>…</p></td></tr></tbody></table>`. The header row is the first row made of `th`, and a cell has only three attributes: `colspan` and `rowspan` (merging, an integer from 2 to `RICH_TEXT_SCHEMA.tableSpans`: 20 horizontally and 50 vertically; 1 is not written) and `data-align` (column alignment, `center` or `right`, on every cell of the column). A row fully covered by a merge from above stays an empty `<tr></tr>`. `thead`, `tfoot`, `colgroup` and `caption` are not in the Schema, and **by default DOMPurify removes the contents of `thead` and `colgroup` along with the tags**: the header of a third-party table written in `thead` disappears. The Editor does not write `thead`, so for its HTML this is no loss. `tbody`, `tr`, `th` and `td` outside a table are dropped by the HTML parser before sanitizing (the text stays), `style` and `colwidth` are not allowed through, and `colspan` and `rowspan` are let through only by the `sanitizeRichText` hook: on `th` and `td`, and only as digits within the Schema's limits; the bare config does not know them at all. Without such a check, `colspan="10000"` in third-party HTML would expand into 10,000 empty cells for everyone who opens the Document.
|
|
105
105
|
|
|
106
|
-
There are no other classes, `style` (except the custom color, see below), `aria-*` or `data-*` (except eight Schema names: `data-type` and `data-checked` of the checklist, `data-color` for color, `data-align` for alignment, `data-width` for image width, `data-block` and `data-layout` of the Layout, and `data-tone` of the Wrapper) in the output HTML. An `h1` appears in a Document only if the Editor has the `heading1` Tool (on by default in `full`); a screen that puts an H1 above the Editor turns this Tool off.
|
|
106
|
+
There are no other classes, `style` (except the custom color, see below), `aria-*` or `data-*` (except eight Schema names: `data-type` of the checklist and of the content wrapper of a collapsible Block, `data-checked` of the checklist, `data-color` for color, `data-align` for alignment, `data-width` for image width, `data-block` and `data-layout` of the Layout, and `data-tone` of the Wrapper) in the output HTML. An `h1` appears in a Document only if the Editor has the `heading1` Tool (on by default in `full`); a screen that puts an H1 above the Editor turns this Tool off.
|
|
107
107
|
|
|
108
108
|
**An image** is written by the Editor like this: `<figure><img src="https://…" alt="…" data-width="50"><figcaption>…</figcaption></figure>`. `figcaption` is present only when the caption is not empty, `data-width` only on an image with a set width (`50`, `75` or `100` percent of the Document width, `RICH_TEXT_SCHEMA.imageWidths`; without it, the image keeps its own size, no wider than the Document), and `alt` is always present and empty for an image without a description. The URL must be an absolute `http` or `https` URL (`RICH_TEXT_SCHEMA.imageProtocols`): the Editor never writes `data:` (base64) or `blob:`, and an image never has `style`, `width`, `height` or `class` — neither on `img` nor on `figure`. The Editor does not output a bare `<img>` outside `figure`; `sanitizeRichText` removes it.
|
|
109
109
|
|
|
@@ -153,7 +153,7 @@ Checked with the real DOMPurify 3.4.16 on jsdom in `tests/editor-sanitize.test.t
|
|
|
153
153
|
|
|
154
154
|
#### Custom Tools: the third argument
|
|
155
155
|
|
|
156
|
-
If the Editor works with custom Tools ([RichTextEditor, "Custom Tool"](../inputs/rich-text-editor.md#свой-инструмент)), the server passes **the same `tools` array** to `sanitizeRichText`. The description of a custom Tool is data, and the module with it is imported on the server: sanitizing reads only the Schema rules (`blocks`, `marks`) and does not touch the
|
|
156
|
+
If the Editor works with custom Tools ([RichTextEditor, "Custom Tool"](../inputs/rich-text-editor.md#свой-инструмент)), the server passes **the same `tools` array** to `sanitizeRichText`. The description of a custom Tool is data, and the module with it is imported on the server: sanitizing reads only the Schema rules (`blocks`, `marks`) and does not touch the Extensions. To keep Tiptap out of the server graph too, keep the description in a module with no extension imports — the Extensions are then supplied by a Loader ([RichTextEditor, "Extensions Loader"](../inputs/rich-text-editor.md#загрузчик-extensions)) or built next to the Editor.
|
|
157
157
|
|
|
158
158
|
```ts
|
|
159
159
|
import { sanitizeRichText } from "@olddevs/ui/editor/schema";
|
|
@@ -245,6 +245,12 @@ The Editor's Wrapper (`div[data-block="wrapper"]` with Blocks inside, section "W
|
|
|
245
245
|
|
|
246
246
|
`sanitizeRichText` keeps a Wrapper where the Editor writes it: at the top level and directly in a Layout column, not in another Wrapper, list, quote or table; otherwise the Wrapper is stripped and the Blocks stay one after another. A Layout inside a Wrapper is unwrapped into its Blocks.
|
|
247
247
|
|
|
248
|
+
## Collapsible block
|
|
249
|
+
|
|
250
|
+
A collapsible Block of the Editor (the `details` Tool, the "Collapsible block" section of the [RichTextEditor](../inputs/rich-text-editor.md#сворачиваемый-блок) page) is shown as **a native `details` and `summary`**: it needs neither the engine nor JavaScript, the browser itself opens and closes it, from the mouse and from the keyboard (Tab, Enter, Space), and a screen reader reads it as a disclosure element — the kit adds no roles or `aria-*` to the markup. The Block arrives closed; it is open only if it was saved with `open` (the Editor's `persist` option). The frame, the padding and the semibold title are the same as in the Editor, the `summary` marker is the browser's native one, and the Blocks in the content keep the Document's rhythm. The content wrapper `div[data-type="detailsContent"]` has no look of its own.
|
|
251
|
+
|
|
252
|
+
`sanitizeRichText` keeps the Block in the shape the Editor writes: `summary` and the content wrapper only directly in `details` (otherwise the tag is removed and the Blocks stay in a row), `open` only empty and only on `details`; `iframe` is not opened this way. A Layout or a Wrapper inside the content unwraps into Blocks.
|
|
253
|
+
|
|
248
254
|
## Custom Block
|
|
249
255
|
|
|
250
256
|
`RichTextView` shows a custom Block (`div[data-block="acme_card"]`, section "Custom Tool" of the [RichTextEditor](../inputs/rich-text-editor.md#свой-инструмент) page) as sanitized HTML and gives it **the same vertical rhythm as a paragraph, and nothing more**: the spacing to the next Block is the same as for a paragraph in the same place (in a Document, a quote, a cell, a column). Blocks inside a custom container Block get the Document's rhythm, as in a Wrapper; inline content (text, Atoms, Marks) gets no spacing. On Paper it is the same.
|
|
@@ -259,7 +265,7 @@ The look comes from the consumer's CSS, which targets the `data-block` selector
|
|
|
259
265
|
}
|
|
260
266
|
```
|
|
261
267
|
|
|
262
|
-
A node view from the
|
|
268
|
+
A node view from the Extensions (a React view) is not used in `RichTextView`: there is no engine here. An inline Atom is shown as whatever is in the HTML: an empty `span` with no text is not visible; a Block can be brought to life in the View with an Island (the next section). The rhythm is checked by `tests/rich-text-paper-css.test.tsx` on the real cascade.
|
|
263
269
|
|
|
264
270
|
## Custom Block's Island
|
|
265
271
|
|
|
@@ -295,18 +301,18 @@ export const acmePlayer: CustomTool = {
|
|
|
295
301
|
|
|
296
302
|
**How it works and why `RichTextView` stays a server component.** It works the same way as for "Copy" (the section above): the component itself has no state, and the client piece is a separate file with its own directive.
|
|
297
303
|
|
|
298
|
-
1. The whole Tool description (
|
|
304
|
+
1. The whole Tool description (Extensions, functions) cannot cross the RSC boundary, so `RichTextView` picks only the Islands from `tools`, and only those of Blocks whose `data-block="<name>"` is in the Document (`islandsFor`, `src/editor/core/viewIsland.ts`), and passes references to client components and attribute names to the Island. If there is no `tools`, no `island` on the Block, or no such Block in the Document, there is no Island at all: no anchor `<template>` in the markup, no effect, no loading of the consumer's Island code (the server serializes a reference to a client component only when the Block is there). `island` must be a component from a module with `"use client"`: a server component or a function with a dynamic import is not passed to a client Island.
|
|
299
305
|
2. The Block's server HTML is always in the page. Before hydration, before the Island loads and without JS, the reader sees it — that is, the Block's content as it was saved.
|
|
300
306
|
3. After mounting, the Island (`src/editor/ui/BlockIsland.tsx` and the implementation in `BlockIslandSlots.tsx`, with its own `"use client"`) finds the Blocks of **its own** View the same way "Copy" finds its Mount points: going left from its anchor `<template>`, not through the global `document` — a View in a shadow root or an `iframe` works. In every occurrence of the Block, the content is replaced by the Island; when `tools` changes (the Island is gone) or the Block goes away, the content comes back. If a Block with an Island sits inside another Block with an Island, the outer one comes to life: the component gets its whole text, and the inner one stays as its content.
|
|
301
307
|
4. The Island's state is not reset by a parent re-render: the Island looks for Blocks again only when the Document changed, not `tools` (an inline array literal in the parent does not remount the Island). For this, `RichTextView` keeps the `dangerouslySetInnerHTML` object stable (`useMemo` on the Document): a new object on every re-render would make React rewrite the markup together with the inserted Islands and "Copy" buttons.
|
|
302
308
|
|
|
303
|
-
**The Island is a separate component**, not the
|
|
309
|
+
**The Island is a separate component**, not the React view from the Extensions; the Editor does not read the `island` field.
|
|
304
310
|
|
|
305
311
|
**Cost.** The Island implementation (`BlockIslandSlots.tsx`) is loaded lazily (`React.lazy`) and is not part of the View's first load: while the chunk is in flight, the Block's server HTML is visible. The static `editorView` measurement includes only the shell and the choice of Islands — about 0.2 kB gzip; Tiptap is still not in the View's graph, which `tests/rich-text-view.test.tsx` checks.
|
|
306
312
|
|
|
307
313
|
## Accessibility
|
|
308
314
|
|
|
309
|
-
The component adds no roles: it is a regular `div` with semantic HTML inside — headings, lists, a quote. The Document's headings start with H2 if the Editor worked without `heading1`: the page keeps the H1 itself. A Document with `heading1` contains its own `h1`, and then the page does not put in a second one. An H1 in `RichTextView` is styled with the same typography as in the Editor. If the Document is a separate area of the page, give the container a name through `aria-label` and `role="region"`.
|
|
315
|
+
The component adds no roles: it is a regular `div` with semantic HTML inside — headings, lists, a quote. A collapsible Block is a native `details` and `summary`: opening from the keyboard (Tab, Enter, Space) and announcing the state are the browser's job. The Document's headings start with H2 if the Editor worked without `heading1`: the page keeps the H1 itself. A Document with `heading1` contains its own `h1`, and then the page does not put in a second one. An H1 in `RichTextView` is styled with the same typography as in the Editor. If the Document is a separate area of the page, give the container a name through `aria-label` and `role="region"`.
|
|
310
316
|
|
|
311
317
|
## Anti-patterns
|
|
312
318
|
|