@olddevs/ui 4.5.1 → 4.6.1
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 +26 -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 +68 -68
- 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 +11 -11
- package/docs/api/migration-notes.md +2 -2
- package/docs/api/utilities.en.md +6 -6
- package/docs/components/actions/split-button.en.md +9 -9
- package/docs/components/content/data-display.en.md +1 -1
- package/docs/components/content/layout-primitives.en.md +3 -3
- package/docs/components/content/rich-text-toc.en.md +1 -1
- package/docs/components/content/rich-text-view.en.md +23 -17
- package/docs/components/content/rich-text-view.md +17 -11
- package/docs/components/content/virtual-grid.en.md +2 -2
- package/docs/components/date-time/date-picker.en.md +1 -1
- package/docs/components/inputs/rich-text-editor.en.md +283 -230
- package/docs/components/inputs/rich-text-editor.md +139 -86
- package/docs/components/navigation/mega-menu.en.md +10 -10
- package/docs/components/overlays/menu.en.md +2 -2
- package/docs/components/overlays/shortcuts-help.en.md +1 -1
- package/docs/components/table/data-table.en.md +3 -3
- package/docs/components/table/filter-bar.en.md +1 -1
- package/docs/components/table/toolbar.en.md +1 -1
- 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 +193 -133
- package/docs/glossary.md +191 -132
- package/docs/icons.en.md +12 -9
- package/docs/icons.md +3 -0
- package/docs/palette.en.md +3 -3
- package/docs/theming.en.md +6 -6
- 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
|
@@ -19,6 +19,7 @@ exports:
|
|
|
19
19
|
RichTextTools,
|
|
20
20
|
SearchToolOptions,
|
|
21
21
|
TocToolOptions,
|
|
22
|
+
DetailsToolOptions,
|
|
22
23
|
TocLevel,
|
|
23
24
|
SearchToolCommands,
|
|
24
25
|
TocToolCommands,
|
|
@@ -72,15 +73,16 @@ story: src/editor/RichTextEditor.stories.tsx
|
|
|
72
73
|
- **Блок кода.** С шапкой (язык слева, «Копировать» справа), языком (Python, TypeScript, JavaScript, Go, HTML, JSON, YAML, Markdown или обычный текст) и подсветкой lowlight. В Документ пишется только `class="language-…"`, а для `RichTextView` Блоки подсвечивает серверный хелпер из `@olddevs/ui/editor/highlight`.
|
|
73
74
|
- **HTML-режим.** Кнопка «HTML» фиксированной Панели (Инструмент `source`) меняет Документ на поле с его исходным HTML; выход, `blur`, отправка формы и `getHTML()` применяют правку через Схему, как входящий `value`.
|
|
74
75
|
- **Набор возможностей.** `preset="full" | "basic"` или точный список `tools`; выключенный Инструмент убирает и кнопку, и разметку из Схемы.
|
|
75
|
-
- **Опции, Состояние и Команды Инструмента.** У встроенных `search` и `
|
|
76
|
+
- **Опции, Состояние и Команды Инструмента.** У встроенных `search`, `toc` и `details` есть Опции: запись `tool("toc", { offsetVar: "--header-h", levels: [2, 3] })` стоит в `tools` рядом с именами, включает Инструмент и настраивает его (у Поиска — стартовые флажки `matchCase`, `wholeWord`, `regex`; у Оглавления — `offsetVar`, `levels`, `listWidth`; у сворачиваемого Блока — `persist`). Состояние читает хук `useRichTextToolState(ref, "search")`: у Поиска — открыта ли панель поиска, запрос, число совпадений и номер текущего, у Оглавления — заголовки и текущий раздел; хук отдаёт `null` до монтажа, на сервере, после размонтирования Редактора и без Инструмента в наборе. Команды вызываются через Хэндл: `ref.current?.tool("search")?.open()`, `ref.current?.tool("toc")?.jumpTo(2)`; каждая возвращает `boolean`. Опции читаются один раз, при создании движка. Свой Инструмент с теми же тремя частями описывается `defineTool` ([«Опции, Состояние и Команды»](../../api/editor-extension.md#опции-состояние-и-команды)).
|
|
76
77
|
- **Хэндл (`ref`).** Экран управляет полем через `ref`: `insertHTML`, `insertText`, `getText`, `getSelectedText`, `isEmpty`, `focus(position?)`, `blur`, `undo`, `redo`, `tool(name)`; `getJSON()`/`setJSON()` выгружают и загружают Документ JSON-конвертом без потерь (раздел «JSON»), конверт принимает и `defaultValue`. Свою панель экран строит на хуке `useRichTextEditorState(ref, selector)`. `openSearch` устарел (удаляется в 5.0): его замена — `tool("search")?.open()`. До Готовности (на сервере, до монтажа, пока работают Загрузчики Инструментов) методы тихие: раздел «Хэндл (`ref`) и своя панель вне поля».
|
|
77
78
|
- **События.** О событиях поля экран узнаёт колбэками `onReady()`, `onFocus()` и `onBlur()` (раздел «События»).
|
|
78
|
-
-
|
|
79
|
+
- **Сворачиваемый Блок.** Инструмент `details` (в `full`) — Блок из заголовка и содержимого: вставляется пунктом «Сворачиваемый блок» Меню вставки, в HTML — теги `details` и `summary`, в Просмотре раскрывается родным браузерным способом без движка. Опция `tool("details", { persist: true })` пишет открытость Блока в Документ (`open`); по умолчанию нет. В Markdown Блок — сырой HTML `<details><summary>`.
|
|
80
|
+
- **Скелет и Загрузчик.** До Готовности поле показывает Скелет: рамку и Документ статичным HTML без ввода, без прыжка высоты. Документ Скелета — `value`, очищенный Схемой, и только в браузере; на сервере Скелет — рамка без Документа. Extensions своего Инструмента можно передать Загрузчиком (`engine: () => import(…)`); его сбой — ошибка поля с «Повторить».
|
|
79
81
|
- **Строка статуса и Сообщение поля.** Под полем внутри рамки — Строка статуса (`status`: React-узел экрана у начала, счётчик кита в конце, без `role`) и Сообщение поля: команда движка `showNotice({ tone: "status" | "error", text })` / `showNotice(null)`. `status` только озвучивается, `error` видна строкой `role="alert"` до следующей правки; тем же каналом идут сообщения о картинках.
|
|
80
82
|
- **Кнопки своего Инструмента.** Свой Инструмент (`CustomTool` в `tools`) может нести кнопки в поле `buttons`: подпись (строка или функция от `{ locale }`), иконка, `can`, `isActive`, `isVisible`, `selectionMenu`, `shortcut` и ровно одно из `run` и `popover`. Кит рисует кнопку одной группой перед «Отменить» и «Повторить», вешает сочетание и показывает его в подсказке, `aria-keyshortcuts` и Справочнике сочетаний.
|
|
81
83
|
- **Карточка у текста.** Ещё один вид UI-записи — Карточка у текста (`textCards`): диапазон `rangeAt(state, pos)` (для Метки — готовый `markRange("acme_comment")`) и тело `render({ editor, range, close })`. Кит открывает её кареткой в диапазоне или щелчком, держит рамку (слой, якорь под диапазоном, переворот у края, Escape), не забирает фокус из Документа и переводит фокус в неё по `Alt+F10`.
|
|
82
84
|
- **Пункты вставки и Действия над Блоком.** Ещё два вида UI-записей — Пункты вставки (`insertItems`: «/», «+», «Добавить ›» и «Формат ›» Ручки) и Действия над Блоком (`blockActions`: секция меню Ручки перед «Удалить»), тоже с ровно одним из `run` и `popover`. Поповер Пункта встаёт у каретки, Действия — у Блока.
|
|
83
|
-
- **Вставка и drop.** Свой Инструмент влияет на вставку и drop только через
|
|
85
|
+
- **Вставка и drop.** Свой Инструмент влияет на вставку и drop только через Extensions.
|
|
84
86
|
- **Имена и HTML-соглашение.** Имена всего своего (Инструмента, Блоков, Меток, атрибутов) начинаются с Префикса потребителя (`acme_card`). HTML своего Блока — `div` или `span` с `data-block`, Метки — `span` с `data-mark`, атрибуты — `data-acme_*` со значением по правилу (список, шаблон, адрес).
|
|
85
87
|
- **Пресеты.** Это массивы `RICH_TEXT_PRESETS.full` и `.basic`, чтобы дописать к ним своё.
|
|
86
88
|
- **Сервер и Просмотр.** Тот же массив `tools` принимают серверная очистка (`sanitizeRichText(html, DOMPurify, tools)`, `richTextSchema(tools)`) и `RichTextView` (Остров своего Блока — поле `island`).
|
|
@@ -97,6 +99,7 @@ import { RichTextEditor } from "@olddevs/ui/editor";
|
|
|
97
99
|
- Длинный текст с форматированием: статья, описание, регламент, предложение клиенту. Автор пишет заголовки, списки, цитаты и выделяет слова.
|
|
98
100
|
- Значение хранится и отдаётся как HTML, а показывается потом страницей.
|
|
99
101
|
- Компонент «на будущее» (ADR-0048): экрана-заказчика при выпуске не было, API взят по аналогам — Imperavi Redactor 5, Tiptap Simple Editor, minimal-tiptap, Notion. Ваш экран — первая проверка этого API. Решение о движке — ADR-0050.
|
|
102
|
+
- Сворачиваемый Блок `details` — тоже «на будущее» (ADR-0048): экрана-заказчика при выпуске не было, API взят по расширению Details из Tiptap; первый экран вправе потребовать другой API.
|
|
100
103
|
|
|
101
104
|
## Когда не использовать
|
|
102
105
|
|
|
@@ -112,7 +115,7 @@ import { RichTextEditor } from "@olddevs/ui/editor";
|
|
|
112
115
|
npm install @tiptap/core @tiptap/pm @tiptap/react @tiptap/starter-kit @tiptap/extensions \
|
|
113
116
|
@tiptap/extension-list @tiptap/extension-subscript @tiptap/extension-superscript \
|
|
114
117
|
@tiptap/extension-text-style @tiptap/extension-highlight @tiptap/extension-text-align @tiptap/extension-link \
|
|
115
|
-
@tiptap/extension-table @tiptap/extension-blockquote @tiptap/extension-horizontal-rule \
|
|
118
|
+
@tiptap/extension-table @tiptap/extension-details @tiptap/extension-blockquote @tiptap/extension-horizontal-rule \
|
|
116
119
|
@tiptap/markdown marked @tiptap/suggestion \
|
|
117
120
|
@tiptap/extension-code-block-lowlight lowlight highlight.js
|
|
118
121
|
```
|
|
@@ -250,7 +253,7 @@ import { RichTextEditor } from "@olddevs/ui/editor";
|
|
|
250
253
|
|
|
251
254
|
- **На телефоне кнопки нет.** На сенсорном экране без наведения (`@media (hover: none) and (pointer: coarse)` — телефон, планшет без трекпада) сочетаний нет, поэтому кнопка Справочника скрыта, а пункты меню («¶», «…», таблица, Меню вставки, Ручка Блока) не показывают подписи сочетаний. Планшет с трекпадом и ноутбук с сенсорным экраном подсказки сохраняют.
|
|
252
255
|
- **Разметка — таблица** с заголовками столбцов «Команда» и «Сочетание»; сочетание — `Kbd` кита (`<kbd>`). Скринридер читает строку как пару «команда — сочетание».
|
|
253
|
-
- **Свои кнопки.** Сочетание кнопки своего Инструмента (поле `shortcut`) попадает в Справочник отдельной группой без заголовка после групп кита, в порядке `tools`; подпись берётся из локали `UiProvider`.
|
|
256
|
+
- **Свои кнопки.** Сочетание кнопки своего Инструмента (поле `shortcut`) попадает в Справочник отдельной группой без заголовка после групп кита, в порядке `tools`; подпись берётся из локали `UiProvider`. Клавиши, которые вешают Extensions своего Инструмента, сюда не попадают — раздел «Кнопка своего Инструмента».
|
|
254
257
|
- **Порядок групп:** выделение (первое `⌘A` — текст Блока, второе — весь Документ), отмена и повтор, команды Блоков (дублировать, вверх, вниз, меню Ручки), очистить стили, Метки (жирный, курсив, зачёркнутый, подчёркнутый, строчный код, индексы), тип Блока (текст, заголовки, цитата, блок кода), списки и сдвиг пункта (`⌘]` и `⌘`), выравнивание, ссылка, поиск (`Ctrl/⌘+F`, `Ctrl/⌘+Alt+F`), переход в Панель (`Alt+F10`).
|
|
255
258
|
- **Состав по `tools`.** Строка есть только у команды включённого Инструмента: в `basic` нет заголовков, подчёркивания, индексов, выравнивания, чек-листа и блока кода, а сдвиг пункта есть при любом из трёх списков. Заголовок 1 — только при `heading` и `heading1` вместе. Всегда есть команды без своего Инструмента: выделение, дублировать и переставить Блок, меню Ручки Блока, `Alt+F10`.
|
|
256
259
|
- **Свои Инструменты.** Сочетания их Пунктов вставки и Действий над Блоком (поле `shortcut`) стоят группой без заголовка после групп кита, по Инструменту на группу, в порядке `tools`; подпись строки — подпись UI-записи в текущей локали.
|
|
@@ -329,38 +332,39 @@ import { RichTextEditor } from "@olddevs/ui/editor";
|
|
|
329
332
|
|
|
330
333
|
**Выключенный Инструмент убирает и кнопку, и расширение Схемы.** Его Блок или Метку не получить ни сочетанием клавиш, ни Markdown-разметкой при наборе, ни вставкой, а из `value` они приходят без разметки Блока, остаётся текст.
|
|
331
334
|
|
|
332
|
-
| `Tool` | Кнопка | Разметка
|
|
333
|
-
| ----------------- | -------------------------------------- |
|
|
334
|
-
| `heading` | «¶»: Заголовок 2–4 | `h2`, `h3`, `h4`
|
|
335
|
-
| `heading1` | «¶»: добавляет Заголовок 1 | `h1`
|
|
336
|
-
| `history` | Отменить, Повторить | —
|
|
337
|
-
| `bold` | Жирный | `strong`
|
|
338
|
-
| `italic` | Курсив | `em`
|
|
339
|
-
| `underline` | «…»: Подчёркивание | `u`
|
|
340
|
-
| `strike` | Зачёркнутый | `s`
|
|
341
|
-
| `code` | «…»: Строчный код | `code`
|
|
342
|
-
| `superscript` | «…»: Верхний индекс | `sup`
|
|
343
|
-
| `subscript` | «…»: Нижний индекс | `sub`
|
|
344
|
-
| `clearFormatting` | «…»: Очистить стили | — (снимает Метки)
|
|
345
|
-
| `link` | Ссылка (поповер адреса) | `a[href]`
|
|
346
|
-
| `color` | Цвет ▾ (Меню выделения): «Цвет текста» | `span[data-color]`, `span[style]`
|
|
347
|
-
| `highlight` | Маркер ▾ (Панель), «Цвет ▾» | `mark[data-color]`, `mark[style]`
|
|
348
|
-
| `align` | Выравнивание ▾ (меню) | `p`, `h2`–`h4` с `data-align`
|
|
349
|
-
| `bulletList` | Списки ▾: Маркированный | `ul`, `li`
|
|
350
|
-
| `orderedList` | Списки ▾: Нумерованный | `ol`, `li`
|
|
351
|
-
| `taskList` | Списки ▾: Чек-лист | `ul[data-type=taskList]` и пункты
|
|
352
|
-
| `blockquote` | «…»: Цитата | `blockquote`
|
|
353
|
-
| `codeBlock` | «…»: Блок кода | `pre`, `code class="language-*"`
|
|
354
|
-
| `horizontalRule` | «+»: Разделитель | `hr`
|
|
355
|
-
| `table` | Таблица ▾ (меню) | `table`, `tbody`, `tr`, `th`, `td`
|
|
356
|
-
| `image` | Картинка (поповер адреса) | `figure`, `img`, `figcaption`
|
|
357
|
-
| `emoji` | Эмодзи (поповер с сеткой) | — (обычный текст)
|
|
358
|
-
| `source` | HTML (переключатель режима) | — (правит HTML Документа)
|
|
359
|
-
| `search` | Поиск (панель поиска и замены) | — (ищет по Документу)
|
|
360
|
-
| `layout` | «+»: Раскладка | `div` с `data-block` и `data-layout`
|
|
361
|
-
| `wrapper` | «+»: Обёртка | `div[data-block=wrapper]`
|
|
362
|
-
| `
|
|
363
|
-
| `
|
|
335
|
+
| `Tool` | Кнопка | Разметка | Сочетание | Markdown при наборе | `basic` |
|
|
336
|
+
| ----------------- | -------------------------------------- | ----------------------------------------------------- | ------------------------------------------------ | ---------------------------- | ------- |
|
|
337
|
+
| `heading` | «¶»: Заголовок 2–4 | `h2`, `h3`, `h4` | `Ctrl/⌘+Alt+2…4` | `## `, `### `, `#### ` | — |
|
|
338
|
+
| `heading1` | «¶»: добавляет Заголовок 1 | `h1` | `Ctrl/⌘+Alt+1` | `# ` | — |
|
|
339
|
+
| `history` | Отменить, Повторить | — | `Ctrl/⌘+Z`, `+Shift+Z` | — | да |
|
|
340
|
+
| `bold` | Жирный | `strong` | `Ctrl/⌘+B` | `**текст**` | да |
|
|
341
|
+
| `italic` | Курсив | `em` | `Ctrl/⌘+I` | `*текст*` | да |
|
|
342
|
+
| `underline` | «…»: Подчёркивание | `u` | `Ctrl/⌘+U` | — | — |
|
|
343
|
+
| `strike` | Зачёркнутый | `s` | `Ctrl/⌘+Shift+S` | `~~текст~~` | да |
|
|
344
|
+
| `code` | «…»: Строчный код | `code` | `Ctrl/⌘+E` | `` `текст` `` | да |
|
|
345
|
+
| `superscript` | «…»: Верхний индекс | `sup` | `Ctrl/⌘+.` | — | — |
|
|
346
|
+
| `subscript` | «…»: Нижний индекс | `sub` | `Ctrl/⌘+,` | — | — |
|
|
347
|
+
| `clearFormatting` | «…»: Очистить стили | — (снимает Метки) | `Ctrl/⌘+\` | — | — |
|
|
348
|
+
| `link` | Ссылка (поповер адреса) | `a[href]` | `Ctrl/⌘+K` | `[текст](адрес)` при вставке | да |
|
|
349
|
+
| `color` | Цвет ▾ (Меню выделения): «Цвет текста» | `span[data-color]`, `span[style]` | — | — | — |
|
|
350
|
+
| `highlight` | Маркер ▾ (Панель), «Цвет ▾» | `mark[data-color]`, `mark[style]` | — | — | — |
|
|
351
|
+
| `align` | Выравнивание ▾ (меню) | `p`, `h2`–`h4` с `data-align` | `Ctrl/⌘+Shift+L`, `+E`, `+R` | — | — |
|
|
352
|
+
| `bulletList` | Списки ▾: Маркированный | `ul`, `li` | `Ctrl/⌘+Shift+8` | `- ` или `* ` | да |
|
|
353
|
+
| `orderedList` | Списки ▾: Нумерованный | `ol`, `li` | `Ctrl/⌘+Shift+7` | `1. ` | да |
|
|
354
|
+
| `taskList` | Списки ▾: Чек-лист | `ul[data-type=taskList]` и пункты | `Ctrl/⌘+Shift+9` | `[ ] ` или `[x] ` | — |
|
|
355
|
+
| `blockquote` | «…»: Цитата | `blockquote` | `Ctrl/⌘+Shift+B` | `> ` | да |
|
|
356
|
+
| `codeBlock` | «…»: Блок кода | `pre`, `code class="language-*"` | `Ctrl/⌘+Alt+C` | ` ``` ` | — |
|
|
357
|
+
| `horizontalRule` | «+»: Разделитель | `hr` | — | `---` | — |
|
|
358
|
+
| `table` | Таблица ▾ (меню) | `table`, `tbody`, `tr`, `th`, `td` | `Tab`, `Shift+Tab`, `Alt+↓` в таблице | — | — |
|
|
359
|
+
| `image` | Картинка (поповер адреса) | `figure`, `img`, `figcaption` | — | `` при вставке | — |
|
|
360
|
+
| `emoji` | Эмодзи (поповер с сеткой) | — (обычный текст) | — | — | — |
|
|
361
|
+
| `source` | HTML (переключатель режима) | — (правит HTML Документа) | — | — | — |
|
|
362
|
+
| `search` | Поиск (панель поиска и замены) | — (ищет по Документу) | `Ctrl/⌘+F`, `Ctrl/⌘+Alt+F` | — | да |
|
|
363
|
+
| `layout` | «+»: Раскладка | `div` с `data-block` и `data-layout` | `Tab`, `Shift+Tab`, стрелки у края колонки | — | — |
|
|
364
|
+
| `wrapper` | «+»: Обёртка | `div[data-block=wrapper]` | `Enter` в пустом последнем абзаце — выход | — | — |
|
|
365
|
+
| `details` | «+»: Сворачиваемый блок | `details`, `summary`, `div[data-type=detailsContent]` | `Enter` в заголовке и в пустом конце содержимого | — | — |
|
|
366
|
+
| `insertMenu` | «+» Меню вставки | — (Блоки других Инструментов) | — | `/` | — |
|
|
367
|
+
| `toc` | Оглавление в левом поле (не в `full`) | — (читает заголовки) | — | — | — |
|
|
364
368
|
|
|
365
369
|
Абзац есть всегда, мягкий перенос строки — `Shift+Enter`. `insertMenu` и `emoji`, как и `clearFormatting`, своей разметки не имеют: `insertMenu` даёт доступ к Блокам других Инструментов, а выключают его там, где `/` — обычный символ (пути, «и/или»); в `basic` его нет по той же причине. `emoji` вставляет обычный текст — Схема, санитайзер и Markdown от него не меняются (раздел «Эмодзи»); в `basic` его нет: в комментарии системная палитра ОС удобнее, а кнопка занимала бы место у короткого ряда. `source` — HTML-режим (раздел «HTML-режим»): своей разметки у него тоже нет, правка HTML проходит ту же Схему, что `value`, и обойти набор Инструментов им нельзя; в `basic` его нет — в комментарии кнопка «HTML» лишняя, если экран её не просил.
|
|
366
370
|
|
|
@@ -374,7 +378,7 @@ import { RichTextEditor } from "@olddevs/ui/editor";
|
|
|
374
378
|
|
|
375
379
|
### Опции встроенного Инструмента: `tool()`
|
|
376
380
|
|
|
377
|
-
Опции есть у
|
|
381
|
+
Опции есть у трёх встроенных Инструментов — `search`, `toc` и `details`. Их задаёт помощник `tool(name, options)`: он возвращает Настроенный Инструмент, запись для `tools`, которая стоит рядом с именами и описаниями своих Инструментов (ADR-0069).
|
|
378
382
|
|
|
379
383
|
```tsx
|
|
380
384
|
import { RICH_TEXT_PRESETS, RichTextEditor, tool } from "@olddevs/ui/editor";
|
|
@@ -392,23 +396,23 @@ const tools = [
|
|
|
392
396
|
- **Запись включает Инструмент.** `tool("toc", …)` включает `toc` без строки `"toc"`. Если в `tools` есть и голое имя (например, `search` из `full`), побеждает Настроенный Инструмент: кнопка и панель поиска остаются одни.
|
|
393
397
|
- **Передавайте только то, что меняете.** Остальные опции берутся из умолчаний кита. Вложенные объекты сливаются, массивы заменяются целиком: `levels: [2, 3]` — именно эти уровни, а не дополнение к умолчанию.
|
|
394
398
|
- **Место на поверхности не меняется.** Настроенный встроенный Инструмент стоит там же, где стоял бы по имени; свой — в порядке `tools`.
|
|
395
|
-
- **Типы выводят опции из имени.** `tool("toc", { matchCase: true })` и `tool("bold", {})` не компилируются: `tool()` принимает только Инструменты с Опциями, сейчас это `search` и `
|
|
399
|
+
- **Типы выводят опции из имени.** `tool("toc", { matchCase: true })` и `tool("bold", {})` не компилируются: `tool()` принимает только Инструменты с Опциями, сейчас это `search`, `toc` и `details`.
|
|
396
400
|
- **Две записи `tool()` с одним именем** — ошибка набора, как два своих Инструмента с одним именем: ошибка поля «Редактор не запустился» и `console.error` в разработке.
|
|
397
401
|
- **Неверное значение** (из JS без типов: `levels: [5]`, `offsetVar: "header"`) заменяется умолчанием, в разработке — одним `console.error` на Инструмент; поле продолжает работать.
|
|
398
402
|
- **Опции читаются один раз**, вместе с набором `tools`, при создании движка. Чтобы сменить их у смонтированного поля, перемонтируйте его ключом (`key`) — история правок при этом теряется.
|
|
399
403
|
- **Сервер.** `tool()` не тянет в модуль ни Tiptap, ни React, поэтому набор `tools` с ним собирается в серверном модуле. Настроенный встроенный Инструмент состоит из данных, и если его опции — данные, его можно передать клиентскому компоненту пропом.
|
|
400
404
|
|
|
401
|
-
Опции каждого Инструмента описаны в его разделе: «Поиск и
|
|
405
|
+
Опции каждого Инструмента описаны в его разделе: «Поиск и замена», «Оглавление» и «Сворачиваемый Блок». Опции своего Инструмента задаёт `defineTool` ([«Опции, Состояние и Команды»](../../api/editor-extension.md#опции-состояние-и-команды)).
|
|
402
406
|
|
|
403
407
|
## Свой Инструмент
|
|
404
408
|
|
|
405
|
-
Свой Инструмент — описание одной возможности Редактора: его Блоки и Метки, правила Схемы данными и
|
|
409
|
+
Свой Инструмент — описание одной возможности Редактора: его Блоки и Метки, правила Схемы данными и Extensions (расширения Tiptap). Он передаётся в `tools` вперемешку с именами кита и подчиняется тем же правилам: **не переданный Инструмент убирает свои Блоки и Метки из Схемы** — из `value`, вставки и команд они не проходят, Блок разворачивается в своё содержимое, Атом пропадает. Решение — ADR-0058, разделы 1 и 2.
|
|
406
410
|
|
|
407
411
|
```tsx
|
|
408
412
|
import { Node } from "@tiptap/core";
|
|
409
413
|
import { RICH_TEXT_PRESETS, RichTextEditor, type CustomTool } from "@olddevs/ui/editor";
|
|
410
414
|
|
|
411
|
-
//
|
|
415
|
+
// Extensions: только поведение. Группу, разбор HTML и атрибуты ставит кит.
|
|
412
416
|
const MergeTag = Node.create({
|
|
413
417
|
name: "acme_mergeTag",
|
|
414
418
|
atom: true,
|
|
@@ -434,31 +438,31 @@ export const mergeTagTool: CustomTool = {
|
|
|
434
438
|
/>;
|
|
435
439
|
```
|
|
436
440
|
|
|
437
|
-
**Описание (`CustomTool`).** Обязательны имя,
|
|
441
|
+
**Описание (`CustomTool`).** Обязательны имя, Extensions и правила Схемы для каждого Node и Mark, которые они определяют. Остальное — по умолчанию: обычный Блок без Признаков, без своих кнопок и пунктов меню.
|
|
438
442
|
|
|
439
|
-
| Поле | Что это
|
|
440
|
-
| -------------- |
|
|
441
|
-
| `name` | Имя Инструмента с Префиксом потребителя, уникальное в `tools`
|
|
442
|
-
| `engine` |
|
|
443
|
-
| `blocks` | Правила Схемы Блоков — по имени Node (`CustomBlock`)
|
|
444
|
-
| `marks` | Правила Схемы Меток — по имени Mark (`CustomMark`)
|
|
445
|
-
| `buttons` | Кнопки Панели — раздел «Кнопка своего Инструмента» (`CustomToolButton`)
|
|
446
|
-
| `insertItems` | Пункты Меню вставки (`CustomInsertItem`) — раздел «Пункты вставки и Действия над Блоком»
|
|
447
|
-
| `blockActions` | Действия над Блоком в меню Ручки Блока (`CustomBlockAction`) — тот же раздел
|
|
448
|
-
| `textCards` | Карточки у текста (`CustomTextCard`) — раздел «Карточка у текста»
|
|
449
|
-
| `trigger` | Свой символ-триггер (`CustomTrigger`) — раздел «Свой символ-триггер»
|
|
443
|
+
| Поле | Что это |
|
|
444
|
+
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
445
|
+
| `name` | Имя Инструмента с Префиксом потребителя, уникальное в `tools` |
|
|
446
|
+
| `engine` | Extensions — массив расширений Tiptap (Node, Mark, Extension, плагины, React-вид узла; Node и Mark — на верхнем уровне) или Загрузчик такого массива (ниже) |
|
|
447
|
+
| `blocks` | Правила Схемы Блоков — по имени Node (`CustomBlock`) |
|
|
448
|
+
| `marks` | Правила Схемы Меток — по имени Mark (`CustomMark`) |
|
|
449
|
+
| `buttons` | Кнопки Панели — раздел «Кнопка своего Инструмента» (`CustomToolButton`) |
|
|
450
|
+
| `insertItems` | Пункты Меню вставки (`CustomInsertItem`) — раздел «Пункты вставки и Действия над Блоком» |
|
|
451
|
+
| `blockActions` | Действия над Блоком в меню Ручки Блока (`CustomBlockAction`) — тот же раздел |
|
|
452
|
+
| `textCards` | Карточки у текста (`CustomTextCard`) — раздел «Карточка у текста» |
|
|
453
|
+
| `trigger` | Свой символ-триггер (`CustomTrigger`) — раздел «Свой символ-триггер» |
|
|
450
454
|
|
|
451
455
|
**Префикс потребителя.** Имена Инструмента, его Блоков, Меток и атрибутов начинаются с префикса через подчёркивание: `acme_mergeTag`, атрибут `acme_size`. В именах кита подчёркивания не бывает, поэтому своё и встроенное не пересекаются ни в наборе, ни в сохранённом Документе. Имена атрибутов — строчными: в HTML регистр имени атрибута теряется. Встроенный Инструмент не подменяется: выключите его в `tools` и поставьте свой под своим именем.
|
|
452
456
|
|
|
453
|
-
**HTML-соглашение.** Его пишет и читает кит,
|
|
457
|
+
**HTML-соглашение.** Его пишет и читает кит, Extensions задают только поведение:
|
|
454
458
|
|
|
455
459
|
- блочный Блок — `<div data-block="acme_card">…</div>`, строчный Атом — `<span data-block="acme_mergeTag"></span>`;
|
|
456
460
|
- своя Метка — `<span data-mark="acme_comment">…</span>`;
|
|
457
461
|
- свой атрибут `acme_size` — `data-acme_size`; других атрибутов у своего Блока и Метки нет.
|
|
458
462
|
|
|
459
|
-
Кит дописывает Node и Mark
|
|
463
|
+
Кит дописывает Node и Mark из Extensions: ставит группу (`block`, `inline` или верхний уровень) и строчность по виду Блока; разбирает HTML только по этому соглашению (теги кита `p`, `table`, `img` свой Блок занять не может); для атрибутов из правил разбирает и выводит `data-*` с проверкой значения. Свой `renderHTML` можно оставить (например, чтобы положить текст в Атом), но он обязан давать тот же тег и `data-block`. Свои атрибуты Node объявлять не обязательно: кит добавит их из правил.
|
|
460
464
|
|
|
461
|
-
**Правила Схемы — данные, без функций.** Их проверяет код кита, и ослабить проверку
|
|
465
|
+
**Правила Схемы — данные, без функций.** Их проверяет код кита, и ослабить проверку через Extensions нельзя: значение вне правил не попадает в HTML Документа ни из `value`, ни из вставки, ни из команды. При разборе атрибут получает значение по умолчанию, при выводе не пишется.
|
|
462
466
|
|
|
463
467
|
| Поле `CustomBlock` | Значение |
|
|
464
468
|
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
@@ -484,19 +488,19 @@ HTML внутри атрибута не бывает: значение с `<` н
|
|
|
484
488
|
|
|
485
489
|
- имя Инструмента, Блока, Метки или атрибута не имеет Префикса потребителя;
|
|
486
490
|
- у двух Инструментов совпало имя либо имя Блока или Метки;
|
|
487
|
-
-
|
|
491
|
+
- Extensions расходятся с правилами: Node или Mark без правила, правило без Node или Mark на верхнем уровне `engine`, атрибут узла без правила, `renderHTML` мимо соглашения, Схема, которую движок не собрал;
|
|
488
492
|
- у кнопки нет `id` или иконки, два `id` в одном Инструменте совпали, или не задано ровно одно из `run` и `popover` (у Пункта вставки и Действия над Блоком — тоже).
|
|
489
493
|
|
|
490
494
|
При ошибке движок не создаётся: под рамкой поля — строка `role="alert"` «Редактор не запустился. Документ не изменён.» (`editor.toolsFailed`), скрытое поле формы и `getHTML()` отдают входящее значение, `onChange` молчит, `ref.current.editor` — `null`. Причина — в `console.error` в режиме разработки, с именем Инструмента. Так потеря данных видна до первого сохранения, а не на сервере. **Неизвестная строка в `tools` по-прежнему отбрасывается молча**: ошибка относится только к описаниям своих Инструментов.
|
|
491
495
|
|
|
492
496
|
Кнопки, пункты Меню вставки, Действия над Блоком и Карточки у текста описаны в разделах ниже; сообщить автору об ошибке Инструмент может командой `showNotice` (раздел «Сообщение поля»). Всё API расширения и граница его стабильности (что подчиняется semver, что считается ломающим) описаны на странице [API расширения Редактора](../../api/editor-extension.md). Если у Инструмента есть настройки, а экрану нужны его Состояние и Команды, описывайте его через `defineTool`: [«Опции, Состояние и Команды»](../../api/editor-extension.md#опции-состояние-и-команды).
|
|
493
497
|
|
|
494
|
-
### Загрузчик
|
|
498
|
+
### Загрузчик Extensions
|
|
495
499
|
|
|
496
|
-
Вместо массива `engine` своего Инструмента можно передать функцию с динамическим импортом — она отдаёт промис того же массива (ADR-0058, пп. 1.3 и 11.3–11.6). Так модуль с описанием можно импортировать и на сервере (правила Схемы — данные), а Tiptap и React
|
|
500
|
+
Вместо массива `engine` своего Инструмента можно передать функцию с динамическим импортом — она отдаёт промис того же массива (ADR-0058, пп. 1.3 и 11.3–11.6). Так модуль с описанием можно импортировать и на сервере (правила Схемы — данные), а Tiptap и React из Extensions уходят в отдельный чанк:
|
|
497
501
|
|
|
498
502
|
```tsx
|
|
499
|
-
// acme-merge-tag.ts — без Tiptap в графе: его тянет только чанк
|
|
503
|
+
// acme-merge-tag.ts — без Tiptap в графе: его тянет только чанк с Extensions.
|
|
500
504
|
export const mergeTagTool: CustomTool = {
|
|
501
505
|
name: "acme_mergeTag",
|
|
502
506
|
engine: () => import("./acme-merge-tag-engine.js").then((module) => module.engine),
|
|
@@ -509,7 +513,7 @@ export const mergeTagTool: CustomTool = {
|
|
|
509
513
|
};
|
|
510
514
|
```
|
|
511
515
|
|
|
512
|
-
- **Готовность ждёт Загрузчики.** Движок создаётся, когда отработали Загрузчики всех Инструментов набора: Схема фиксируется при создании, и Блоки из `value` без
|
|
516
|
+
- **Готовность ждёт Загрузчики.** Движок создаётся, когда отработали Загрузчики всех Инструментов набора: Схема фиксируется при создании, и Блоки из `value` без своих Extensions потерялись бы при разборе. До того методы Хэндла тихие, `ref.current.editor` — `null`, `onReady` не вызван (раздел «События»).
|
|
513
517
|
- **Загрузка стартует с первого рендера в браузере**, ещё до монтажа, а не в эффекте; на сервере Загрузчик не вызывается. Промис кэшируется на самой функции Загрузчика: второй Редактор на странице с тем же Инструментом ничего не ждёт и Загрузчик не вызывает. Поэтому Загрузчик — одна функция на модуль, а не новая стрелка на каждый рендер.
|
|
514
518
|
- **Сбой загрузки** (сеть, устаревший чанк после выкладки) — ошибка поля «Редактор не запустился. Документ не изменён.» и кнопка «Повторить» (`editor.retry`). Она заново вызывает только упавшие Загрузчики; удачные остаются в кэше. Документ всё это время виден Скелетом. Редактор не стартует без Инструмента: его Блоки пропали бы из Документа. `onReady` при сбое не вызывается, причина — в `console.error` в режиме разработки.
|
|
515
519
|
- **Предзагрузки кит не публикует.** Чтобы чанк пришёл вместе со страницей, а не после её скрипта, добавьте на него `modulepreload` сами. Адрес чанка с хешем — из манифеста сборщика (в Vite — `build.manifest`):
|
|
@@ -524,8 +528,8 @@ export const mergeTagTool: CustomTool = {
|
|
|
524
528
|
|
|
525
529
|
Скелет (ADR-0058, п. 11.4) есть у каждого Редактора, с Загрузчиками и без: до Готовности и при сбое Загрузчика поле рисует рамку, место фиксированной Панели и Документ статичным HTML — тем же классом и ритмом, что Документ движка, без ввода и без роли `textbox`. Движок встаёт на его место без прыжка высоты.
|
|
526
530
|
|
|
527
|
-
- **Документ Скелета — `value`, очищенный Схемой**, а не строка как есть: кит разбирает её в инертном документе браузера (скрипты и обработчики там не исполняются) по Схеме встроенных Инструментов набора и своих,
|
|
528
|
-
- **Блок кода и таблица в `full` тоже ленивые** (ADR-0058, п. 11.1): их нет в `basic`, а их
|
|
531
|
+
- **Документ Скелета — `value`, очищенный Схемой**, а не строка как есть: кит разбирает её в инертном документе браузера (скрипты и обработчики там не исполняются) по Схеме встроенных Инструментов набора и своих, чьи Extensions уже есть, и выгружает обратно в HTML. В страницу не попадает и до Готовности всё, чего Схема не знает: `<script>`, `on*`, `javascript:`, неизвестные теги и атрибуты (раздел «Схема и безопасность»). Блок своего Инструмента, чей Загрузчик ещё не отработал, виден текстом: его разметку Схема снимает, а движок с пришедшими Extensions разбирает `value` заново.
|
|
532
|
+
- **Блок кода и таблица в `full` тоже ленивые** (ADR-0058, п. 11.1): их нет в `basic`, а их Extensions тяжелее 10 кБ gzip, поэтому они приходят отдельным чанком тем же Загрузчиком, что у своего Инструмента. Вместе с ними приходят меню «Таблица», окно «Вставить таблицу» и шапка блока кода. `basic` чанки не грузит и готов сразу.
|
|
529
533
|
- До прихода чанков Редактор с ними — Скелет, `onReady` ждёт оба. Их Блоки в Скелете видны своей формой (блок кода с шапкой, таблица ячейками), и поле не прыгает с приходом чанков. Чтобы чанки пришли вместе со страницей, поставьте на них `modulepreload`, как на чанк своего Инструмента. Размер «старта» и «полного» — в таблице «Размер импорта» в [getting-started](../../getting-started.md#размер-импорта).
|
|
530
534
|
- **На сервере и до гидратации Скелет — только рамка и место Панели**, без Документа: разбор Схемой идёт в DOM браузера. Документ появляется после монтирования, поэтому на SSR-странице высота поля меняется один раз, как до Скелета. В клиентском рендере Документ Скелета есть с первого кадра.
|
|
531
535
|
|
|
@@ -533,10 +537,10 @@ export const mergeTagTool: CustomTool = {
|
|
|
533
537
|
|
|
534
538
|
### Текст атома
|
|
535
539
|
|
|
536
|
-
Одно правило для встроенных и своих Атомов (ADR-0058, раздел 4): Текст атома — то, что даёт `renderText`
|
|
540
|
+
Одно правило для встроенных и своих Атомов (ADR-0058, раздел 4): Текст атома — то, что даёт `renderText` из Extensions; Атом без содержимого и без `renderText` — пустая строка, Атом с содержимым внутри (картинка с подписью) — текст своих детей. Из него кит строит всё, что выдаёт текст:
|
|
537
541
|
|
|
538
|
-
- **Вес в `maxLength`** равен длине Текста атома, поэтому счётчик совпадает с `getText()` без разделителей Блоков. Для встроенных: разделитель весит 0, мягкий перенос — 1, картинка — длину подписи; Merge Tag из примера выше — длину `{{ключа}}`. Отдельного поля веса нет: оно разошлось бы с текстом. Серверного счётчика у кита нет — вес зависит от
|
|
539
|
-
- **`getText()`, `getSelectedText()`, текст при копировании и `getMarkdown()`** отдают один и тот же Текст атома; Атом попадает в выделенный текст только целиком. Атом без `renderMarkdown` выгружается в Markdown своим Текстом атома; пропадает только Атом без текста; свой `renderMarkdown`
|
|
542
|
+
- **Вес в `maxLength`** равен длине Текста атома, поэтому счётчик совпадает с `getText()` без разделителей Блоков. Для встроенных: разделитель весит 0, мягкий перенос — 1, картинка — длину подписи; Merge Tag из примера выше — длину `{{ключа}}`. Отдельного поля веса нет: оно разошлось бы с текстом. Серверного счётчика у кита нет — вес зависит от Extensions.
|
|
543
|
+
- **`getText()`, `getSelectedText()`, текст при копировании и `getMarkdown()`** отдают один и тот же Текст атома; Атом попадает в выделенный текст только целиком. Атом без `renderMarkdown` выгружается в Markdown своим Текстом атома; пропадает только Атом без текста; свой `renderMarkdown` в Extensions сильнее.
|
|
540
544
|
- **Пустота — правило кита, без поля описания.** Любой Атом, свой или встроенный, — содержимое для `required`, `isEmpty()` и `getHTML()`: вставка видео без подписи проходит проверку обязательности. Пустой не-атомарный Блок (абзац без текста) содержимым не считается.
|
|
541
545
|
- **Вставка из буфера за пределом `maxLength`** срезает хвост по символам, а не по позициям. Атом не режется: не поместившийся уходит целиком вместе со всем, что вставлено после него; если предел всё ещё превышен, вставка отвергается целиком.
|
|
542
546
|
|
|
@@ -544,8 +548,8 @@ export const mergeTagTool: CustomTool = {
|
|
|
544
548
|
|
|
545
549
|
Тот же массив `tools` понимают серверная очистка и Просмотр (ADR-0058, п. 2.3 и раздел 3).
|
|
546
550
|
|
|
547
|
-
- **Сервер** передаёт очистке тот же массив `tools`: `sanitizeRichText(html, DOMPurify, tools)` пропускает свои Блоки, Атомы и Метки по их правилам и срезает всё прочее, `richTextSchema(tools)` отдаёт эти правила данными. Имена Инструментов кита белый список не сужают, без третьего аргумента очистка прежняя. Оба имени есть и в `@olddevs/ui/editor`, и в лёгком `@olddevs/ui/editor/schema`; в графе лёгкого входа по-прежнему нет ни Tiptap, ни React, а описание своего Инструмента — данные, которые сервер читает без
|
|
548
|
-
- **Markdown.** Свои правила — в
|
|
551
|
+
- **Сервер** передаёт очистке тот же массив `tools`: `sanitizeRichText(html, DOMPurify, tools)` пропускает свои Блоки, Атомы и Метки по их правилам и срезает всё прочее, `richTextSchema(tools)` отдаёт эти правила данными. Имена Инструментов кита белый список не сужают, без третьего аргумента очистка прежняя. Оба имени есть и в `@olddevs/ui/editor`, и в лёгком `@olddevs/ui/editor/schema`; в графе лёгкого входа по-прежнему нет ни Tiptap, ни React, а описание своего Инструмента — данные, которые сервер читает без Extensions. Подробно — [RichTextView, «Свои Инструменты: третий аргумент»](../content/rich-text-view.md#свои-инструменты-третий-аргумент).
|
|
552
|
+
- **Markdown.** Свои правила — в Extensions, как у любого расширения Tiptap: `renderMarkdown`, а для разбора — `markdownTokenizer` и `parseMarkdown`; с ними Блок проходит круг `getMarkdown()` → `setMarkdown()`. Без них `getMarkdown()` не теряет текст: Блок выгружается своим содержимым (контейнер — своими Блоками), Атом — Текстом атома из `renderText` (экранированным, как любой текст), Метка — своим текстом; пропадает только Атом без текста. Обратно такой Блок из Markdown не создаётся — его текст встаёт абзацами. В режиме разработки — одно предупреждение `console.warn` на Инструмент, когда это умолчание впервые сработало.
|
|
549
553
|
- **`RichTextView`** показывает свой Блок очищенным HTML с вертикальным ритмом абзаца — и больше ничего; вид задаёт ваш CSS: селекторы по `data-block` и токены кита ([RichTextView, «Свой Блок»](../content/rich-text-view.md#свой-блок)). Оживить Блок в показе можно Островом — полем `island` у Блока; `RichTextView` получает тот же `tools` ([RichTextView, «Остров своего Блока»](../content/rich-text-view.md#остров-своего-блока)).
|
|
550
554
|
|
|
551
555
|
## Кнопка своего Инструмента
|
|
@@ -558,7 +562,7 @@ import { SparkleIcon } from "@olddevs/ui/icons";
|
|
|
558
562
|
|
|
559
563
|
export const stampTool: CustomTool = {
|
|
560
564
|
name: "acme_stamp",
|
|
561
|
-
engine: [], // кнопке своей Схемы и
|
|
565
|
+
engine: [], // кнопке своей Схемы и Extensions не нужно
|
|
562
566
|
buttons: [
|
|
563
567
|
{
|
|
564
568
|
id: "stamp",
|
|
@@ -588,7 +592,7 @@ export const stampTool: CustomTool = {
|
|
|
588
592
|
- **Место.** Свои кнопки стоят на Панели одной группой перед «Отменить» и «Повторить», после групп кита, в порядке `tools` и порядке массива `buttons`. Фиксированная и плавающая Панель показывают один набор. Порядок кнопок кита задаёт кит; спрятать встроенную можно только выключив Инструмент, а короткую Панель получают, сужая `tools`. Выключенный Инструмент уносит и кнопки, и сочетание.
|
|
589
593
|
- **Состояние.** `can`, `isActive` и `isVisible` читаются на каждой транзакции, как у кнопок кита, поэтому берут состояние Документа: `editor.isActive("acme_pin")`. В нередактируемом поле (`readOnly`, `disabled`) кит выключает кнопки сам.
|
|
590
594
|
- **Поповер.** Рамку держит кит: слой `Popover`, якорь у кнопки, стек Escape (внутри `Dialog` Escape закрывает поповер, а не окно), имя рамки — подпись кнопки. Содержимое монтируется заново на каждое открытие. `close` закрывает поповер тем же путём, что Escape. Закрытие возвращает фокус и выделение в Документ, а не на кнопку; щелчок мимо фокус не трогает. Вызванный сочетанием поповер встаёт у каретки, а не у кнопки: кнопки на экране может и не быть (`toolbar="none"`).
|
|
591
|
-
- **Сочетание — единственный источник правды.** Кит сам привязывает клавишу к `run` (или открывает поповер), пишет её в подсказку с `Kbd`, в `aria-keyshortcuts` кнопки и в Справочник сочетаний: свои строки стоят там отдельной группой без заголовка после групп кита, в порядке `tools`. Недоступная кнопка клавишу не берёт, и она идёт дальше, в Документ. Клавиши, которые
|
|
595
|
+
- **Сочетание — единственный источник правды.** Кит сам привязывает клавишу к `run` (или открывает поповер), пишет её в подсказку с `Kbd`, в `aria-keyshortcuts` кнопки и в Справочник сочетаний: свои строки стоят там отдельной группой без заголовка после групп кита, в порядке `tools`. Недоступная кнопка клавишу не берёт, и она идёт дальше, в Документ. Клавиши, которые вешают Extensions (`addKeyboardShortcuts`), в Справочник не попадают: сочетание кнопки задавайте полем `shortcut`, а не расширением.
|
|
592
596
|
- **Подпись.** Функция получает `{ locale }` (локаль `UiProvider`), и подпись перерисовывается при её смене; в одноязычном приложении хватает строки. Переопределение подписей — опциями фабрики Инструмента (например, поле `labels`; рецепт — [API расширения, «Подписи через опции фабрики»](../../api/editor-extension.md#подписи-через-опции-фабрики)), каталог `UiProvider` под Инструменты потребителя не расширяется. Если подпись пустая, в разработке в консоль идёт `console.error` с именем Инструмента и кнопки, а вместо подписи в имя кнопки, подсказку и имя поповера подставляется имя Инструмента.
|
|
593
597
|
- **Кнопок-меню** (с радиопунктами, с подменю) у своих Инструментов нет: для выбора из списка хватает поповера.
|
|
594
598
|
|
|
@@ -719,7 +723,7 @@ const mentionTool: CustomTool = {
|
|
|
719
723
|
- **Независимость от «/».** Триггер работает и без `insertMenu` в `tools`; одновременно открыт только один список: запрос начинается ровно с одного символа-триггера. Открывается только в правке: в `readOnly`, `disabled` и в HTML-режиме списка нет.
|
|
720
724
|
- **Доступность.** Поле остаётся `role="textbox"` и на время списка получает `aria-controls` и `aria-activedescendant`, как у «/»; список — `role="listbox"` с именем из `label`.
|
|
721
725
|
- **Ошибки в `tools`** (поле показывает ошибку, причина — `console.error` в разработке): два Инструмента с одним `char`; `char` — не один печатный знак, либо пробел, либо `/`; `items` или `onSelect` не функции; `label` не строка и не функция. Свой символ-триггер не пересекается со встроенными: `/` занят Меню вставки.
|
|
722
|
-
- **Упоминание — узел потребителя.** Кит не знает, что вставляется: Атом упоминания, ссылка, тег — решает `onSelect`. Атом описывается как любой свой Блок (
|
|
726
|
+
- **Упоминание — узел потребителя.** Кит не знает, что вставляется: Атом упоминания, ссылка, тег — решает `onSelect`. Атом описывается как любой свой Блок (Extensions, правила Схемы, `renderText` — Текст атома), поэтому упоминание попадает в `getText()`, Markdown и `maxLength`.
|
|
723
727
|
|
|
724
728
|
## Карточка у текста
|
|
725
729
|
|
|
@@ -1137,6 +1141,54 @@ ARIA сетки — шаблон `grid` из APG: `role="grid"` → `row` → `g
|
|
|
1137
1141
|
|
|
1138
1142
|
Строки — `editor.wrapper` («Обёртка»), `editor.unwrap` («Снять обёртку»), `editor.wrapperTone` («Тон: {current}»), `editor.wrapperToneNone` («Без тона») и `editor.wrapperToneName` (имена пяти тонов) в каталоге сообщений (английском и русском); иконки — `WrapperIcon`, `UnwrapIcon` и `PaletteIcon` у «Тон ›». Решение — ADR-0057.
|
|
1139
1143
|
|
|
1144
|
+
## Сворачиваемый Блок
|
|
1145
|
+
|
|
1146
|
+
Инструмент `details` (есть в `full`, в `basic` нет) — Блок из заголовка и содержимого, которое читатель раскрывает и сворачивает: подробности под заголовком, как спойлер на GitHub и toggle в Notion. Движок — расширение Details из Tiptap, API взят по нему (ADR-0048). Вставляется пунктом **«Сворачиваемый блок»** Меню вставки (`/collapsible`, `/details` или «+») и **«Добавить › Сворачиваемый блок»** Ручки Блока: пустой раскрытый Блок, каретка в заголовке. Вставка — одна правка, отмена — одним `Ctrl/⌘+Z`.
|
|
1147
|
+
|
|
1148
|
+
- **Заголовок и содержимое.** Заголовок (`summary`) — одна строка текста с Метками, без переносов и Блоков. Содержимое — любые Блоки: абзац, заголовок, списки, цитата, блок кода, таблица, картинка, разделитель, другой сворачиваемый Блок. Раскладки и Обёртки внутри нет: вложенности нет на уровне Схемы, и Раскладка или Обёртка из `value` и вставки разворачивается в свои Блоки. Сам Блок стоит там же, где любой Блок: в Документе, колонке Раскладки, Обёртке, цитате, ячейке и содержимом другого Блока. Меню вставки ставит его под Блоком Ручки: из абзаца — под ним, из списка и цитаты — под всем списком или цитатой, из Обёртки, колонки и содержимого другого Блока — внутри них.
|
|
1149
|
+
- **Раскрытие.** В Редакторе слева от заголовка — кнопка с шевроном: настоящий `button` с именем из каталога («Подробности: {заголовок}») и `aria-expanded`. Она в обходе Tab (из Документа Tab ведёт к кнопке ближайшего Блока ниже каретки), Enter и пробел раскрывают и сворачивают, фокус остаётся на кнопке. Свёрнутое содержимое спрятано и от каретки: стрелки не заводят её внутрь. В `readOnly` Блок тоже раскрывается и сворачивается.
|
|
1150
|
+
- **Опция `persist`.** `tool("details", { persist: true })`, по умолчанию `false` (как у Tiptap). Без неё открытость живёт только в показе: раскрытие не попадает ни в Документ, ни в историю правок, ни в `onChange`, `open` в HTML не пишется и из `value` не читается, и раскрытый при правке Блок не публикуется раскрытым без спроса. С `persist: true` открытый Блок пишет `open=""`, `open` читается из `value`, вставки и `setMarkdown()`, а раскрытие и сворачивание — правка Документа (`onChange`, отмена). Вставленный из меню Блок раскрыт при любом `persist`. Опции читаются один раз, при создании движка; неверное значение (не булево) заменяется умолчанием с `console.error` в разработке.
|
|
1151
|
+
- **Ручка Блока — как у Обёртки.** Ручка у заголовка — ручка всего Блока: «Вверх», «Вниз», «Дублировать» и «Удалить» берут его с содержимым. У Блоков содержимого своя Ручка у левого края содержимого: «Добавить ›» ставит новый Блок ниже в содержимом, «Вверх», «Вниз» и `Alt+Shift+↑/↓` не выходят из него. «Тона ›» у Блока нет. **«Снять сворачивание»** — Действие над Блоком Инструмента `details`: пункт у самого Блока и у Блока прямо в его содержимом, в секции действий Инструментов перед «Удалить». Заголовок становится абзацем, Блоки содержимого встают за ним, каретка остаётся в своём тексте. Одна правка, одна отмена.
|
|
1152
|
+
- **В Редакторе Блок обведён рамкой** (`--rte-hr`) с внутренним отступом, заголовок полужирный, маркер `summary` заменён кнопкой; в `RichTextView` — та же рамка и тот же заголовок, но с родным маркером браузера.
|
|
1153
|
+
|
|
1154
|
+
| Клавиша | Что делает |
|
|
1155
|
+
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
1156
|
+
| `Enter` в заголовке раскрытого Блока | Каретка уходит в содержимое: в пустой первый абзац, если он есть, иначе — в новый абзац в начале содержимого |
|
|
1157
|
+
| `Enter` в заголовке свёрнутого Блока | Новый абзац под Блоком |
|
|
1158
|
+
| `Enter` в пустом последнем абзаце содержимого | Выход: абзац переезжает под Блок, каретка с ним |
|
|
1159
|
+
| `Backspace` в начале заголовка | Блок распадается, как «Снять сворачивание» |
|
|
1160
|
+
| `Backspace` в заголовке дальше от начала | Удаляет один знак; эмодзи и составной знак — целиком |
|
|
1161
|
+
| `↓` / `→` из заголовка свёрнутого Блока | Каретка-щель сразу под Блоком, мимо спрятанного содержимого |
|
|
1162
|
+
| `Tab` / `Shift+Tab` | Не перехватываются: как в Документе. Ловушки клавиатуры нет |
|
|
1163
|
+
|
|
1164
|
+
**Разметка** — теги кита `details` и `summary` и обёртка содержимого `div[data-type="detailsContent"]`, без классов и `style`:
|
|
1165
|
+
|
|
1166
|
+
```html
|
|
1167
|
+
<details>
|
|
1168
|
+
<summary>Как собрать пакет</summary>
|
|
1169
|
+
<div data-type="detailsContent"><p>Запустите проверки, затем сборку.</p></div>
|
|
1170
|
+
</details>
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
`open` пишется только с `persist` и пустым: `<details open="">`. HTML без обёртки содержимого (GitHub, браузеры: `<details><summary>…</summary><p>…</p></details>`) читается так же — Схема достраивает её; Блок без содержимого получает пустой абзац. Без Инструмента `details` Блок из `value` и вставки разворачивается: заголовок становится абзацем, Блоки содержимого идут следом, текст не теряется.
|
|
1174
|
+
|
|
1175
|
+
**Санитайзер** пускает `details`, `summary` и обёртку содержимого только в этой форме. `summary` — только прямо в `details`; `div[data-type="detailsContent"]` — только прямо в `details` и только с таким значением `data-type`; `open` — только на `details` и только пустой (`open="open"` снимается, Блок остаётся закрытым). Вне такого места теги теряют обёртку, а их содержимое остаётся Блоками подряд. `iframe`, `class`, `style` и обработчики срезаются, как везде. Белый список набором `tools` не сужается: `open` проходит и при `persist: false` — другой экран мог его записать, а сервер не вправе срезать чужое.
|
|
1176
|
+
|
|
1177
|
+
**Markdown — сырой HTML, как на GitHub.** `getMarkdown()` выгружает Блок тегами `details` и `summary` с Markdown внутри:
|
|
1178
|
+
|
|
1179
|
+
```md
|
|
1180
|
+
<details>
|
|
1181
|
+
<summary>Как собрать пакет</summary>
|
|
1182
|
+
|
|
1183
|
+
Запустите проверки, затем сборку.
|
|
1184
|
+
|
|
1185
|
+
</details>
|
|
1186
|
+
```
|
|
1187
|
+
|
|
1188
|
+
Пустая строка после `summary` заставляет GitHub читать содержимое как Markdown, и Блок там сворачивается. Заголовок — строчный Markdown одной строкой: GitHub покажет звёздочки в нём как есть, зато круг `getMarkdown()` → `setMarkdown()` идёт без потерь; `<` в заголовке пишется как `<`. С `persist` открытый Блок пишется `<details open>`. `setMarkdown()` читает такой Блок обратно — с вложенными Блоками, списками и кодом, где `</details>` внутри ограждения не считается закрывающим тегом; `<details open>` без `persist` даёт закрытый Блок. Тег без парного `</details>` остаётся текстом. Это отличие от Раскладки и Обёртки без тона, которые в Markdown разворачиваются: у сворачиваемого Блока в Markdown есть общепринятая форма, у колонок — нет. Без Инструмента `details` те же строки разворачиваются в Блоки подряд (после закрывающего тега может остаться пустой абзац).
|
|
1189
|
+
|
|
1190
|
+
Строки — `editor.details` («Сворачиваемый блок»), `editor.unwrapDetails` («Снять сворачивание») и `editor.detailsToggle` («Подробности: {заголовок}», для Блока без заголовка — «Подробности: без заголовка») в каталоге сообщений (английском и русском); иконки — `DetailsIcon` у пункта вставки и `UnwrapIcon` у «Снять сворачивание». Решение — ADR-0048 («на будущее»: заказчика при выпуске не было).
|
|
1191
|
+
|
|
1140
1192
|
## Эмодзи
|
|
1141
1193
|
|
|
1142
1194
|
Инструмент `emoji` (есть в `full`, в `basic` нет) — кнопка «Эмодзи» на Панели сразу за «Таблицей ▾», перед отменой и повтором. Она открывает **поповер кита** (`Popover`) с сеткой отобранного вручную набора: «Избранное» (20) и шесть категорий по 10 — «Смайлы», «Жесты», «Животные», «Еда», «Занятия», «Путешествия». Сетка — одна, категории идут подряд с подписями; полного набора Unicode, поиска, «недавних», оттенков кожи и `:шорткодов:` нет и не будет — это быстрый путь к привычным символам, а не окно выбора символов ОС.
|
|
@@ -1415,7 +1467,7 @@ ref.current?.editor?.commands.showNotice(null);
|
|
|
1415
1467
|
- Своих длительностей, кнопок закрытия и спиннеров у сообщения нет: оно уходит само только с правкой, остальное — командой.
|
|
1416
1468
|
- **Документ команда не меняет**: ни транзакции, ни шага истории, ни `onChange`. `editor.can().showNotice(…)` сообщения не показывает.
|
|
1417
1469
|
- Сообщение без текста (`text: ""`) — то же, что `null`. Текст кит не переводит: он показывается как есть, без разметки. Сообщения самого кита (файлы) идут через каталог сообщений `editor.*`, строки своих Инструментов — из их каталога.
|
|
1418
|
-
- **Кто вызывает.** Экран — `ref.current.editor.commands.showNotice(…)`. Свой Инструмент — из
|
|
1470
|
+
- **Кто вызывает.** Экран — `ref.current.editor.commands.showNotice(…)`. Свой Инструмент — из Extensions: `this.editor.commands.showNotice(…)` в расширении Tiptap. Команда есть у каждого Редактора независимо от `tools`, в том числе без `image`. Тип `FieldNotice` экспортируется из `@olddevs/ui/editor`; им же типизирована команда `editor.commands.showNotice`.
|
|
1419
1471
|
- Сообщения о картинках идут этим же каналом: «Загружается файл photo.png…» и «Файл photo.png загружен» — `status`, отказ загрузки, «не картинка» и «файлы не принимаются» — `error`. Свой `error` и сообщение о файле делят одну область: приходит новое — прежнее уходит.
|
|
1420
1472
|
|
|
1421
1473
|
## Высота поля: `maxHeight` и `resizable`
|
|
@@ -1489,7 +1541,7 @@ import type { RichTextEditorHandle } from "@olddevs/ui/editor";
|
|
|
1489
1541
|
|
|
1490
1542
|
**Вызов метода до Готовности безопасен** — на сервере, до монтажа и пока работают Загрузчики Инструментов (раздел «Свой Инструмент»). Движок создаётся в браузере после первого рендера и Загрузчиков, поэтому вызов в этот момент не бросает исключений и не копит очередь.
|
|
1491
1543
|
|
|
1492
|
-
- Чтение отдаёт известное значение: `getHTML()` — входящий `value`, `getText()` и `getSelectedText()` — `""`, `getJSON()` — `null`, `isEmpty()` судит по входящему значению по тому же правилу, что после монтажа (свой Блок до монтажа — всегда содержимое: Атом ли он,
|
|
1544
|
+
- Чтение отдаёт известное значение: `getHTML()` — входящий `value`, `getText()` и `getSelectedText()` — `""`, `getJSON()` — `null`, `isEmpty()` судит по входящему значению по тому же правилу, что после монтажа (свой Блок до монтажа — всегда содержимое: Атом ли он, знают только его Extensions).
|
|
1493
1545
|
- Запись и фокус ничего не делают.
|
|
1494
1546
|
- Вставка и `setJSON()` возвращают `false`, `canUndo()` и `canRedo()` — тоже `false`.
|
|
1495
1547
|
|
|
@@ -1596,7 +1648,7 @@ function Screen() {
|
|
|
1596
1648
|
|
|
1597
1649
|
### Порядок вставки и drop
|
|
1598
1650
|
|
|
1599
|
-
Во вставку из буфера и drop
|
|
1651
|
+
Во вставку из буфера и drop вмешиваются только Extensions своего Инструмента — плагинами ProseMirror `transformPastedHTML`, `transformPasted`, `handlePaste` и `handleDrop` в `engine`. Своих имён хуков и пропсов `onPaste` и `onDrop` у Редактора нет.
|
|
1600
1652
|
|
|
1601
1653
|
- **Обработчики своих Инструментов видят событие раньше встроенных** — вставки Markdown, загрузки картинок и Раскладки, — где бы свой Инструмент ни стоял в `tools`. `handlePaste` и `handleDrop`, вернувшие `true`, забирают событие целиком: встроенные до него не доходят. Так ссылку на видео можно превратить в свой Блок до того, как её разберёт Markdown.
|
|
1602
1654
|
- **Разбор по Схеме и предел действуют всегда.** Что вставка внесла сверх Схемы (`<script>`, `onclick`, неизвестные Схеме теги, значения вне правил атрибутов), отбрасывается, как у входящего `value`; очистка HTML (`transformPastedHTML`) идёт до Схемы и ослабить её не может. Транзакция своего обработчика, которая уводит текст за `maxLength`, **отклоняется целиком**; урезание хвоста есть только у встроенной вставки из буфера (транзакция с признаком `paste`).
|
|
@@ -1635,7 +1687,7 @@ export const pdfTool: CustomTool = { name: "acme_pdfDrop", engine: [PdfDrop] };
|
|
|
1635
1687
|
- `maxHeight` — предел высоты области Документа (число — пиксели, строка — CSS-длина), дальше Документ прокручивается внутри рамки; `resizable` — ручка высоты у нижнего края области (ползунок с клавиатуры), `maxHeight` с ней — стартовая высота. См. «Высота поля».
|
|
1636
1688
|
- `surface` — `"theme"` (по умолчанию) или `"paper"`: светлый Лист в любой теме, см. «Поверхность». Тип — `RichTextSurface`.
|
|
1637
1689
|
- `status` — React-узел Строки статуса внутри рамки поля, у начала строки счётчика; кит её не озвучивает. Команда движка `showNotice({ tone, text } | null)` — Сообщение поля. Оба — в «Строка статуса и Сообщение поля»; подчиняются semver кита.
|
|
1638
|
-
- `tools` — имена кита, Настроенные Инструменты `tool(name, options)` для `search` и `
|
|
1690
|
+
- `tools` — имена кита, Настроенные Инструменты `tool(name, options)` для `search`, `toc` и `details`, определения `defineTool(…)` и описания своих Инструментов (`CustomTool`, в том числе их `buttons`, `insertItems`, `blockActions`, `textCards` и `trigger`), Пресеты массивами — `RICH_TEXT_PRESETS`; см. «Инструменты и Пресеты» и «Свой Инструмент». Описание с ошибкой — ошибка поля.
|
|
1639
1691
|
- `ref` (`RichTextEditorHandle`): `getHTML()` — текущий HTML, без ожидания задержки и без `onChange` (в HTML-режиме сначала применяет правку поля — тогда `onChange` вызывается, если Документ изменился; то же у `getMarkdown()`); `getMarkdown()` и `setMarkdown(md)` — см. «Markdown»; `getJSON()` и `setJSON(document)` — см. «JSON»; `clear()` — очистить Документ, экран узнаёт об этом через `onChange("")`; `focus(position?)`, `blur()`, `insertHTML`, `insertText`, `getText`, `getSelectedText`, `isEmpty`, `undo`, `redo`, `canUndo`, `canRedo` — см. «Хэндл (`ref`) и своя панель вне поля»; `tool(name)` — Команды Инструмента, см. «Команды Инструмента: `handle.tool()`»; `openSearch({ replace? })` — устарел, удаляется в 5.0, замена — `tool("search")?.open({ replace })`; `editor` — исходный `Editor` Tiptap, его API **следует мажорной версии Tiptap**, до монтажа и на сервере он `null`. `focus()` без аргумента ставит каретку в конец Документа (в HTML-режиме — в поле HTML).
|
|
1640
1692
|
- `useRichTextEditorState(ref, selector)` — хук для своей панели вне поля, см. «Хэндл (`ref`) и своя панель вне поля».
|
|
1641
1693
|
- `useRichTextToolState(ref, name, selector?)` — хук Состояния Инструмента (`search`, `toc` и свои с `defineTool`): снимок данных, `null` до монтажа, после размонтирования и без Инструмента; см. «Состояние Инструмента: `useRichTextToolState`». Подчиняется semver кита, как и `tool()`, `defineTool()`, `handle.tool()` и карта `RichTextTools`.
|
|
@@ -1652,8 +1704,8 @@ export const pdfTool: CustomTool = { name: "acme_pdfDrop", engine: [PdfDrop] };
|
|
|
1652
1704
|
- **Таблицы.** Таблица GFM (`| a | b |` и строка-разделитель, для распознавания вставки — не короче `---` в ячейке) становится таблицей, первая строка — заголовком; `getMarkdown()` выгружает таблицу обратно, всегда со строкой-заголовком (без неё — с пустой). Многострочное содержимое ячейки в Markdown уходит одной строкой, а переносы — `<br>`; `|` в ячейке экранируется. **Выравнивание столбца сохраняется при переносе в обе стороны:** `:---:` и `---:` при вставке и `setMarkdown` становятся `data-align` на ячейках столбца, а `getMarkdown()` пишет их обратно; `:---` — умолчание, атрибута не даёт. **Слияние ячеек Markdown не выражает:** `getMarkdown()` раскладывает слитую ячейку по сетке — текст остаётся в её левой верхней позиции, накрытые позиции становятся пустыми ячейками, поэтому столбцы ниже не съезжают; само слияние теряется, и `setMarkdown(getMarkdown())` вернёт таблицу без него.
|
|
1653
1705
|
- **`getMarkdown()`** — Документ как Markdown, сейчас, без ожидания `changeDelay`. Пустой Документ и вызов до монтажа (на сервере движка нет) — `""`.
|
|
1654
1706
|
- **`setMarkdown(md)`** заменяет Документ разобранным Markdown. Как `clear()`, это Замена снаружи: `onChange` получает новый HTML сразу и один раз, предел `maxLength` её не режет. До монтажа ничего не делает.
|
|
1655
|
-
- **Схема главнее.** Разобранное проходит через Схему Редактора: всё, чего в ней сейчас нет, отбрасывается, в том числе из `setMarkdown`. В этой версии кита теряются картинки с адресом вне `http`/`https` (пропадают целиком, вместе с `alt`), ссылки с адресом вне политики `http`/`https`/`mailto` (текст ссылки остаётся) и исходный HTML внутри Markdown (пропадает
|
|
1656
|
-
- **Свои Инструменты.** Свои правила Markdown — в
|
|
1707
|
+
- **Схема главнее.** Разобранное проходит через Схему Редактора: всё, чего в ней сейчас нет, отбрасывается, в том числе из `setMarkdown`. В этой версии кита теряются картинки с адресом вне `http`/`https` (пропадают целиком, вместе с `alt`), ссылки с адресом вне политики `http`/`https`/`mailto` (текст ссылки остаётся) и исходный HTML внутри Markdown (пропадает целиком, кроме `details` и `summary` сворачиваемого Блока). Без Инструмента `taskList` чек-листы становятся обычными пунктами списка, без `codeBlock` блок кода пропадает, без `table` пропадает таблица. Раскладки и Обёртки без тона из Markdown не бывает: их Блоки встают подряд. Сворачиваемый Блок читается из сырого HTML `<details><summary>` и выгружается им же (раздел «Сворачиваемый Блок»). Обёртку с тоном даёт только GitHub alert (`> [!NOTE]` и ещё четыре значения, раздел «Обёртка», «Тон»), и `getMarkdown()` выгружает её им же. С `heading1` `#` даёт H1, а `getMarkdown` пишет H1 как `#`. Без него `#` прижимается к H2; `#####` в любом случае — к H4. Что именно принимается, определяет набор Инструментов, а не этот список.
|
|
1708
|
+
- **Свои Инструменты.** Свои правила Markdown — в Extensions; без них свой Блок выгружается содержимым, Атом — Текстом атома (раздел «Свой Инструмент», «Вне Редактора»).
|
|
1657
1709
|
- **Что теряется при выгрузке.** Markdown не выражает цвет текста и маркера, выравнивание Блока, подчёркивание, ширину и подпись картинки, слияние ячеек таблицы и список внутри ячейки: картинка выгружается как ``, `getMarkdown()` остальное опускает, а `setMarkdown(getMarkdown())` вернёт Документ без них. Так устроен формат: для хранения без потерь берите HTML, для файла без потерь — JSON-конверт (раздел «JSON»).
|
|
1658
1710
|
|
|
1659
1711
|
## JSON
|
|
@@ -1685,17 +1737,17 @@ const opened: boolean = ref.current?.setJSON(JSON.parse(fileText)) ?? false;
|
|
|
1685
1737
|
|
|
1686
1738
|
<!-- allow-list -->
|
|
1687
1739
|
|
|
1688
|
-
| Что | Разрешено
|
|
1689
|
-
| -------- |
|
|
1690
|
-
| Блоки | `p`, `h1`, `h2`, `h3`, `h4`, `ul`, `ol`, `li`, `blockquote`, `hr`, `pre`, `table`, `tbody`, `tr`, `th`, `td`; `label`, `input`, `span` — только внутри пункта чек-листа; `div` — там же, а ещё Раскладка и её колонки (Раскладка — только на верхнем уровне, колонка — только прямо в ней) и Обёртка (на верхнем уровне и прямо в колонке, не в другой Обёртке); `figure` — картинка, внутри неё `img` и необязательная `figcaption` (вне `figure` их нет)
|
|
1691
|
-
| Метки | `strong`, `em`, `u`, `s`, `code`, `sup`, `sub`, `mark` (только с цветом), `br`, `a`
|
|
1692
|
-
| Атрибуты | `ol[start]` — только когда нумерация не с 1; `ul[data-type]`, `li[data-type]`, `li[data-checked]` — чек-лист; `input[type]`, `input[checked]` — его флажок; `code[class]` — язык блока кода, только «language-python», «language-typescript», «language-javascript», «language-go», «language-html», «language-json», «language-yaml» или «language-markdown» (без языка класса нет); `span[data-color]`, `mark[data-color]` — цвет текста и маркер, только имена палитры Редактора — на `span` цвета текста, на `mark` маркера; `span[style]`, `mark[style]` — произвольный цвет автора, только «color:#rrggbb» и «background-color:#rrggbb», и не вместе с data-color; `p[data-align]`, `h1[data-align]`, `h2[data-align]`, `h3[data-align]`, `h4[data-align]` — выравнивание Блока, по центру или справа; `th[colspan]`, `td[colspan]` — слияние по горизонтали, целое от 2 до 20; `th[rowspan]`, `td[rowspan]` — по вертикали, от 2 до 50; `th[data-align]`, `td[data-align]` — выравнивание столбца, по центру или справа, на каждой его ячейке; `a[href]` — только http, https или mailto; `a[rel]` — всегда «noopener noreferrer»; `img[src]` — только абсолютный http или https, без data и blob; `img[alt]` — всегда, пустой у картинки без описания; `img[data-width]` — 50, 75 или 100; `div[data-block]` — Раскладка («layout»), её колонка («column») и Обёртка («wrapper»); `div[data-layout]` — вариант Раскладки, доли колонок в процентах: «100», «50-50», «33-33-33», «25-25-25-25», «60-40» или «40-60»; `div[data-tone]` — тон Обёртки, только на ней: «neutral», «accent», «success», «warning» или «danger» (без тона атрибута нет) |
|
|
1740
|
+
| Что | Разрешено |
|
|
1741
|
+
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1742
|
+
| Блоки | `p`, `h1`, `h2`, `h3`, `h4`, `ul`, `ol`, `li`, `blockquote`, `hr`, `pre`, `table`, `tbody`, `tr`, `th`, `td`; `label`, `input`, `span` — только внутри пункта чек-листа; `div` — там же, а ещё Раскладка и её колонки (Раскладка — только на верхнем уровне, колонка — только прямо в ней) и Обёртка (на верхнем уровне и прямо в колонке, не в другой Обёртке); `details` — сворачиваемый Блок, внутри него `summary` и `div` содержимого (оба — только прямо в `details`; вне него теги снимаются); `figure` — картинка, внутри неё `img` и необязательная `figcaption` (вне `figure` их нет) |
|
|
1743
|
+
| Метки | `strong`, `em`, `u`, `s`, `code`, `sup`, `sub`, `mark` (только с цветом), `br`, `a` |
|
|
1744
|
+
| Атрибуты | `ol[start]` — только когда нумерация не с 1; `ul[data-type]`, `li[data-type]`, `li[data-checked]` — чек-лист; `input[type]`, `input[checked]` — его флажок; `code[class]` — язык блока кода, только «language-python», «language-typescript», «language-javascript», «language-go», «language-html», «language-json», «language-yaml» или «language-markdown» (без языка класса нет); `span[data-color]`, `mark[data-color]` — цвет текста и маркер, только имена палитры Редактора — на `span` цвета текста, на `mark` маркера; `span[style]`, `mark[style]` — произвольный цвет автора, только «color:#rrggbb» и «background-color:#rrggbb», и не вместе с data-color; `p[data-align]`, `h1[data-align]`, `h2[data-align]`, `h3[data-align]`, `h4[data-align]` — выравнивание Блока, по центру или справа; `th[colspan]`, `td[colspan]` — слияние по горизонтали, целое от 2 до 20; `th[rowspan]`, `td[rowspan]` — по вертикали, от 2 до 50; `th[data-align]`, `td[data-align]` — выравнивание столбца, по центру или справа, на каждой его ячейке; `a[href]` — только http, https или mailto; `a[rel]` — всегда «noopener noreferrer»; `img[src]` — только абсолютный http или https, без data и blob; `img[alt]` — всегда, пустой у картинки без описания; `img[data-width]` — 50, 75 или 100; `div[data-block]` — Раскладка («layout»), её колонка («column») и Обёртка («wrapper»); `div[data-layout]` — вариант Раскладки, доли колонок в процентах: «100», «50-50», «33-33-33», «25-25-25-25», «60-40» или «40-60»; `div[data-tone]` — тон Обёртки, только на ней: «neutral», «accent», «success», «warning» или «danger» (без тона атрибута нет); `div[data-type]` — обёртка содержимого сворачиваемого Блока, только значение «detailsContent» и только прямо в `details`; `details[open]` — открытый Блок, только пустой «open», пишется с Опцией «persist» |
|
|
1693
1745
|
|
|
1694
1746
|
Пункт чек-листа Tiptap пишет так: `<li data-type="taskItem" data-checked="true"><label><input type="checkbox" checked><span></span></label><div><p>…</p></div></li>`. Санитайзеру стоит пускать `input`, `label`, `span` и `div` только внутри такого пункта, а `input` — только с `type="checkbox"`.
|
|
1695
1747
|
|
|
1696
1748
|
Таблица — `table > tbody > tr > th|td`; у ячейки — только `colspan` и `rowspan` (`colspan` — целое от 2 до 20, `rowspan` — от 2 до 50) и `data-align` (`center` или `right`), ни `style`, ни `colwidth`. `thead`, `tfoot`, `colgroup` и `caption` в Схеме нет и Редактор их не пишет (строка-заголовок — первая строка из `th`); `tbody`, `tr`, `th` и `td` вне таблицы парсер HTML выбрасывает сам, поэтому хук для них не нужен.
|
|
1697
1749
|
|
|
1698
|
-
Других классов, `style` (кроме цвета автора), `aria-*` и `data-*` (кроме восьми имён Схемы: `data-type
|
|
1750
|
+
Других классов, `style` (кроме цвета автора), `aria-*` и `data-*` (кроме восьми имён Схемы: `data-type` (чек-лист и обёртка содержимого сворачиваемого Блока), `data-checked`, `data-color`, `data-align`, `data-width`, `data-block`, `data-layout`, `data-tone`; у своих Инструментов к ним добавляются `data-mark` и `data-<имя>`) в отдаваемом HTML нет: типографика живёт на контейнере, а не на узлах Документа.
|
|
1699
1751
|
|
|
1700
1752
|
**`style` — узкое исключение.** Цвет автора пишется как `<span style="color:#rrggbb">` и `<mark style="background-color:#rrggbb">` и больше ничего в `style` не бывает: ни другие свойства, ни другие теги, ни другие формы цвета. `ALLOWED_ATTR` у DOMPurify общий на все теги и значений не проверяет, поэтому в голом `richTextSanitizeConfig()` `style` нет вовсе, а пускает его только `sanitizeRichText` ([RichTextView](../content/rich-text-view.md#санитизация--на-сервере-потребителя)), сверив тег и значение с `RICH_TEXT_SCHEMA.customColor.styles`.
|
|
1701
1753
|
|
|
@@ -1722,6 +1774,7 @@ const opened: boolean = ref.current?.setJSON(JSON.parse(fileText)) ?? false;
|
|
|
1722
1774
|
- **Эмодзи.** Кнопка — `aria-haspopup="dialog"`; поповер — `role="dialog"` с именем «Эмодзи» (`editor.emoji`) и сеткой `role="grid"` («Выберите эмодзи»), в которой фокус остаётся на сетке, а текущую ячейку называет `aria-activedescendant`, как у сетки размера таблицы. Категории — `rowgroup` с именем («Избранное», «Смайлы»…), эмодзи — `gridcell` с именем из каталога сообщений (`editor.emojiName`: «Огонь», «Красное сердце»), а не символом: скринридеры озвучивают символы по-разному, а иногда молчат. Символ в ячейке скрыт от скринридера. Escape закрывает поповер и возвращает фокус в Документ.
|
|
1723
1775
|
- **Раскладка.** Колонки идут в разметке слева направо — в том же порядке их читает скринридер и в том же порядке они встают столбцом на узком экране. Своей роли у колонки нет: это группа Блоков Документа, а не виджет. Tab, который обрабатывает сам Документ (колонки, список, таблица), не уходит к ловушке фокуса `Dialog` и `Drawer` вокруг поля; Tab, который Документ пропускает (последняя колонка при Tab, первая при Shift+Tab; абзац вне списка), переводит фокус дальше — ловушки клавиатуры нет.
|
|
1724
1776
|
- **Обёртка.** Своей роли у неё нет: это группа Блоков Документа, а не виджет, и скринридер читает её Блоки по порядку, как Блоки вокруг. `Tab` в Обёртке не перехватывается — ловушки клавиатуры нет; из Обёртки выводят `Enter` в пустом последнем абзаце и стрелки у её края. **Тон** роли и `aria-*` не добавляет — он только оформление: смысл заметки («Внимание: …») автор пишет словом (WCAG 1.4.1). Подменю «Тон ›» — `Menu` кита: пункты-радио с `aria-checked`, имя пункта подменю — с текущим тоном.
|
|
1777
|
+
- **Сворачиваемый Блок.** Кнопка раскрытия — настоящий `button` с именем из каталога (`editor.detailsToggle`: «Подробности: {заголовок}») и `aria-expanded`; она в обходе Tab, Enter и пробел раскрывают Блок, а фокус остаётся на кнопке. Содержимое свёрнутого Блока скрыто и от скринридера, и от каретки. В `RichTextView` Блок — родные `details` и `summary`: роли и имени кит не добавляет, раскрывает браузер, с клавиатуры и без JavaScript. `Tab` в Блоке не перехватывается — ловушки клавиатуры нет.
|
|
1725
1778
|
- **HTML-режим.** Кнопка «HTML» — переключатель с `aria-pressed`, в обходе стрелками Панели, как любая кнопка. Поле — нативный `textarea` с именем из каталога сообщений («HTML-код документа», `editor.sourceField`); пояснение и ошибка `FormField` уходят в его `aria-describedby` тем же слиянием, что у Документа; `aria-invalid` зеркалит Документ. Автоисправления выключены: `spellcheck="false"`, `autocapitalize`, `autocorrect` и `autocomplete` — `off`, `dir="ltr"`. Tab и `Shift+Tab` — нативные: табуляцию поле не вставляет, ловушки клавиатуры нет; отмена внутри поля — обычная отмена текстового поля. Вход ставит фокус в поле (каретка в начале), выход кнопкой — в Документ (каретка в конце). `Alt+F10` из поля — на кнопку «HTML», как из Документа на Панель: Панель объявляет это сочетание и в режиме. Остальные кнопки Панели в режиме — `aria-disabled`, в обходе стрелками. Щелчок по подписи `FormField` и отказ `required` ведут фокус в поле HTML. Тесты: `tests/rich-text-editor-source.test.tsx`, в браузере — `tests-visual/rich-text-editor-source.spec.ts`.
|
|
1726
1779
|
- **Сообщение поля и Строка статуса.** Живой регион `role="status"` стоит в каждом Редакторе и пуст, пока нет сообщения `status`; ошибка — `role="alert"` под полем. Строка статуса — контейнер без `role` и `aria-live`: кит её не озвучивает, «Сохранено» экран оборачивает в `role="status"` сам. Строка видна при `readOnly` и `disabled`, счётчик остаётся в `aria-describedby`. Тесты: `tests/rich-text-editor-notice.test.tsx`; в витрине — `StatusLine`, `StatusLineReadOnly`, `FieldNotice`.
|
|
1727
1780
|
- **Карточка у текста.** `role="dialog"` без `aria-modal`, имя — подпись записи (`label`); фокус при открытии остаётся в Документе. О появлении объявляет живой регион `role="status"` фразой `editor.textCardShown` с подписью и сочетанием `Alt+F10`; `Alt+F10` переводит фокус в Карточку, Escape возвращает каретку и прячет её. См. раздел «Карточка у текста».
|
|
@@ -1766,8 +1819,8 @@ const opened: boolean = ref.current?.setJSON(JSON.parse(fileText)) ?? false;
|
|
|
1766
1819
|
|
|
1767
1820
|
## Ссылки
|
|
1768
1821
|
|
|
1769
|
-
- Исходник: `src/editor/RichTextEditor.tsx`; Панель — `src/editor/ui/EditorToolbar.tsx`, меню форматирования («¶», «…», «Выравнивание», «Списки») — `src/editor/ui/FormatMenus.tsx` и `src/editor/core/blockTypes.ts`, плавающая — `src/editor/ui/FloatingToolbar.tsx`, Меню выделения — `src/editor/ui/SelectionBubble.tsx`, общее условие показа и позиция — `src/editor/ui/selectionMenu.ts`; ссылка — `src/editor/features/link/link.ts`, описание Инструмента `src/editor/features/link/linkTool.tsx` и форма адреса `src/editor/features/link/LinkPopover.tsx`; эмодзи — описание Инструмента `src/editor/features/emoji/emojiTool.tsx` и сетка `src/editor/features/emoji/EmojiGrid.tsx`; кнопки из описаний Инструментов и рамка их поповеров — `src/editor/ui/ToolButtonView.tsx`, их сочетания — `src/editor/core/toolButtons.ts`, сочетания их Пунктов вставки и Действий над Блоком — `src/editor/core/toolEntryShortcuts.ts`; картинка — `src/editor/features/image/image.ts` (узел, загрузка, вставка файлов), `src/editor/features/image/ImagePopover.tsx`, `src/editor/features/image/ImageToolbar.tsx`; Сообщение поля — команда `src/editor/core/fieldNotice.ts`, тексты о файлах `src/editor/features/image/imageNotice.ts`, живой регион, строка ошибки и Строка статуса `src/editor/ui/FieldStatus.tsx`; свой Инструмент — публичная форма `src/editor/core/customTool.ts`, движок и проверка набора `src/editor/core/customToolEngine.ts`, разворот `tools` и Опции встроенных `src/editor/features/builtins.ts`; `tool()` и карта `RichTextTools` — `src/editor/core/toolOptions.ts`, типы Команд — `src/editor/core/toolCommands.ts`, типы Состояния — `src/editor/core/toolState.ts`, хук Состояния — `src/editor/hooks/useRichTextToolState.ts`, описания Поиска и Оглавления — `src/editor/features/search/searchTool.ts` и `src/editor/features/toc/tocTool.ts`; таблица — `src/editor/features/table/tables.ts` (узлы, слияние, выравнивание, Markdown), `src/editor/features/table/tableActions.ts` (команды меню, сортировка, память размера), `src/editor/features/table/TableMenuButton.tsx` (меню «Таблица»), `src/editor/features/table/TableSizeGrid.tsx` и `src/editor/features/table/TableInsertDialog.tsx`; Раскладка — `src/editor/features/layout/layout.ts` (узлы, команды, клавиатура) и `src/editor/features/layout/columnLayouts.ts` (варианты данными); Обёртка — `src/editor/features/wrapper/wrapper.ts` (узел, вставка, снятие, тон, клавиатура, Markdown) и `src/editor/features/wrapper/wrapperTones.ts` (тоны и метки GitHub alerts данными); Документ с контейнерами Блоков — `src/editor/core/document.ts`; Хэндл — `src/editor/core/handle.ts`, хук — `src/editor/hooks/useRichTextEditorState.ts`; HTML-режим — состояние и применение в `src/editor/RichTextEditor.tsx`, форматирование HTML — `src/editor/core/sourceFormat.ts`; Инструменты и Пресеты — `src/editor/core/tools.ts`; команды над Блоками — `src/editor/core/blockCommands.ts`; Ручка Блока — `src/editor/ui/BlockHandle.tsx`; таблица сочетаний и подписи — `src/editor/core/shortcuts.ts`, Справочник сочетаний — `src/editor/ui/ShortcutsButton.tsx`; Стекло всплывающих поверхностей — `src/editor/styles/glass.ts`; Меню вставки — `src/editor/core/insertItems.ts` (реестр пунктов), `src/editor/ui/SlashMenu.tsx`, `src/editor/ui/InsertMenuButton.tsx`, `src/editor/core/slashCommand.ts`; Схема данными — `src/editor/core/schema.ts`; палитра и выравнивания — `src/editor/styles/palette.ts`, расширения цвета — `src/editor/features/color/styleExtensions.ts`; цвет автора — форма и разбор `src/editor/features/color/customColor.ts`, контраст `src/editor/features/color/contrast.ts`, поповер `src/editor/features/color/CustomColorPopover.tsx`; типографика — `src/editor/styles/prose.ts`; блок кода — `src/editor/features/code/codeBlock.ts` (расширение, псевдонимы, `Alt+F10`), `src/editor/features/code/CodeBlockView.tsx` (шапка: язык и «Копировать»), `src/editor/features/code/CodeCopyButton.tsx` (кнопка с подсказкой), `src/editor/features/code/codeHeader.ts` (классы шапки, общие с Просмотром), Лист `surface="paper"` — `src/editor/styles/surface.ts` и `src/editor/styles/tokens.ts`, `src/editor/features/code/codeLanguages.ts` (языки данными), `src/editor/features/code/lowlight.ts` (грамматики), `src/editor/styles/codeTheme.ts` (тема подсветки синтаксиса), серверный хелпер — `src/editor/features/code/highlight.ts`; поиск и замена — `src/editor/features/search/searchTool.ts` (Инструмент), `src/editor/features/search/searchPlugin.ts` и `src/editor/features/search/SearchPanel.tsx`; JSON — `src/editor/core/jsonDocument.ts`; Высота поля — Ручка высоты `src/editor/ui/FieldResizer.tsx`; эмодзи (данные набора) — `src/editor/features/emoji/emoji.ts`; предел символов — `src/editor/core/maxLength.ts`; Markdown — `src/editor/core/markdown.ts`.
|
|
1770
|
-
- Тесты: `tests/rich-text-editor-text-card.test.tsx` (Карточка у текста), `tests/rich-text-editor.test.tsx`, `tests/rich-text-editor-handle.test.tsx` и `tests/rich-text-editor-handle-ssr.test.tsx` (Хэндл и хук), `tests/rich-text-editor-toolbar.test.tsx`, `tests/rich-text-editor-block-menu.test.tsx`, `tests/rich-text-editor-more-menu.test.tsx`, `tests/rich-text-editor-align-list-menus.test.tsx`, `tests/rich-text-editor-insert-menu.test.tsx`, `tests/rich-text-editor-schema.test.tsx`, `tests/rich-text-editor-markdown.test.tsx`, `tests/rich-text-editor-color.test.tsx`, `tests/rich-text-editor-custom-color.test.tsx`, `tests/rich-text-editor-link.test.tsx`, `tests/rich-text-editor-floating.test.tsx`, `tests/rich-text-editor-bubble.test.tsx`, `tests/rich-text-editor-glass.test.tsx`, `tests/rich-text-editor-table.test.tsx`, `tests/rich-text-editor-lazy-builtins.test.tsx` (ленивые блок кода и таблица: `basic` их не грузит, `full` — Скелет до чанков), `tests/rich-text-editor-image.test.tsx`, `tests/rich-text-editor-notice.test.tsx`, `tests/rich-text-editor-code-block.test.tsx`, `tests/rich-text-editor-block-commands.test.tsx`, `tests/rich-text-editor-block-handle.test.tsx`, `tests/rich-text-editor-shortcuts.test.tsx`, `tests/rich-text-editor-layout.test.tsx`, `tests/rich-text-editor-wrapper.test.tsx`, `tests/rich-text-editor-wrapper-tone.test.tsx`, `tests/rich-text-editor-source.test.tsx`, `tests/rich-text-editor-custom-tool.test.tsx` (свой Инструмент; тестовые Инструменты — `tests/editor-test-tools.ts`), `tests/rich-text-editor-custom-ui.test.tsx` (Пункты вставки и Действия над Блоком своего Инструмента), `tests/rich-text-editor-trigger.test.tsx` (свой символ-триггер: открытие, выбор, запрос, асинхронные пункты, ошибки набора), `tests/rich-text-editor-custom-button.test.tsx` (кнопки своего Инструмента), `tests/rich-text-editor-custom-markdown.test.tsx` (Markdown своего Блока по умолчанию и по своим правилам), `tests/rich-text-editor-atom-text.test.tsx` (Текст атома, вес в `maxLength`, пустота), `tests/rich-text-editor-empty-table.test.tsx` (пустая таблица — содержимое), `tests/rich-text-editor-max-height.test.tsx` (`maxHeight` и `resizable`: что внутри области прокрутки, место Ручки Блока при прокрутке), `tests/rich-text-editor-events.test.tsx` (`onReady`, `onFocus`, `onBlur`, порядок вставки и drop), `tests/rich-text-editor-loaders.test.tsx` (Загрузчики
|
|
1822
|
+
- Исходник: `src/editor/RichTextEditor.tsx`; Панель — `src/editor/ui/EditorToolbar.tsx`, меню форматирования («¶», «…», «Выравнивание», «Списки») — `src/editor/ui/FormatMenus.tsx` и `src/editor/core/blockTypes.ts`, плавающая — `src/editor/ui/FloatingToolbar.tsx`, Меню выделения — `src/editor/ui/SelectionBubble.tsx`, общее условие показа и позиция — `src/editor/ui/selectionMenu.ts`; ссылка — `src/editor/features/link/link.ts`, описание Инструмента `src/editor/features/link/linkTool.tsx` и форма адреса `src/editor/features/link/LinkPopover.tsx`; эмодзи — описание Инструмента `src/editor/features/emoji/emojiTool.tsx` и сетка `src/editor/features/emoji/EmojiGrid.tsx`; кнопки из описаний Инструментов и рамка их поповеров — `src/editor/ui/ToolButtonView.tsx`, их сочетания — `src/editor/core/toolButtons.ts`, сочетания их Пунктов вставки и Действий над Блоком — `src/editor/core/toolEntryShortcuts.ts`; картинка — `src/editor/features/image/image.ts` (узел, загрузка, вставка файлов), `src/editor/features/image/ImagePopover.tsx`, `src/editor/features/image/ImageToolbar.tsx`; Сообщение поля — команда `src/editor/core/fieldNotice.ts`, тексты о файлах `src/editor/features/image/imageNotice.ts`, живой регион, строка ошибки и Строка статуса `src/editor/ui/FieldStatus.tsx`; свой Инструмент — публичная форма `src/editor/core/customTool.ts`, движок и проверка набора `src/editor/core/customToolEngine.ts`, разворот `tools` и Опции встроенных `src/editor/features/builtins.ts`; `tool()` и карта `RichTextTools` — `src/editor/core/toolOptions.ts`, типы Команд — `src/editor/core/toolCommands.ts`, типы Состояния — `src/editor/core/toolState.ts`, хук Состояния — `src/editor/hooks/useRichTextToolState.ts`, описания Поиска и Оглавления — `src/editor/features/search/searchTool.ts` и `src/editor/features/toc/tocTool.ts`; таблица — `src/editor/features/table/tables.ts` (узлы, слияние, выравнивание, Markdown), `src/editor/features/table/tableActions.ts` (команды меню, сортировка, память размера), `src/editor/features/table/TableMenuButton.tsx` (меню «Таблица»), `src/editor/features/table/TableSizeGrid.tsx` и `src/editor/features/table/TableInsertDialog.tsx`; Раскладка — `src/editor/features/layout/layout.ts` (узлы, команды, клавиатура) и `src/editor/features/layout/columnLayouts.ts` (варианты данными); Обёртка — `src/editor/features/wrapper/wrapper.ts` (узел, вставка, снятие, тон, клавиатура, Markdown) и `src/editor/features/wrapper/wrapperTones.ts` (тоны и метки GitHub alerts данными); сворачиваемый Блок — `src/editor/features/details/details.ts` (узлы, вставка, снятие, клавиатура), `detailsMarkdown.ts` (Markdown сырым HTML) и `detailsTool.ts` (описание Инструмента); Документ с контейнерами Блоков — `src/editor/core/document.ts`; Хэндл — `src/editor/core/handle.ts`, хук — `src/editor/hooks/useRichTextEditorState.ts`; HTML-режим — состояние и применение в `src/editor/RichTextEditor.tsx`, форматирование HTML — `src/editor/core/sourceFormat.ts`; Инструменты и Пресеты — `src/editor/core/tools.ts`; команды над Блоками — `src/editor/core/blockCommands.ts`; Ручка Блока — `src/editor/ui/BlockHandle.tsx`; таблица сочетаний и подписи — `src/editor/core/shortcuts.ts`, Справочник сочетаний — `src/editor/ui/ShortcutsButton.tsx`; Стекло всплывающих поверхностей — `src/editor/styles/glass.ts`; Меню вставки — `src/editor/core/insertItems.ts` (реестр пунктов), `src/editor/ui/SlashMenu.tsx`, `src/editor/ui/InsertMenuButton.tsx`, `src/editor/core/slashCommand.ts`; Схема данными — `src/editor/core/schema.ts`; палитра и выравнивания — `src/editor/styles/palette.ts`, расширения цвета — `src/editor/features/color/styleExtensions.ts`; цвет автора — форма и разбор `src/editor/features/color/customColor.ts`, контраст `src/editor/features/color/contrast.ts`, поповер `src/editor/features/color/CustomColorPopover.tsx`; типографика — `src/editor/styles/prose.ts`; блок кода — `src/editor/features/code/codeBlock.ts` (расширение, псевдонимы, `Alt+F10`), `src/editor/features/code/CodeBlockView.tsx` (шапка: язык и «Копировать»), `src/editor/features/code/CodeCopyButton.tsx` (кнопка с подсказкой), `src/editor/features/code/codeHeader.ts` (классы шапки, общие с Просмотром), Лист `surface="paper"` — `src/editor/styles/surface.ts` и `src/editor/styles/tokens.ts`, `src/editor/features/code/codeLanguages.ts` (языки данными), `src/editor/features/code/lowlight.ts` (грамматики), `src/editor/styles/codeTheme.ts` (тема подсветки синтаксиса), серверный хелпер — `src/editor/features/code/highlight.ts`; поиск и замена — `src/editor/features/search/searchTool.ts` (Инструмент), `src/editor/features/search/searchPlugin.ts` и `src/editor/features/search/SearchPanel.tsx`; JSON — `src/editor/core/jsonDocument.ts`; Высота поля — Ручка высоты `src/editor/ui/FieldResizer.tsx`; эмодзи (данные набора) — `src/editor/features/emoji/emoji.ts`; предел символов — `src/editor/core/maxLength.ts`; Markdown — `src/editor/core/markdown.ts`.
|
|
1823
|
+
- Тесты: `tests/rich-text-editor-text-card.test.tsx` (Карточка у текста), `tests/rich-text-editor.test.tsx`, `tests/rich-text-editor-handle.test.tsx` и `tests/rich-text-editor-handle-ssr.test.tsx` (Хэндл и хук), `tests/rich-text-editor-toolbar.test.tsx`, `tests/rich-text-editor-block-menu.test.tsx`, `tests/rich-text-editor-more-menu.test.tsx`, `tests/rich-text-editor-align-list-menus.test.tsx`, `tests/rich-text-editor-insert-menu.test.tsx`, `tests/rich-text-editor-schema.test.tsx`, `tests/rich-text-editor-markdown.test.tsx`, `tests/rich-text-editor-color.test.tsx`, `tests/rich-text-editor-custom-color.test.tsx`, `tests/rich-text-editor-link.test.tsx`, `tests/rich-text-editor-floating.test.tsx`, `tests/rich-text-editor-bubble.test.tsx`, `tests/rich-text-editor-glass.test.tsx`, `tests/rich-text-editor-table.test.tsx`, `tests/rich-text-editor-lazy-builtins.test.tsx` (ленивые блок кода и таблица: `basic` их не грузит, `full` — Скелет до чанков), `tests/rich-text-editor-image.test.tsx`, `tests/rich-text-editor-notice.test.tsx`, `tests/rich-text-editor-code-block.test.tsx`, `tests/rich-text-editor-block-commands.test.tsx`, `tests/rich-text-editor-block-handle.test.tsx`, `tests/rich-text-editor-shortcuts.test.tsx`, `tests/rich-text-editor-layout.test.tsx`, `tests/rich-text-editor-wrapper.test.tsx`, `tests/rich-text-editor-wrapper-tone.test.tsx`, `tests/rich-text-editor-details.test.tsx`, `tests/rich-text-editor-source.test.tsx`, `tests/rich-text-editor-custom-tool.test.tsx` (свой Инструмент; тестовые Инструменты — `tests/editor-test-tools.ts`), `tests/rich-text-editor-custom-ui.test.tsx` (Пункты вставки и Действия над Блоком своего Инструмента), `tests/rich-text-editor-trigger.test.tsx` (свой символ-триггер: открытие, выбор, запрос, асинхронные пункты, ошибки набора), `tests/rich-text-editor-custom-button.test.tsx` (кнопки своего Инструмента), `tests/rich-text-editor-custom-markdown.test.tsx` (Markdown своего Блока по умолчанию и по своим правилам), `tests/rich-text-editor-atom-text.test.tsx` (Текст атома, вес в `maxLength`, пустота), `tests/rich-text-editor-empty-table.test.tsx` (пустая таблица — содержимое), `tests/rich-text-editor-max-height.test.tsx` (`maxHeight` и `resizable`: что внутри области прокрутки, место Ручки Блока при прокрутке), `tests/rich-text-editor-events.test.tsx` (`onReady`, `onFocus`, `onBlur`, порядок вставки и drop), `tests/rich-text-editor-loaders.test.tsx` (Загрузчики Extensions, «Повторить»), `tests/rich-text-editor-skeleton.test.tsx` (Скелет), `tests/rich-text-editor-emoji.test.tsx` (эмодзи), `tests/rich-text-editor-json.test.tsx` (JSON), `tests/editor-search.test.tsx`, `tests/editor-search-panel.test.tsx` и `tests/editor-search-replace.test.tsx` (поиск и замена), `tests/rich-text-editor-tool-options.test.tsx` и `tests/editor-tool-options-types.test.ts` (Опции `tool()`), `tests/rich-text-editor-tool-state.test.tsx`, `tests/rich-text-editor-tool-state-ssr.test.tsx` и `tests/editor-tool-state-types.test.ts` (Состояние), `tests/rich-text-editor-tool-commands.test.tsx`, `tests/editor-search-commands-reveal.test.tsx` и `tests/editor-tool-commands-types.test.ts` (Команды `handle.tool()`), `tests/rich-text-editor-define-tool.test.tsx`, `tests/editor-define-tool-types.test.ts` и `tests/editor-define-tool-server.test.tsx` (`defineTool`), `tests/rich-text-paper.test.tsx`, `tests/rich-text-paper-css.test.tsx`, `tests/editor-entry.test.ts`, `tests/editor-schema-entry.test.ts`, `tests/editor-highlight-entry.test.ts`; в браузере — `tests-visual/interactions.spec.ts` («RichTextEditor inside Dialog» — Escape в списке стилей, в Меню вставки, в поповере ссылки и в меню языка блока кода; «RichTextEditor inside-dialog» и «inside-drawer» — меню «…» и поповеры цвета мышью и клавиатурой; «RichTextEditor insert menu»; «RichTextEditor block commands» — настоящие `⌥⇧↑/↓`, `⌘]`, `⌘[` и `⌘⇧D`), `tests-visual/rich-text-editor-image.spec.ts` (перетаскивание файла и ручки ширины), `tests-visual/rich-text-editor-bubble.spec.ts` (Меню выделения в `Dialog`, `Drawer` и контейнере прокрутки, у края окна, инверсная поверхность в трёх темах), `tests-visual/rich-text-editor-layout.spec.ts` (Раскладка в `Dialog`: колонки рядом и Tab по ним, стрелки, столбец на телефоне), `tests-visual/rich-text-editor-wrapper.spec.ts` (Обёртка: пунктир в Редакторе и без рамки в `RichTextView`, тон одним видом в обоих, выход по Enter, `↑` из Обёртки), `tests-visual/rich-text-editor-details.spec.ts` (сворачиваемый Блок: раскрытие в Редакторе и в `RichTextView`, кнопка с клавиатуры), `tests-visual/rich-text-editor-block-handle.spec.ts` (меню ручки в `Dialog`: `Ctrl/⌘+/`, Escape по уровням; место ручки у первой строки Блока), `tests-visual/rich-text-editor-trigger.spec.ts` (свой символ-триггер: список у каретки, настоящий ввод `@`, выбор мышью и Enter, `a@b` внутри слова, axe в трёх темах), `tests-visual/rich-text-editor-source.spec.ts` (HTML-режим: высота поля, рост по содержимому, фокус на входе и выходе), `tests-visual/rich-text-editor-height.spec.ts` (Высота поля), `tests-visual/rich-text-editor-emoji.spec.ts` (эмодзи), `tests-visual/rich-text-editor-skeleton.spec.ts` (Скелет).
|
|
1771
1824
|
- API расширения одной картой, «Стабильность» и рецепты — [API расширения Редактора](../../api/editor-extension.md).
|
|
1772
1825
|
- Показ без правки — [RichTextView](../content/rich-text-view.md).
|
|
1773
1826
|
- Storybook: `Inputs/RichTextEditor` (истории `SearchStatusLine`, `TableOfContentsOptions`, `TableOfContentsExternalNavigation`, `ToolCommands`, `DefinedTool` и `DefinedToolConfigured`).
|