@olddevs/ui 4.4.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/editor/RichTextEditor.d.ts +11 -4
- package/dist/editor/RichTextEditor.d.ts.map +1 -1
- package/dist/editor/RichTextEditor.js +90 -60
- package/dist/editor/RichTextEditor.js.map +1 -1
- package/dist/editor/RichTextView.d.ts +2 -1
- package/dist/editor/RichTextView.d.ts.map +1 -1
- package/dist/editor/RichTextView.js.map +1 -1
- package/dist/editor/core/containers.d.ts +2 -6
- package/dist/editor/core/containers.d.ts.map +1 -1
- package/dist/editor/core/containers.js +2 -1
- package/dist/editor/core/containers.js.map +1 -1
- package/dist/editor/core/customTool.d.ts +8 -2
- package/dist/editor/core/customTool.d.ts.map +1 -1
- package/dist/editor/core/customTool.js.map +1 -1
- package/dist/editor/core/customToolEngine.d.ts +9 -1
- package/dist/editor/core/customToolEngine.d.ts.map +1 -1
- package/dist/editor/core/customToolEngine.js +12 -0
- package/dist/editor/core/customToolEngine.js.map +1 -1
- package/dist/editor/core/extensions.d.ts.map +1 -1
- package/dist/editor/core/extensions.js +1 -1
- package/dist/editor/core/extensions.js.map +1 -1
- package/dist/editor/core/handle.d.ts +18 -1
- package/dist/editor/core/handle.d.ts.map +1 -1
- package/dist/editor/core/handle.js +2 -1
- package/dist/editor/core/handle.js.map +1 -1
- package/dist/editor/core/handleWatchers.d.ts +30 -0
- package/dist/editor/core/handleWatchers.d.ts.map +1 -1
- package/dist/editor/core/handleWatchers.js +15 -0
- package/dist/editor/core/handleWatchers.js.map +1 -1
- package/dist/editor/core/nodeNames.d.ts +7 -0
- package/dist/editor/core/nodeNames.d.ts.map +1 -0
- package/dist/editor/core/nodeNames.js +2 -0
- package/dist/editor/core/nodeNames.js.map +1 -0
- package/dist/editor/core/schema.d.ts.map +1 -1
- package/dist/editor/core/schema.js +4 -2
- package/dist/editor/core/schema.js.map +1 -1
- package/dist/editor/core/toolCommands.d.ts +78 -0
- package/dist/editor/core/toolCommands.d.ts.map +1 -0
- package/dist/editor/core/toolCommands.js +2 -0
- package/dist/editor/core/toolCommands.js.map +1 -0
- package/dist/editor/core/toolDefinition.d.ts +139 -0
- package/dist/editor/core/toolDefinition.d.ts.map +1 -0
- package/dist/editor/core/toolDefinition.js +28 -0
- package/dist/editor/core/toolDefinition.js.map +1 -0
- package/dist/editor/core/toolDescriptor.d.ts +71 -2
- package/dist/editor/core/toolDescriptor.d.ts.map +1 -1
- package/dist/editor/core/toolDescriptor.js +6 -2
- package/dist/editor/core/toolDescriptor.js.map +1 -1
- package/dist/editor/core/toolOptions.d.ts +157 -0
- package/dist/editor/core/toolOptions.d.ts.map +1 -0
- package/dist/editor/core/toolOptions.js +21 -0
- package/dist/editor/core/toolOptions.js.map +1 -0
- package/dist/editor/core/toolState.d.ts +46 -0
- package/dist/editor/core/toolState.d.ts.map +1 -0
- package/dist/editor/core/toolState.js +2 -0
- package/dist/editor/core/toolState.js.map +1 -0
- package/dist/editor/core/tools.d.ts +3 -1
- package/dist/editor/core/tools.d.ts.map +1 -1
- package/dist/editor/core/tools.js.map +1 -1
- package/dist/editor/core/viewIsland.d.ts.map +1 -1
- package/dist/editor/core/viewIsland.js +2 -1
- package/dist/editor/core/viewIsland.js.map +1 -1
- package/dist/editor/features/builtins.d.ts +9 -4
- package/dist/editor/features/builtins.d.ts.map +1 -1
- package/dist/editor/features/builtins.js +36 -7
- package/dist/editor/features/builtins.js.map +1 -1
- package/dist/editor/features/search/SearchPanel.d.ts.map +1 -1
- package/dist/editor/features/search/SearchPanel.js +26 -12
- package/dist/editor/features/search/SearchPanel.js.map +1 -1
- package/dist/editor/features/search/revealMatch.d.ts +16 -0
- package/dist/editor/features/search/revealMatch.d.ts.map +1 -1
- package/dist/editor/features/search/revealMatch.js +22 -0
- package/dist/editor/features/search/revealMatch.js.map +1 -1
- package/dist/editor/features/search/searchPlugin.d.ts +8 -3
- package/dist/editor/features/search/searchPlugin.d.ts.map +1 -1
- package/dist/editor/features/search/searchPlugin.js +17 -6
- package/dist/editor/features/search/searchPlugin.js.map +1 -1
- package/dist/editor/features/search/searchSnapshot.d.ts +14 -0
- package/dist/editor/features/search/searchSnapshot.d.ts.map +1 -0
- package/dist/editor/features/search/searchSnapshot.js +17 -0
- package/dist/editor/features/search/searchSnapshot.js.map +1 -0
- package/dist/editor/features/search/searchTool.d.ts +3 -0
- package/dist/editor/features/search/searchTool.d.ts.map +1 -1
- package/dist/editor/features/search/searchTool.js +30 -1
- package/dist/editor/features/search/searchTool.js.map +1 -1
- package/dist/editor/features/toc/RichTextToc.d.ts +29 -1
- package/dist/editor/features/toc/RichTextToc.d.ts.map +1 -1
- package/dist/editor/features/toc/RichTextToc.js +55 -10
- package/dist/editor/features/toc/RichTextToc.js.map +1 -1
- package/dist/editor/features/toc/documentHeadings.d.ts +30 -0
- package/dist/editor/features/toc/documentHeadings.d.ts.map +1 -0
- package/dist/editor/features/toc/documentHeadings.js +20 -0
- package/dist/editor/features/toc/documentHeadings.js.map +1 -0
- package/dist/editor/features/toc/headings.d.ts +6 -2
- package/dist/editor/features/toc/headings.d.ts.map +1 -1
- package/dist/editor/features/toc/headings.js +4 -2
- package/dist/editor/features/toc/headings.js.map +1 -1
- package/dist/editor/features/toc/tocSnapshot.d.ts +10 -0
- package/dist/editor/features/toc/tocSnapshot.d.ts.map +1 -0
- package/dist/editor/features/toc/tocSnapshot.js +8 -0
- package/dist/editor/features/toc/tocSnapshot.js.map +1 -0
- package/dist/editor/features/toc/tocTool.d.ts +6 -0
- package/dist/editor/features/toc/tocTool.d.ts.map +1 -0
- package/dist/editor/features/toc/tocTool.js +21 -0
- package/dist/editor/features/toc/tocTool.js.map +1 -0
- package/dist/editor/hooks/useRichTextToolState.d.ts +43 -0
- package/dist/editor/hooks/useRichTextToolState.d.ts.map +1 -0
- package/dist/editor/hooks/useRichTextToolState.js +77 -0
- package/dist/editor/hooks/useRichTextToolState.js.map +1 -0
- package/dist/editor/index.d.ts +8 -0
- package/dist/editor/index.d.ts.map +1 -1
- package/dist/editor/index.js +3 -0
- package/dist/editor/index.js.map +1 -1
- package/docs/api/editor-extension.en.md +223 -23
- package/docs/api/editor-extension.md +224 -22
- package/docs/api/migration-notes.en.md +36 -1
- package/docs/api/migration-notes.md +35 -0
- package/docs/components/content/rich-text-toc.en.md +13 -11
- package/docs/components/content/rich-text-toc.md +12 -10
- package/docs/components/index.en.md +2 -2
- package/docs/components/index.md +1 -1
- package/docs/components/inputs/rich-text-editor.en.md +227 -11
- package/docs/components/inputs/rich-text-editor.md +228 -10
- package/docs/getting-started.en.md +7 -7
- package/docs/getting-started.md +6 -6
- package/docs/glossary.en.md +52 -48
- package/docs/glossary.md +51 -47
- package/package.json +1 -1
- package/src/editor/RichTextEditor.tsx +94 -39
- package/src/editor/RichTextView.tsx +2 -1
- package/src/editor/core/containers.ts +8 -6
- package/src/editor/core/customTool.ts +8 -2
- package/src/editor/core/customToolEngine.ts +20 -1
- package/src/editor/core/extensions.ts +2 -1
- package/src/editor/core/handle.ts +25 -1
- package/src/editor/core/handleWatchers.ts +45 -0
- package/src/editor/core/nodeNames.ts +7 -0
- package/src/editor/core/schema.ts +5 -2
- package/src/editor/core/toolCommands.ts +85 -0
- package/src/editor/core/toolDefinition.ts +208 -0
- package/src/editor/core/toolDescriptor.ts +94 -3
- package/src/editor/core/toolOptions.ts +211 -0
- package/src/editor/core/toolState.ts +56 -0
- package/src/editor/core/tools.ts +3 -1
- package/src/editor/core/viewIsland.ts +5 -4
- package/src/editor/features/builtins.ts +72 -13
- package/src/editor/features/search/SearchPanel.tsx +38 -14
- package/src/editor/features/search/revealMatch.ts +45 -0
- package/src/editor/features/search/searchPlugin.ts +40 -11
- package/src/editor/features/search/searchSnapshot.ts +29 -0
- package/src/editor/features/search/searchTool.ts +51 -1
- package/src/editor/features/toc/RichTextToc.tsx +108 -10
- package/src/editor/features/toc/documentHeadings.ts +59 -0
- package/src/editor/features/toc/headings.ts +10 -3
- package/src/editor/features/toc/tocSnapshot.ts +24 -0
- package/src/editor/features/toc/tocTool.ts +41 -0
- package/src/editor/hooks/useRichTextToolState.ts +153 -0
- package/src/editor/index.ts +19 -0
|
@@ -11,7 +11,7 @@ story: src/editor/features/toc/RichTextToc.stories.tsx
|
|
|
11
11
|
|
|
12
12
|
## Кратко для AI
|
|
13
13
|
|
|
14
|
-
`RichTextToc` — Оглавление Документа рядом с Просмотром (`RichTextView`) или Редактором в `readOnly`. Свёрнуто — полоса штрихов, по одному на заголовок, штрих текущего раздела подсвечен. Наведение на полосу раскрывает список заголовков, клик по пункту прокручивает к заголовку и ставит на него фокус. Компонент клиентский и подключается только явно: экран ставит его в свою колонку и передаёт `target` — ref на Просмотр или Хэндл Редактора. Заголовки читаются из DOM цели, якорей и `id` в Документе нет, адрес страницы не меняется. Живёт во входе `@olddevs/ui/editor`. Решение — ADR-0066.
|
|
14
|
+
`RichTextToc` — Оглавление Документа рядом с Просмотром (`RichTextView`) или Редактором в `readOnly`. Свёрнуто — полоса штрихов, по одному на заголовок, штрих текущего раздела подсвечен. Наведение на полосу раскрывает список заголовков, клик по пункту прокручивает к заголовку и ставит на него фокус. Компонент клиентский и подключается только явно: экран ставит его в свою колонку и передаёт `target` — ref на Просмотр или Хэндл Редактора. Заголовки читаются из DOM цели, якорей и `id` в Документе нет, адрес страницы не меняется. Проп `levels` оставляет только нужные уровни из `h1`–`h4`. Живёт во входе `@olddevs/ui/editor`. Решение — ADR-0066.
|
|
15
15
|
|
|
16
16
|
```tsx
|
|
17
17
|
import { RichTextToc, RichTextView } from "@olddevs/ui/editor";
|
|
@@ -28,7 +28,7 @@ import { RichTextToc, RichTextView } from "@olddevs/ui/editor";
|
|
|
28
28
|
- Навигация по страницам приложения или разделам сайта — это `SideNav` или `Tabs`, а не Оглавление Документа.
|
|
29
29
|
- Ссылка на раздел, которой можно поделиться: Оглавление ведёт к заголовку без якоря (`#slug`), адрес страницы не меняется. Ссылка на раздел потребует отдельного ADR об `id` в Схеме.
|
|
30
30
|
- Оглавление как часть самого Документа, Блоком внутри текста: такого Блока в Схеме нет.
|
|
31
|
-
- Оглавление нужно внутри поля Редактора, а своей колонки у экрана нет — это Инструмент `toc` Редактора: та же полоса в левом поле рамки, липкая под Панелью и в поле с `maxHeight` (страница `RichTextEditor`, раздел «Оглавление»).
|
|
31
|
+
- Оглавление нужно внутри поля Редактора, а своей колонки у экрана нет — это Инструмент `toc` Редактора: та же полоса в левом поле рамки, липкая под Панелью и в поле с `maxHeight` (страница `RichTextEditor`, раздел «Оглавление»). Те же `offsetVar`, `levels` и `listWidth` там задаёт запись `tool("toc", options)`, а заголовки и текущий раздел экран читает хуком `useRichTextToolState`. У отдельного `RichTextToc` Состояния и Команд нет: у него нет Хэндла.
|
|
32
32
|
|
|
33
33
|
## Минимальный пример
|
|
34
34
|
|
|
@@ -55,20 +55,22 @@ export function Article({ html }: { html: string }) {
|
|
|
55
55
|
|
|
56
56
|
## Public API
|
|
57
57
|
|
|
58
|
-
| Prop | Значение
|
|
59
|
-
| ----------- |
|
|
60
|
-
| `target` | Обязательный. Ref на `RichTextView` или на Хэндл `RichTextEditor`, чьи заголовки показывает Оглавление. Заголовки читаются после монтирования: до гидратации полосы нет.
|
|
61
|
-
| `offsetVar` | Имя CSS-переменной с высотой липкой шапки экрана, по умолчанию `--topbar-h` (её публикует `TopBar`). Переход ставит заголовок под шапку, подсветка считает раздел от её нижнего края.
|
|
62
|
-
| `listWidth` | Ширина списка: длина CSS или пиксели, по умолчанию `240`. Постоянная, а не по самому длинному заголовку; заголовок, что не помещается, обрезается многоточием.
|
|
63
|
-
| `
|
|
58
|
+
| Prop | Значение |
|
|
59
|
+
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
60
|
+
| `target` | Обязательный. Ref на `RichTextView` или на Хэндл `RichTextEditor`, чьи заголовки показывает Оглавление. Заголовки читаются после монтирования: до гидратации полосы нет. |
|
|
61
|
+
| `offsetVar` | Имя CSS-переменной с высотой липкой шапки экрана, по умолчанию `--topbar-h` (её публикует `TopBar`). Переход ставит заголовок под шапку, подсветка считает раздел от её нижнего края. |
|
|
62
|
+
| `listWidth` | Ширина списка: длина CSS или пиксели, по умолчанию `240`. Постоянная, а не по самому длинному заголовку; заголовок, что не помещается, обрезается многоточием. |
|
|
63
|
+
| `levels` | Уровни заголовков, которые попадают в Оглавление: подмножество `1`–`4`, по умолчанию все четыре. Заголовка другого уровня нет ни в полосе, ни в списке, а отступ пунктов считается от самого крупного из оставшихся. Набор читается при каждой сборке списка, поэтому его смена пересобирает список. |
|
|
64
|
+
| `className` | Классы корня. Остальные свойства `div` уходят на корень. |
|
|
64
65
|
|
|
65
66
|
### Какие заголовки попадают
|
|
66
67
|
|
|
67
68
|
- `h1`–`h4` на верхнем уровне Документа и внутри Обёртки.
|
|
68
69
|
- Не попадают заголовки в колонках Раскладки: два заголовка на одной высоте в соседних колонках путают подсветку текущего раздела. Не попадают и заголовки внутри своих Блоков и их Островов: их содержимое — часть Блока, а не структура Документа.
|
|
69
|
-
-
|
|
70
|
+
- Проп `levels` сужает набор: `levels={[2, 3]}` оставляет только `h2` и `h3`. Остальных уровней нет ни в полосе, ни в списке; пустой массив оставляет ноль заголовков.
|
|
71
|
+
- Меньше двух заголовков (после `levels`) — компонент ничего не рисует, колонка экрана остаётся пустой.
|
|
70
72
|
|
|
71
|
-
Отступ пункта и длина штриха считаются по уровню **относительно самого крупного заголовка в
|
|
73
|
+
Отступ пункта и длина штриха считаются по уровню **относительно самого крупного заголовка из попавших в Оглавление**: если Документ начинается с `h2`, у `h2` нет отступа и самый длинный штрих. Чем глубже уровень, тем короче штрих.
|
|
72
74
|
|
|
73
75
|
### Живой Документ
|
|
74
76
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
sourceHash:
|
|
2
|
+
sourceHash: 3aa1b5325b8c
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Component catalog
|
|
@@ -29,7 +29,7 @@ page.
|
|
|
29
29
|
## Input fields
|
|
30
30
|
|
|
31
31
|
- [TextControl](inputs/text-control.md): `TextControl` — the base text field: a single line or multi-line text, with an icon and a placeholder inside; the other input fields are built on it
|
|
32
|
-
- [RichTextEditor](inputs/rich-text-editor.md): `RichTextEditor`, the `useRichTextEditorState`
|
|
32
|
+
- [RichTextEditor](inputs/rich-text-editor.md): `RichTextEditor`, the `useRichTextEditorState` and `useRichTextToolState` hooks, the `tool` and `defineTool` helpers, the `RICH_TEXT_PRESETS` Presets, a custom Tool (`CustomTool`) — a rich text editor: headings, lists, links, inline formatting (bold, italic, color). Suitable for long articles and descriptions, and the value is stored as HTML. A separate entry point `@olddevs/ui/editor`; the consumer installs the Tiptap engine with the command from that page
|
|
33
33
|
- [Editor extension API](../api/editor-extension.md) — a reference for those who extend the Editor with custom Tools, buttons and Loaders: what can be plugged in and what will not change between versions
|
|
34
34
|
- [NumberInput](inputs/number-input.md): `NumberInput` — a number field with "increase" and "decrease" buttons, bounds and formatting; an empty field yields `null`, not zero
|
|
35
35
|
- [Choice controls](inputs/choice-controls.md): `Checkbox`, `CheckboxGroup`, `Radio`, `RadioGroup` — checkboxes and radio buttons: mark several independent options or choose exactly one from a set
|
package/docs/components/index.md
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
## Поля ввода
|
|
25
25
|
|
|
26
26
|
- [TextControl](inputs/text-control.md): `TextControl` — базовое текстовое поле: одна строка или многострочный текст, с иконкой и плейсхолдером внутри; на нём построены остальные поля ввода
|
|
27
|
-
- [RichTextEditor](inputs/rich-text-editor.md): `RichTextEditor`,
|
|
27
|
+
- [RichTextEditor](inputs/rich-text-editor.md): `RichTextEditor`, хуки `useRichTextEditorState` и `useRichTextToolState`, помощники `tool` и `defineTool`, Пресеты `RICH_TEXT_PRESETS`, свой Инструмент (`CustomTool`) — редактор форматированного текста: заголовки, списки, ссылки, выделение слов (жирный, курсив, цвет). Подходит для длинных статей и описаний, значение хранится как HTML. Отдельный вход `@olddevs/ui/editor`; движок Tiptap потребитель ставит сам — командой со страницы
|
|
28
28
|
- [API расширения Редактора](../api/editor-extension.md) — справочник для тех, кто дописывает Редактор своими Инструментами, кнопками и Загрузчиками: что можно подключить и что не изменится между версиями
|
|
29
29
|
- [NumberInput](inputs/number-input.md): `NumberInput` — поле для числа с кнопками «больше» и «меньше», границами и форматированием; пустое поле даёт `null`, а не ноль
|
|
30
30
|
- [Choice controls](inputs/choice-controls.md): `Checkbox`, `CheckboxGroup`, `Radio`, `RadioGroup` — флажки и радиокнопки: отметить несколько независимых вариантов или выбрать ровно один из набора
|
|
@@ -7,6 +7,24 @@ exports:
|
|
|
7
7
|
RichTextEditorHandle,
|
|
8
8
|
RichTextDocument,
|
|
9
9
|
useRichTextEditorState,
|
|
10
|
+
useRichTextToolState,
|
|
11
|
+
RichTextToolState,
|
|
12
|
+
tool,
|
|
13
|
+
defineTool,
|
|
14
|
+
ConfiguredTool,
|
|
15
|
+
ToolDefinition,
|
|
16
|
+
ToolDefinitionInput,
|
|
17
|
+
ToolDefinitionContext,
|
|
18
|
+
CustomToolBody,
|
|
19
|
+
RichTextTools,
|
|
20
|
+
SearchToolOptions,
|
|
21
|
+
TocToolOptions,
|
|
22
|
+
TocLevel,
|
|
23
|
+
SearchToolCommands,
|
|
24
|
+
TocToolCommands,
|
|
25
|
+
SearchToolState,
|
|
26
|
+
TocToolState,
|
|
27
|
+
TocToolHeading,
|
|
10
28
|
Tool,
|
|
11
29
|
ToolName,
|
|
12
30
|
RichTextEditorPreset,
|
|
@@ -37,7 +55,7 @@ category: inputs
|
|
|
37
55
|
status: stable
|
|
38
56
|
source: src/editor/RichTextEditor.tsx
|
|
39
57
|
story: src/editor/RichTextEditor.stories.tsx
|
|
40
|
-
sourceHash:
|
|
58
|
+
sourceHash: b5a4f8fde844
|
|
41
59
|
---
|
|
42
60
|
|
|
43
61
|
# RichTextEditor
|
|
@@ -55,7 +73,8 @@ sourceHash: 3eaf71d10511
|
|
|
55
73
|
- **Code block.** It has a header (language on the left, "Copy" on the right), a language (Python, TypeScript, JavaScript, Go, HTML, JSON, YAML, Markdown or plain text) and lowlight highlighting. Only `class="language-…"` is written into the Document, and for `RichTextView` the server helper from `@olddevs/ui/editor/highlight` highlights the Blocks.
|
|
56
74
|
- **HTML mode.** The "HTML" button of the fixed Toolbar (the `source` Tool) swaps the Document for a field with its source HTML; leaving the mode, `blur`, form submission and `getHTML()` apply the edit through the Schema, like an incoming `value`.
|
|
57
75
|
- **Feature set.** `preset="full" | "basic"` or an exact `tools` list; a disabled Tool removes both its button and its markup from the Schema.
|
|
58
|
-
- **
|
|
76
|
+
- **Tool options, state and commands.** The built-in `search` and `toc` Tools have options: a `tool("toc", { offsetVar: "--header-h", levels: [2, 3] })` entry sits in `tools` next to the names, switches the Tool on and configures it (for Search, the starting flags `matchCase`, `wholeWord` and `regex`; for the Table of contents, `offsetVar`, `levels` and `listWidth`). The `useRichTextToolState(ref, "search")` hook reads the state: for Search, whether the find panel is open, the query, the number of matches and the number of the current one; for the Table of contents, the headings and the current section. The hook returns `null` before mount, on the server, after the Editor unmounts and when the Tool is not in the set. Commands are called through the Handle: `ref.current?.tool("search")?.open()`, `ref.current?.tool("toc")?.jumpTo(2)`; each returns a `boolean`. Options are read once, when the engine is created. A custom Tool with the same three parts is described with `defineTool` (["Options, state and commands"](../../api/editor-extension.md#опции-состояние-и-команды)).
|
|
77
|
+
- **Handle (`ref`).** The screen drives the field through `ref`: `insertHTML`, `insertText`, `getText`, `getSelectedText`, `isEmpty`, `focus(position?)`, `blur`, `undo`, `redo`, `tool(name)`; `getJSON()`/`setJSON()` export and load the Document as a lossless JSON envelope (see the "JSON" section), and `defaultValue` accepts the envelope too. The screen builds its own toolbar with the `useRichTextEditorState(ref, selector)` hook. `openSearch` is deprecated (removed in 5.0): use `tool("search")?.open()` instead. Before Readiness (on the server, before mount, while Tool Loaders are running) the methods are silent: see the "Handle (`ref`) and a custom toolbar outside the field" section.
|
|
59
78
|
- **Events.** The screen learns about field events through the `onReady()`, `onFocus()` and `onBlur()` callbacks (see the "Events" section).
|
|
60
79
|
- **Skeleton and Loader.** Until Readiness the field shows a Skeleton: the frame and the Document as static HTML without input, with no height jump. The Skeleton's Document is `value` cleaned by the Schema, and only in the browser; on the server the Skeleton is a frame without a Document. The Engine part of a custom Tool can be passed as a Loader (`engine: () => import(…)`); if it fails, the result is a field error with "Retry".
|
|
61
80
|
- **Status line and Field notice.** Below the field, inside the frame, are the Status line (`status`: the screen's React node at the start, the kit's counter at the end, no `role`) and the Field notice: the engine command `showNotice({ tone: "status" | "error", text })` / `showNotice(null)`. `status` is only announced, `error` is shown as a `role="alert"` line until the next edit; image messages go through the same channel.
|
|
@@ -305,7 +324,7 @@ The `link` Tool: the "Link" button on the Toolbar and `Ctrl/⌘+K` open a popove
|
|
|
305
324
|
|
|
306
325
|
## Tools and Presets
|
|
307
326
|
|
|
308
|
-
`preset="full"` (default) is everything in the release except the Table of contents `toc`: it changes the layout of the field, so it is switched on only explicitly. `preset="basic"` is for comments and short descriptions. `tools` is an exact list and takes precedence over a Preset; descriptions of custom Tools can be mixed in with the kit's names (see the "Custom Tool" section). The type of a `tools` element is `Tool` (a kit name or a `CustomTool`), the type of a kit name is `ToolName`; both are exported from `@olddevs/ui/editor`.
|
|
327
|
+
`preset="full"` (default) is everything in the release except the Table of contents `toc`: it changes the layout of the field, so it is switched on only explicitly. `preset="basic"` is for comments and short descriptions. `tools` is an exact list and takes precedence over a Preset; descriptions of custom Tools can be mixed in with the kit's names (see the "Custom Tool" section). The type of a `tools` element is `Tool` (a kit name, a Configured Tool from `tool(…)`, a `defineTool(…)` definition or a `CustomTool`), the type of a kit name is `ToolName`; both are exported from `@olddevs/ui/editor`.
|
|
309
328
|
|
|
310
329
|
Presets are also exported as arrays of names — `RICH_TEXT_PRESETS.full` and `RICH_TEXT_PRESETS.basic` — so that you can assemble your own set from them: `tools={[...RICH_TEXT_PRESETS.full, mergeTagTool]}`. The arrays are frozen. A Preset's content is "what the kit considers the full set", not a frozen list: a new built-in Tool arrives in a Preset in a minor version. If you need a stable set, pass an exact list of names.
|
|
311
330
|
|
|
@@ -354,6 +373,34 @@ A name enters the `Tool` type together with the Tool itself, not earlier: wideni
|
|
|
354
373
|
|
|
355
374
|
`preset` and `tools` are read **when the engine is created**: the Schema of a live ProseMirror cannot be changed. To change the set on a mounted field, remount it with a key (`key`) — the edit history is lost in the process.
|
|
356
375
|
|
|
376
|
+
### Options of a built-in Tool: `tool()`
|
|
377
|
+
|
|
378
|
+
Two built-in Tools have options: `search` and `toc`. You set them with the `tool(name, options)` helper, which returns a Configured Tool: an entry for `tools` that sits next to the names and the descriptions of custom Tools (ADR-0069).
|
|
379
|
+
|
|
380
|
+
```tsx
|
|
381
|
+
import { RICH_TEXT_PRESETS, RichTextEditor, tool } from "@olddevs/ui/editor";
|
|
382
|
+
|
|
383
|
+
// Build the set once per module, not in a render: `tools` is read when the engine is created.
|
|
384
|
+
const tools = [
|
|
385
|
+
...RICH_TEXT_PRESETS.full,
|
|
386
|
+
tool("toc", { offsetVar: "--header-h", levels: [2, 3] }),
|
|
387
|
+
tool("search", { matchCase: true }),
|
|
388
|
+
];
|
|
389
|
+
|
|
390
|
+
<RichTextEditor tools={tools} defaultValue={html} />;
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
- **An entry switches the Tool on.** `tool("toc", …)` enables `toc` without the `"toc"` string. If `tools` also holds the bare name (for example, `search` from `full`), the Configured Tool wins: there is still one Find button and one find panel.
|
|
394
|
+
- **Pass only what you change.** The remaining options come from the kit's defaults. Nested objects are merged and arrays are replaced whole: `levels: [2, 3]` means exactly these levels, not an addition to the default.
|
|
395
|
+
- **The position in the UI does not change.** A configured built-in Tool sits where it would sit by name; a custom Tool sits in `tools` order.
|
|
396
|
+
- **Types infer the options from the name.** `tool("toc", { matchCase: true })` and `tool("bold", {})` do not compile: `tool()` accepts only Tools that have options, which today means `search` and `toc`.
|
|
397
|
+
- **Two `tool()` entries with the same name** are a set error, like two custom Tools with the same name: a field error ("The editor couldn't start") and a `console.error` in development.
|
|
398
|
+
- **A wrong value** (from JS without types: `levels: [5]`, `offsetVar: "header"`) is replaced with the default, with a single `console.error` per Tool in development; the field keeps working.
|
|
399
|
+
- **Options are read once**, together with the `tools` set, when the engine is created. To change them on a mounted field, remount it with a key (`key`) — the edit history is lost in the process.
|
|
400
|
+
- **Server.** `tool()` pulls neither Tiptap nor React into the module, so a `tools` set that uses it can be built in a server module. A configured built-in Tool is made of data, and if its options are data, you can pass it to a client component as a prop.
|
|
401
|
+
|
|
402
|
+
The options of each Tool are described in its own section: "Find and replace" and "Table of contents". The options of a custom Tool are set by `defineTool` (["Options, state and commands"](../../api/editor-extension.md#опции-состояние-и-команды)).
|
|
403
|
+
|
|
357
404
|
## Custom Tool
|
|
358
405
|
|
|
359
406
|
A custom Tool is the description of one Editor capability: its Blocks and Marks, Schema rules as data, and an Engine part (Tiptap extensions). It is passed in `tools` mixed with the kit's names and follows the same rules: **a Tool that is not passed removes its Blocks and Marks from the Schema** — they do not pass through `value`, paste or commands; a Block unwraps into its content and an Atom disappears. The decision is ADR-0058, sections 1 and 2.
|
|
@@ -443,7 +490,7 @@ An attribute cannot contain HTML: a value with `<` does not pass any rule, and t
|
|
|
443
490
|
|
|
444
491
|
On an error the engine is not created: under the field's frame there is a `role="alert"` line "The editor couldn't start. The document is unchanged." (`editor.toolsFailed`), the hidden form field and `getHTML()` return the incoming value, `onChange` stays silent, and `ref.current.editor` is `null`. The cause is in `console.error` in development mode, with the Tool's name. This way data loss is visible before the first save, not on the server. **An unknown string in `tools` is still dropped silently**: the error applies only to descriptions of custom Tools.
|
|
445
492
|
|
|
446
|
-
Buttons, Insert menu items, Block actions and Text cards are described in the sections below; a Tool can report an error to the author with the `showNotice` command (see the "Status line and Field notice" section). The whole extension API and the boundary of its stability (what falls under semver, what counts as breaking) are described on the [Editor extension API](../../api/editor-extension.md) page.
|
|
493
|
+
Buttons, Insert menu items, Block actions and Text cards are described in the sections below; a Tool can report an error to the author with the `showNotice` command (see the "Status line and Field notice" section). The whole extension API and the boundary of its stability (what falls under semver, what counts as breaking) are described on the [Editor extension API](../../api/editor-extension.md) page. If a Tool has settings and the screen needs its state and commands, describe it with `defineTool`: ["Options, state and commands"](../../api/editor-extension.md#опции-состояние-и-команды).
|
|
447
494
|
|
|
448
495
|
### Engine part Loader
|
|
449
496
|
|
|
@@ -1152,7 +1199,7 @@ The `required` check in the mode is evaluated against **the field's text** on ev
|
|
|
1152
1199
|
|
|
1153
1200
|
## Find and replace
|
|
1154
1201
|
|
|
1155
|
-
The `search` Tool (present in `full` and in `basic`) is find and replace over the Document. The "Find" button (a magnifying glass icon) sits at the right edge of the fixed Toolbar, before the Shortcuts reference; `Ctrl/⌘+F` with focus in the Document opens the find panel (outside the field the browser's find works), `Ctrl/⌘+Alt+F` opens it with focus in the replace field, and `
|
|
1202
|
+
The `search` Tool (present in `full` and in `basic`) is find and replace over the Document. The "Find" button (a magnifying glass icon) sits at the right edge of the fixed Toolbar, before the Shortcuts reference; `Ctrl/⌘+F` with focus in the Document opens the find panel (outside the field the browser's find works), `Ctrl/⌘+Alt+F` opens it with focus in the replace field, and the `open` command of the Tool (`ref.current?.tool("search")?.open()`) opens it from screen code.
|
|
1156
1203
|
|
|
1157
1204
|
```tsx
|
|
1158
1205
|
<RichTextEditor preset="basic" /> // the "Find" button and Ctrl/⌘+F are already there
|
|
@@ -1171,6 +1218,74 @@ The `search` Tool (present in `full` and in `basic`) is find and replace over th
|
|
|
1171
1218
|
|
|
1172
1219
|
Message catalog strings: `editor.search` (the button and the `Ctrl/⌘+F` Shortcuts reference row), `searchReplace` (`Ctrl/⌘+Alt+F`), `searchPanel`, `searchField`, `replaceField`, `searchPrevious`, `searchNext`, `searchClose`, `searchMatchCase`, `searchWholeWord`, `searchRegex`, `replaceOne`, `replaceAll`, `searchCount({ current, total })`, `searchNoMatches`, `searchInvalid`.
|
|
1173
1220
|
|
|
1221
|
+
### Find options
|
|
1222
|
+
|
|
1223
|
+
A `tool("search", options)` entry sets the **starting flags**: the panel opens with them, and the commands use them until the search is closed. The flags stay switchable in the panel, and the kit does not remember them between openings: closing the panel returns them to the starting values.
|
|
1224
|
+
|
|
1225
|
+
| Option | Meaning | Default |
|
|
1226
|
+
| ----------- | -------------------------------------------- | ------- |
|
|
1227
|
+
| `matchCase` | The panel opens with "Match case" on | `false` |
|
|
1228
|
+
| `wholeWord` | The panel opens with "Whole words" on | `false` |
|
|
1229
|
+
| `regex` | The panel opens with "Regular expression" on | `false` |
|
|
1230
|
+
|
|
1231
|
+
```tsx
|
|
1232
|
+
// Searching code in Documents: case matters, so nobody has to switch the flag on each time.
|
|
1233
|
+
<RichTextEditor tools={[...RICH_TEXT_PRESETS.full, tool("search", { matchCase: true })]} />
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
### Find state and commands
|
|
1237
|
+
|
|
1238
|
+
The state is read with `useRichTextToolState(ref, "search")`, and the commands are called on `ref.current?.tool("search")`. The general rules for both are in the "Handle (`ref`) and your own toolbar outside the field" section.
|
|
1239
|
+
|
|
1240
|
+
| State field | Meaning |
|
|
1241
|
+
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1242
|
+
| `open` | The find panel is open. |
|
|
1243
|
+
| `query` | The query. Closing the panel removes the query and the highlighting; without a panel, the `setQuery` command sets it. |
|
|
1244
|
+
| `flags` | The flags `{ matchCase, wholeWord, regex }`. Closing the panel returns them to the starting values from the options. |
|
|
1245
|
+
| `total` | How many matches of the query are in the Document. |
|
|
1246
|
+
| `current` | The index of the current match, from 0; `-1` means no matches. |
|
|
1247
|
+
| `error` | The error text if the query is an invalid regular expression (the JavaScript engine's message, not localized); otherwise `null`. With an error there are no matches. |
|
|
1248
|
+
|
|
1249
|
+
The state has no match positions.
|
|
1250
|
+
|
|
1251
|
+
| Command | What it does |
|
|
1252
|
+
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1253
|
+
| `open({ replace?, query? })` | Opens the panel, like the "Find" button and `Ctrl/⌘+F`. `query` puts the query in the field and in the Document highlighting at once; without it, the text selected within one Block becomes the query. `replace: true` puts focus in the replace field. `false` in HTML mode. |
|
|
1254
|
+
| `close()` | Closes the panel and removes the highlighting. Focus and the caret stay where they were, unlike `Escape` in the panel. A panel that is already closed is not an error. |
|
|
1255
|
+
| `setQuery(query, flags?)` | Sets the query. Flags not named in `flags` (and flags set to `undefined`) come from the open panel, or from the options when there is none. A non-empty query scrolls to the first match after the caret. `true` means the query was applied, even with no matches; `false` in HTML mode. |
|
|
1256
|
+
| `next()`, `previous()` | They select the next or previous match, wrapping around at either end, and scroll the field's scroll area or the window to it, even when focus is outside the Document; they do not take focus. `false` means no matches. |
|
|
1257
|
+
| `replace(text)` | Replaces the current match: one edit, one undo; the next match becomes current. `false` means no matches or a field the author cannot edit. |
|
|
1258
|
+
| `replaceAll(text)` | Replaces all matches: one edit, one undo. The conditions are the same as for `replace`. |
|
|
1259
|
+
|
|
1260
|
+
The query and the flags live in the engine, so `setQuery`, `next` and `replace` work without an open panel: you can build your own find interface on them. The panel and the commands share one search, so a replacement made from code shows up in the panel. Scrolling after `next`, `previous` and `setQuery` arrives a moment after the call: its code loads on first use and is not part of the Editor's starting chunk.
|
|
1261
|
+
|
|
1262
|
+
**A counter in the Status line.** While the find panel is open, the screen can show "3 of 12" next to the character counter. Closing the panel removes the query, and the counter disappears.
|
|
1263
|
+
|
|
1264
|
+
```tsx
|
|
1265
|
+
import { useRef } from "react";
|
|
1266
|
+
import { RichTextEditor, useRichTextToolState } from "@olddevs/ui/editor";
|
|
1267
|
+
import type { RichTextEditorHandle } from "@olddevs/ui/editor";
|
|
1268
|
+
|
|
1269
|
+
function ArticleBody({ html }: { html: string }) {
|
|
1270
|
+
const ref = useRef<RichTextEditorHandle>(null);
|
|
1271
|
+
// `null` before mount and on the server, so the screen renders the same in both places.
|
|
1272
|
+
const search = useRichTextToolState(ref, "search");
|
|
1273
|
+
|
|
1274
|
+
return (
|
|
1275
|
+
<RichTextEditor
|
|
1276
|
+
ref={ref}
|
|
1277
|
+
aria-label="Article body"
|
|
1278
|
+
defaultValue={html}
|
|
1279
|
+
status={
|
|
1280
|
+
search?.open && search.query ? (
|
|
1281
|
+
<span>{search.total ? `${search.current + 1} of ${search.total}` : "No matches"}</span>
|
|
1282
|
+
) : null
|
|
1283
|
+
}
|
|
1284
|
+
/>
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
```
|
|
1288
|
+
|
|
1174
1289
|
## Table of contents
|
|
1175
1290
|
|
|
1176
1291
|
The `toc` Tool puts the Table of contents of the Document in the Editor's left gutter: the same strip of strokes and the same list as [`RichTextToc`](../content/rich-text-toc.md), but the Editor holds the place itself (ADR-0068). It is in neither Preset, `full` or `basic`: the Table of contents moves the text, and a kit update does not change the layout of screens that did not ask for it. Switch it on by name:
|
|
@@ -1179,6 +1294,8 @@ The `toc` Tool puts the Table of contents of the Document in the Editor's left g
|
|
|
1179
1294
|
<RichTextEditor tools={[...RICH_TEXT_PRESETS.full, "toc"]} defaultValue={html} />
|
|
1180
1295
|
```
|
|
1181
1296
|
|
|
1297
|
+
A `tool("toc", options)` entry also switches the Tool on and configures it at the same time (see "Table of contents options" below).
|
|
1298
|
+
|
|
1182
1299
|
- **Place.** A column to the left of the Document, inside the field frame. It exists only while there is a strip, from two `h1`–`h4` headings at the top level and in Wrappers: with the second heading the text moves right; without it the text stays at the edge, as without the Tool. The first stroke is level with the first line of the first heading.
|
|
1183
1300
|
- **Stickiness.** The strip sticks under the fixed Toolbar (and under the app header: `--topbar-h`, ADR-0016); without a Toolbar, to the edge of the window. In a field with `maxHeight` or `resizable` it sticks to the top of the scrolling area, and the highlight and the jump count from there; the page does not scroll.
|
|
1184
1301
|
- **The list** works as in `RichTextToc`: hover opens it to the right of the strip, Enter and Space open it from the keyboard, arrows move between items, Escape closes it; the current section is highlighted in the strip and in the list; the width is 240px, and a long heading is cut with an ellipsis. A click on an item does not close the list: moving the pointer away does.
|
|
@@ -1188,6 +1305,70 @@ The `toc` Tool puts the Table of contents of the Document in the Editor's left g
|
|
|
1188
1305
|
|
|
1189
1306
|
Message catalog string: `editor.toc` ("Table of contents"), the name of the strip and the label of the list.
|
|
1190
1307
|
|
|
1308
|
+
### Table of contents options
|
|
1309
|
+
|
|
1310
|
+
A `tool("toc", options)` entry switches the Tool on and configures it. The standalone [`RichTextToc`](../content/rich-text-toc.md#public-api) takes the same `offsetVar`, `levels` and `listWidth`: the two share one core.
|
|
1311
|
+
|
|
1312
|
+
| Option | Meaning | Default |
|
|
1313
|
+
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
|
|
1314
|
+
| `offsetVar` | The name of the CSS variable that holds the height of the screen's sticky header (ADR-0016), written in full with the two leading dashes: `--header-h`. A jump puts the heading under the header, and the highlight treats a section as current from the header's bottom edge. It does not move the Editor's own sticky Toolbar, which still sits under `--topbar-h`. | `--topbar-h` |
|
|
1315
|
+
| `levels` | The heading levels in the Table of contents: a subset of 1–4. A heading of any other level is in neither the strip nor the list, and item indentation is counted from the largest remaining level. An empty array and invalid values are replaced by the default. With fewer than two headings left, there is no gutter. | `[1, 2, 3, 4]` |
|
|
1316
|
+
| `listWidth` | The list width: pixels or a CSS length. Fixed rather than sized to the longest heading. | `240` |
|
|
1317
|
+
|
|
1318
|
+
```tsx
|
|
1319
|
+
// The screen's header publishes its height in `--header-h`; the Table of contents needs only H2 and H3.
|
|
1320
|
+
<RichTextEditor
|
|
1321
|
+
tools={[...RICH_TEXT_PRESETS.full, tool("toc", { offsetVar: "--header-h", levels: [2, 3] })]}
|
|
1322
|
+
defaultValue={html}
|
|
1323
|
+
/>
|
|
1324
|
+
```
|
|
1325
|
+
|
|
1326
|
+
### Table of contents state and commands
|
|
1327
|
+
|
|
1328
|
+
The state is read with `useRichTextToolState(ref, "toc")`, and the command is called on `ref.current?.tool("toc")`.
|
|
1329
|
+
|
|
1330
|
+
| State field | Meaning |
|
|
1331
|
+
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1332
|
+
| `headings` | The headings in reading order, the same as in the strip and the list: top level and Wrappers, only the levels from `levels`, no empty ones. Each is `{ level, text, index }`: a level 1–4, the text without markup, and the index of the list item, from 0. The list is returned even with one heading, although there is no gutter then. |
|
|
1333
|
+
| `current` | The index of the current section in `headings`, from 0; `-1` means no headings. It follows scrolling. After the Document is edited, `headings` changes at once, while `current` catches up with a delay while the list is rebuilt. |
|
|
1334
|
+
|
|
1335
|
+
The headings are read from the Document, not from the DOM: the list is ready on the first frame without waiting for the lazy Table of contents UI, and it changes together with the edit. The state has no DOM nodes or ProseMirror positions.
|
|
1336
|
+
|
|
1337
|
+
There is one command, `jumpTo(index)`: go to a heading of the list, like a click on an item. The index is counted in the same list (with `levels` applied, from 0, empty headings skipped) and is taken from the state snapshot: right after the Document is edited, it leads to the heading that the snapshot names, even though the Table of contents UI rebuilds its list with a delay. While editing, the caret goes to the end of the heading; in `readOnly`, the heading itself gets focus; scrolling puts it under the sticky header (`offsetVar`) and the Toolbar, and the heading's highlight fades. `false` means there is no heading with that index, the Table of contents UI has not loaded yet, or HTML mode is on. Fewer than two headings is not a reason to refuse: there is no gutter, but the list exists, and the jump works.
|
|
1338
|
+
|
|
1339
|
+
**External navigation.** The screen builds breadcrumbs, a minimap or a list of sections next to the field from the state, instead of parsing the Document's DOM a second time.
|
|
1340
|
+
|
|
1341
|
+
```tsx
|
|
1342
|
+
import type { RefObject } from "react";
|
|
1343
|
+
import { useRichTextToolState } from "@olddevs/ui/editor";
|
|
1344
|
+
import type { RichTextEditorHandle } from "@olddevs/ui/editor";
|
|
1345
|
+
|
|
1346
|
+
function SectionNav({ target }: { target: RefObject<RichTextEditorHandle | null> }) {
|
|
1347
|
+
const toc = useRichTextToolState(target, "toc");
|
|
1348
|
+
if (!toc) return null;
|
|
1349
|
+
|
|
1350
|
+
return (
|
|
1351
|
+
<nav aria-label="Sections">
|
|
1352
|
+
<ul>
|
|
1353
|
+
{toc.headings.map((heading) => (
|
|
1354
|
+
<li
|
|
1355
|
+
key={heading.index}
|
|
1356
|
+
aria-current={heading.index === toc.current ? "location" : undefined}
|
|
1357
|
+
>
|
|
1358
|
+
<button
|
|
1359
|
+
type="button"
|
|
1360
|
+
onClick={() => target.current?.tool("toc")?.jumpTo(heading.index)}
|
|
1361
|
+
>
|
|
1362
|
+
{heading.text}
|
|
1363
|
+
</button>
|
|
1364
|
+
</li>
|
|
1365
|
+
))}
|
|
1366
|
+
</ul>
|
|
1367
|
+
</nav>
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
```
|
|
1371
|
+
|
|
1191
1372
|
## Status line and Field notice
|
|
1192
1373
|
|
|
1193
1374
|
Under the field inside the frame there are two independent areas. The **Status line** is what the screen puts there itself: "Saved", a word count, a co-author. The **Field notice** is what the kit and Tools report: an upload result, a paste rejection. The first is a React node in the `status` prop, the second is data and an engine command.
|
|
@@ -1294,7 +1475,8 @@ import type { RichTextEditorHandle } from "@olddevs/ui/editor";
|
|
|
1294
1475
|
| `focus(position?)` | `"end"` (the default), `"start"` or `"restore"`: return focus to where the selection was, for example after your own dialog or menu. |
|
|
1295
1476
|
| `blur()` | Remove focus from the Document. |
|
|
1296
1477
|
| `undo()`, `redo()` | Undo and redo an edit; they work with the `history` Tool enabled. |
|
|
1297
|
-
| `
|
|
1478
|
+
| `tool(name)` | The commands of a Tool: `tool("search")?.open()`, `tool("toc")?.jumpTo(2)`. `null` means the Tool is not in the set, has no commands, or the engine has not been created yet. See "Tool commands: `handle.tool()`". |
|
|
1479
|
+
| `openSearch({ replace? })` | **Deprecated**, removed in 5.0: the same as `tool("search")?.open({ replace })`; warns once in development. |
|
|
1298
1480
|
| `canUndo()`, `canRedo()` | Whether `undo` and `redo` are available: `false` without `history`. |
|
|
1299
1481
|
| `getHTML()`, `getMarkdown()`, `setMarkdown(md)`, `clear()` | See "Public API" and "Markdown". |
|
|
1300
1482
|
| `getJSON()`, `setJSON(document): boolean` | The Document as a lossless JSON envelope, and replacing the Document with an envelope; `false` means the envelope was rejected. See "JSON". |
|
|
@@ -1362,6 +1544,35 @@ function Screen() {
|
|
|
1362
1544
|
|
|
1363
1545
|
**History with an external toolbar.** The Handle provides `undo()` and `redo()` and their availability. For the `disabled` state of an "Undo" button, get availability from the hook's slice — `editor.can().undo?.() === true`: the slice re-renders the toolbar right after an edit. `ref.current.canUndo()` is a one-off read in a handler, and it is not subscribed to re-rendering. Without the `history` Tool the engine has no `undo` command (hence the `?.`), and both buttons stay unavailable.
|
|
1364
1546
|
|
|
1547
|
+
### Tool state: `useRichTextToolState`
|
|
1548
|
+
|
|
1549
|
+
`useRichTextToolState(ref, name, selector?)` gives the screen the state of a Tool: a "3 of 12" counter from Find in the Status line, or external navigation built from the headings of the Table of contents. The hook sits next to `useRichTextEditorState` and follows the Handle's engine in the same way, but it returns not a slice of the Tiptap engine but a typed snapshot of the kit's contract. The state fields of the built-in Tools are in the "Find and replace" and "Table of contents" sections.
|
|
1550
|
+
|
|
1551
|
+
- **Name and type.** `name` is a Tool from the `tools` set; the snapshot type is inferred from the `RichTextTools` map: `search` returns `SearchToolState`, `toc` returns `TocToolState`. A name outside the map (a custom Tool without typing) gives `unknown`.
|
|
1552
|
+
- **Re-renders only when the snapshot changes.** The snapshot is recomputed on every engine transaction and on kit signals that the engine does not have: the find panel opening and closing, and the current section of the Table of contents changing on scroll. The screen re-renders when the result becomes different (plain data — primitives, arrays and plain objects — is compared by value, like `useRichTextEditorState`; `Date`, `Map`, `Set` and class instances are compared by reference), so typing that does not change the snapshot does not touch it. A custom Tool's `state` that creates such values anew on every call re-renders the screen on every transaction.
|
|
1553
|
+
- **A selector.** The third argument subscribes the screen to a slice: `useRichTextToolState(ref, "search", (state) => state.total)` re-renders it only when the number of matches changes. With `null`, the selector is not called.
|
|
1554
|
+
- **`null`** is what the hook returns before mount and in the screen's first render, on the server, after the Editor unmounts, and when the Tool is not in the set: Presets do not include `toc`, so you enable it by name or with a `tool("toc")` entry. This way the screen renders the same on the server and in the browser.
|
|
1555
|
+
- **A snapshot is data**: no DOM nodes, functions or ProseMirror positions. Do not mutate it.
|
|
1556
|
+
- **Cost.** The hook is not part of the `RichTextEditor` starting graph: only a screen that imports it pays for it.
|
|
1557
|
+
|
|
1558
|
+
The state of a custom Tool is declared with `defineTool` (the `state` field) and read with the same hook: ["Options, state and commands"](../../api/editor-extension.md#опции-состояние-и-команды).
|
|
1559
|
+
|
|
1560
|
+
### Tool commands: `handle.tool()`
|
|
1561
|
+
|
|
1562
|
+
`ref.current?.tool(name)` returns the commands object of a Tool: `tool("search")?.open({ query: "TODO" })`, `tool("toc")?.jumpTo(2)`. The commands of the built-in Tools are described in the "Find and replace" and "Table of contents" sections.
|
|
1563
|
+
|
|
1564
|
+
```tsx
|
|
1565
|
+
<button type="button" onClick={() => ref.current?.tool("search")?.open({ query: "TODO" })}>
|
|
1566
|
+
Find TODO
|
|
1567
|
+
</button>
|
|
1568
|
+
```
|
|
1569
|
+
|
|
1570
|
+
- **The type is inferred from the `RichTextTools` map**, by the Tool's name. A name outside the map gives `unknown`.
|
|
1571
|
+
- **`null`** when the Tool is not in `tools`, has no commands, or the engine has not been created yet. That is why the commands of a disabled Tool are unavailable both in types (`?.`) and at run time.
|
|
1572
|
+
- **Every command returns a `boolean`:** done or not (HTML mode, no matches, no such heading, a field that cannot be edited). A refusal is `false`, as with `insertHTML`, not an exception.
|
|
1573
|
+
- **Take the object on every call** instead of keeping it between renders: it is cheap, and this way you always work with the live engine.
|
|
1574
|
+
- **The Handle gets no new method for each command.** The commands of the built-in Tools go through `tool(name)`, not through new methods; `openSearch` remains for compatibility only. Commands of Marks, Blocks and anything a Tool has not declared are still called through `ref.current.editor`.
|
|
1575
|
+
|
|
1365
1576
|
## Events
|
|
1366
1577
|
|
|
1367
1578
|
The screen learns about the Editor through callback props; there is no event bus. The decision is recorded in ADR-0058, section 9.
|
|
@@ -1423,9 +1634,10 @@ export const pdfTool: CustomTool = { name: "acme_pdfDrop", engine: [PdfDrop] };
|
|
|
1423
1634
|
- `maxHeight` — the height limit of the Document area (a number is pixels, a string is a CSS length), beyond it the Document scrolls inside the frame; `resizable` — a height handle at the area's bottom edge (a slider for keyboard users), and `maxHeight` with it is the starting height. See "Field height".
|
|
1424
1635
|
- `surface` — `"theme"` (default) or `"paper"`: a light Paper in any theme, see "Surface". The type is `RichTextSurface`.
|
|
1425
1636
|
- `status` — a React node of the Status line inside the field's frame, at the start of the counter row; the kit does not announce it. The engine command `showNotice({ tone, text } | null)` is the Field notice. Both are in "Status line and Field notice"; they follow the kit's semver.
|
|
1426
|
-
- `tools` — kit names and descriptions of custom Tools (`CustomTool`, including their `buttons`, `insertItems`, `blockActions`, `textCards` and `trigger`), Presets as arrays — `RICH_TEXT_PRESETS`; see "Tools and Presets" and "Custom Tool". A description with an error is a field error.
|
|
1427
|
-
- `ref` (`RichTextEditorHandle`): `getHTML()` — the current HTML, without waiting for the delay and without `onChange` (in HTML mode it first applies the field's edit — then `onChange` is called if the Document changed; the same for `getMarkdown()`); `getMarkdown()` and `setMarkdown(md)` — see "Markdown"; `getJSON()` and `setJSON(document)` — see "JSON"; `clear()` — clear the Document, and `onChange("")` notifies the screen; `focus(position?)`, `blur()`, `insertHTML`, `insertText`, `getText`, `getSelectedText`, `isEmpty`, `undo`, `redo`, `canUndo`, `canRedo` — see "Handle (`ref`) and your own toolbar outside the field"; `openSearch({ replace? })` —
|
|
1637
|
+
- `tools` — kit names, Configured Tools `tool(name, options)` for `search` and `toc`, `defineTool(…)` definitions and descriptions of custom Tools (`CustomTool`, including their `buttons`, `insertItems`, `blockActions`, `textCards` and `trigger`), Presets as arrays — `RICH_TEXT_PRESETS`; see "Tools and Presets" and "Custom Tool". A description with an error is a field error.
|
|
1638
|
+
- `ref` (`RichTextEditorHandle`): `getHTML()` — the current HTML, without waiting for the delay and without `onChange` (in HTML mode it first applies the field's edit — then `onChange` is called if the Document changed; the same for `getMarkdown()`); `getMarkdown()` and `setMarkdown(md)` — see "Markdown"; `getJSON()` and `setJSON(document)` — see "JSON"; `clear()` — clear the Document, and `onChange("")` notifies the screen; `focus(position?)`, `blur()`, `insertHTML`, `insertText`, `getText`, `getSelectedText`, `isEmpty`, `undo`, `redo`, `canUndo`, `canRedo` — see "Handle (`ref`) and your own toolbar outside the field"; `tool(name)` — the commands of a Tool, see "Tool commands: `handle.tool()`"; `openSearch({ replace? })` — deprecated, removed in 5.0, replaced by `tool("search")?.open({ replace })`; `editor` — the underlying Tiptap `Editor`, its API **follows Tiptap's major version**, and before mount and on the server it is `null`. `focus()` without an argument puts the caret at the end of the Document (in HTML mode, in the HTML field).
|
|
1428
1639
|
- `useRichTextEditorState(ref, selector)` — a hook for your own toolbar outside the field, see "Handle (`ref`) and your own toolbar outside the field".
|
|
1640
|
+
- `useRichTextToolState(ref, name, selector?)` — the hook for Tool state (`search`, `toc` and custom Tools with `defineTool`): a data snapshot, `null` before mount, after unmount and without the Tool; see "Tool state: `useRichTextToolState`". It follows the kit's semver, as do `tool()`, `defineTool()`, `handle.tool()` and the `RichTextTools` map.
|
|
1429
1641
|
- SSR: the engine is created only in the browser (`immediatelyRender: false`). On the server it renders a Skeleton without a Document — the frame and the space reserved for the Toolbar — and a hidden field with the incoming value. After hydration the Skeleton shows the Document cleaned by the Schema, and the editable area takes its place after the Loaders, without a height jump. The exception is a JSON envelope in `defaultValue`: only the engine builds its HTML, so with it the Skeleton is a frame without a Document, the hidden field is empty until Readiness, and the height changes once when the Document appears.
|
|
1430
1642
|
|
|
1431
1643
|
## Markdown
|
|
@@ -1546,12 +1758,16 @@ There are no other classes, no `style` (except the author's color), no `aria-*`
|
|
|
1546
1758
|
- Changing `tools` on a mounted field expecting the Schema to be rebuilt: the set is read when the engine is created.
|
|
1547
1759
|
- Conveying the meaning of a note by the Wrapper tone alone: a screen reader cannot see the background, a color-blind person cannot tell the tones apart, and in Markdown without alert support only a quote remains. Write it in words — "Warning: …" (WCAG 1.4.1).
|
|
1548
1760
|
- Coloring text inside a Wrapper that has a tone with a palette color or a custom color: on a colored background its contrast may fall below 4.5:1 — the gate checks the Document's main and secondary text, not the author's color.
|
|
1761
|
+
- Changing `tool(…)` on a mounted field expecting new options: they are read when the engine is created, like `tools`. To change options, remount the field with a key.
|
|
1762
|
+
- Taking the number of Find matches or the headings of the Table of contents from `ref.current.editor`, from the Document's DOM or from internal plugin keys: that is not the kit's contract and it follows the Tiptap major. Use `useRichTextToolState` for this.
|
|
1763
|
+
- Calling `openSearch()` in new code: the method is deprecated and will disappear in 5.0. Use `tool("search")?.open()`.
|
|
1764
|
+
- Writing `tool("bold", …)` or options the Tool does not have in `tools`: the compiler stops it, and in JS without types the extra option is dropped with a `console.error`.
|
|
1549
1765
|
|
|
1550
1766
|
## Links
|
|
1551
1767
|
|
|
1552
|
-
- Source: `src/editor/RichTextEditor.tsx`; the Toolbar is `src/editor/ui/EditorToolbar.tsx`, the formatting menus ("¶", "…", "Alignment", "Lists") are `src/editor/ui/FormatMenus.tsx` and `src/editor/core/blockTypes.ts`, the floating one is `src/editor/ui/FloatingToolbar.tsx`, the Selection menu is `src/editor/ui/SelectionBubble.tsx`, the shared display condition and position are `src/editor/ui/selectionMenu.ts`; the link is `src/editor/features/link/link.ts`, the Tool description `src/editor/features/link/linkTool.tsx` and the URL form `src/editor/features/link/LinkPopover.tsx`; emoji are the Tool description `src/editor/features/emoji/emojiTool.tsx` and the grid `src/editor/features/emoji/EmojiGrid.tsx`; buttons from Tool descriptions and the frame of their popovers are `src/editor/ui/ToolButtonView.tsx`, their shortcuts are `src/editor/core/toolButtons.ts`, the shortcuts of their Insert items and Block actions are `src/editor/core/toolEntryShortcuts.ts`; the image is `src/editor/features/image/image.ts` (node, upload, file paste), `src/editor/features/image/ImagePopover.tsx`, `src/editor/features/image/ImageToolbar.tsx`; the Field notice is the command `src/editor/core/fieldNotice.ts`, the file texts `src/editor/features/image/imageNotice.ts`, the live region, the error line and the Status line `src/editor/ui/FieldStatus.tsx`; a custom Tool is the public form `src/editor/core/customTool.ts`, the engine and set validation `src/editor/core/customToolEngine.ts`, the `tools` expansion `src/editor/features/builtins.ts`; the table is `src/editor/features/table/tables.ts` (nodes, merging, alignment, Markdown), `src/editor/features/table/tableActions.ts` (menu commands, sorting, size memory), `src/editor/features/table/TableMenuButton.tsx` (the "Table" menu), `src/editor/features/table/TableSizeGrid.tsx` and `src/editor/features/table/TableInsertDialog.tsx`; the Layout is `src/editor/features/layout/layout.ts` (nodes, commands, keyboard) and `src/editor/features/layout/columnLayouts.ts` (variants as data); the Wrapper is `src/editor/features/wrapper/wrapper.ts` (node, insertion, unwrapping, tone, keyboard, Markdown) and `src/editor/features/wrapper/wrapperTones.ts` (tones and GitHub alerts labels as data); the Document with Block containers is `src/editor/core/document.ts`; the Handle is `src/editor/core/handle.ts`, the hook is `src/editor/hooks/useRichTextEditorState.ts`; HTML mode is the state and applying in `src/editor/RichTextEditor.tsx`, HTML formatting in `src/editor/core/sourceFormat.ts`; Tools and Presets are `src/editor/core/tools.ts`; Block commands are `src/editor/core/blockCommands.ts`; the Block handle is `src/editor/ui/BlockHandle.tsx`; the shortcut table and labels are `src/editor/core/shortcuts.ts`, the Shortcuts reference is `src/editor/ui/ShortcutsButton.tsx`; the Glass of popup surfaces is `src/editor/styles/glass.ts`; the Insert menu is `src/editor/core/insertItems.ts` (the item registry), `src/editor/ui/SlashMenu.tsx`, `src/editor/ui/InsertMenuButton.tsx`, `src/editor/core/slashCommand.ts`; the Schema as data is `src/editor/core/schema.ts`; the palette and alignments are `src/editor/styles/palette.ts`, the color extensions are `src/editor/features/color/styleExtensions.ts`; the author's color is the form and parsing `src/editor/features/color/customColor.ts`, contrast `src/editor/features/color/contrast.ts`, the popover `src/editor/features/color/CustomColorPopover.tsx`; typography is `src/editor/styles/prose.ts`; the code block is `src/editor/features/code/codeBlock.ts` (extension, aliases, `Alt+F10`), `src/editor/features/code/CodeBlockView.tsx` (the header: language and "Copy"), `src/editor/features/code/CodeCopyButton.tsx` (the button with a tooltip), `src/editor/features/code/codeHeader.ts` (header classes shared with the View), the Paper (`surface="paper"`) is `src/editor/styles/surface.ts` and `src/editor/styles/tokens.ts`, `src/editor/features/code/codeLanguages.ts` (languages as data), `src/editor/features/code/lowlight.ts` (grammars), `src/editor/styles/codeTheme.ts` (the syntax highlighting theme), the server helper is `src/editor/features/code/highlight.ts`; find and replace is `src/editor/features/search/searchTool.ts` (the Tool), `src/editor/features/search/searchPlugin.ts` and `src/editor/features/search/SearchPanel.tsx`; JSON is `src/editor/core/jsonDocument.ts`; Field height is the resize handle `src/editor/ui/FieldResizer.tsx`; emoji (the set's data) is `src/editor/features/emoji/emoji.ts`; the character limit is `src/editor/core/maxLength.ts`; Markdown is `src/editor/core/markdown.ts`.
|
|
1553
|
-
- Tests: `tests/rich-text-editor-text-card.test.tsx` (Text card), `tests/rich-text-editor.test.tsx`, `tests/rich-text-editor-handle.test.tsx` and `tests/rich-text-editor-handle-ssr.test.tsx` (Handle and hook), `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` (lazy code block and table: `basic` does not load them, `full` shows a Skeleton until the chunks load), `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` (a custom Tool; test Tools are in `tests/editor-test-tools.ts`), `tests/rich-text-editor-custom-ui.test.tsx` (Insert items and Block actions of a custom Tool), `tests/rich-text-editor-trigger.test.tsx` (a custom Trigger character: opening, picking, the query, asynchronous items, set errors), `tests/rich-text-editor-custom-button.test.tsx` (buttons of a custom Tool), `tests/rich-text-editor-custom-markdown.test.tsx` (Markdown of a custom Block by default and by its own rules), `tests/rich-text-editor-atom-text.test.tsx` (Atom text, weight in `maxLength`, emptiness), `tests/rich-text-editor-empty-table.test.tsx` (an empty table is content), `tests/rich-text-editor-max-height.test.tsx` (`maxHeight` and `resizable`: what is inside the scroll area, the position of the Block handle during scrolling), `tests/rich-text-editor-events.test.tsx` (`onReady`, `onFocus`, `onBlur`, paste and drop order), `tests/rich-text-editor-loaders.test.tsx` (Engine part Loaders, "Retry"), `tests/rich-text-editor-skeleton.test.tsx` (Skeleton), `tests/rich-text-editor-emoji.test.tsx` (emoji), `tests/rich-text-editor-json.test.tsx` (JSON), `tests/editor-search.test.tsx`, `tests/editor-search-panel.test.tsx` and `tests/editor-search-replace.test.tsx` (find and replace), `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`; in the browser — `tests-visual/interactions.spec.ts` ("RichTextEditor inside Dialog" — Escape in the style list, in the Insert menu, in the link popover and in the code block language menu; "RichTextEditor inside-dialog" and "inside-drawer" — the "…" menu and the color popovers with mouse and keyboard; "RichTextEditor insert menu"; "RichTextEditor block commands" — real `⌥⇧↑/↓`, `⌘]`, `⌘[` and `⌘⇧D`), `tests-visual/rich-text-editor-image.spec.ts` (file drag and width handles), `tests-visual/rich-text-editor-bubble.spec.ts` (the Selection menu in `Dialog`, `Drawer` and a scroll container, at the viewport edge, the inverse surface in three themes), `tests-visual/rich-text-editor-layout.spec.ts` (Layout in a `Dialog`: columns side by side and Tab across them, arrow keys, a stack on a phone), `tests-visual/rich-text-editor-wrapper.spec.ts` (Wrapper: a dashed outline in the Editor and no frame in `RichTextView`, the tone looks the same in both, exit with Enter, `↑` out of a Wrapper), `tests-visual/rich-text-editor-block-handle.spec.ts` (the handle menu in a `Dialog`: `Ctrl/⌘+/`, Escape by levels; the handle's position at the Block's first line), `tests-visual/rich-text-editor-trigger.spec.ts` (a custom Trigger character: the list at the caret, real typing of `@`, picking by mouse and Enter, `a@b` inside a word, axe in three themes), `tests-visual/rich-text-editor-source.spec.ts` (HTML mode: field height, growth with content, focus on entry and exit), `tests-visual/rich-text-editor-height.spec.ts` (Field height), `tests-visual/rich-text-editor-emoji.spec.ts` (emoji), `tests-visual/rich-text-editor-skeleton.spec.ts` (Skeleton).
|
|
1768
|
+
- Source: `src/editor/RichTextEditor.tsx`; the Toolbar is `src/editor/ui/EditorToolbar.tsx`, the formatting menus ("¶", "…", "Alignment", "Lists") are `src/editor/ui/FormatMenus.tsx` and `src/editor/core/blockTypes.ts`, the floating one is `src/editor/ui/FloatingToolbar.tsx`, the Selection menu is `src/editor/ui/SelectionBubble.tsx`, the shared display condition and position are `src/editor/ui/selectionMenu.ts`; the link is `src/editor/features/link/link.ts`, the Tool description `src/editor/features/link/linkTool.tsx` and the URL form `src/editor/features/link/LinkPopover.tsx`; emoji are the Tool description `src/editor/features/emoji/emojiTool.tsx` and the grid `src/editor/features/emoji/EmojiGrid.tsx`; buttons from Tool descriptions and the frame of their popovers are `src/editor/ui/ToolButtonView.tsx`, their shortcuts are `src/editor/core/toolButtons.ts`, the shortcuts of their Insert items and Block actions are `src/editor/core/toolEntryShortcuts.ts`; the image is `src/editor/features/image/image.ts` (node, upload, file paste), `src/editor/features/image/ImagePopover.tsx`, `src/editor/features/image/ImageToolbar.tsx`; the Field notice is the command `src/editor/core/fieldNotice.ts`, the file texts `src/editor/features/image/imageNotice.ts`, the live region, the error line and the Status line `src/editor/ui/FieldStatus.tsx`; a custom Tool is the public form `src/editor/core/customTool.ts`, the engine and set validation `src/editor/core/customToolEngine.ts`, the `tools` expansion and the options of built-in Tools `src/editor/features/builtins.ts`; `tool()` and the `RichTextTools` map `src/editor/core/toolOptions.ts`, the command types `src/editor/core/toolCommands.ts`, the state types `src/editor/core/toolState.ts`, the state hook `src/editor/hooks/useRichTextToolState.ts`, the Find and Table of contents descriptions `src/editor/features/search/searchTool.ts` and `src/editor/features/toc/tocTool.ts`; the table is `src/editor/features/table/tables.ts` (nodes, merging, alignment, Markdown), `src/editor/features/table/tableActions.ts` (menu commands, sorting, size memory), `src/editor/features/table/TableMenuButton.tsx` (the "Table" menu), `src/editor/features/table/TableSizeGrid.tsx` and `src/editor/features/table/TableInsertDialog.tsx`; the Layout is `src/editor/features/layout/layout.ts` (nodes, commands, keyboard) and `src/editor/features/layout/columnLayouts.ts` (variants as data); the Wrapper is `src/editor/features/wrapper/wrapper.ts` (node, insertion, unwrapping, tone, keyboard, Markdown) and `src/editor/features/wrapper/wrapperTones.ts` (tones and GitHub alerts labels as data); the Document with Block containers is `src/editor/core/document.ts`; the Handle is `src/editor/core/handle.ts`, the hook is `src/editor/hooks/useRichTextEditorState.ts`; HTML mode is the state and applying in `src/editor/RichTextEditor.tsx`, HTML formatting in `src/editor/core/sourceFormat.ts`; Tools and Presets are `src/editor/core/tools.ts`; Block commands are `src/editor/core/blockCommands.ts`; the Block handle is `src/editor/ui/BlockHandle.tsx`; the shortcut table and labels are `src/editor/core/shortcuts.ts`, the Shortcuts reference is `src/editor/ui/ShortcutsButton.tsx`; the Glass of popup surfaces is `src/editor/styles/glass.ts`; the Insert menu is `src/editor/core/insertItems.ts` (the item registry), `src/editor/ui/SlashMenu.tsx`, `src/editor/ui/InsertMenuButton.tsx`, `src/editor/core/slashCommand.ts`; the Schema as data is `src/editor/core/schema.ts`; the palette and alignments are `src/editor/styles/palette.ts`, the color extensions are `src/editor/features/color/styleExtensions.ts`; the author's color is the form and parsing `src/editor/features/color/customColor.ts`, contrast `src/editor/features/color/contrast.ts`, the popover `src/editor/features/color/CustomColorPopover.tsx`; typography is `src/editor/styles/prose.ts`; the code block is `src/editor/features/code/codeBlock.ts` (extension, aliases, `Alt+F10`), `src/editor/features/code/CodeBlockView.tsx` (the header: language and "Copy"), `src/editor/features/code/CodeCopyButton.tsx` (the button with a tooltip), `src/editor/features/code/codeHeader.ts` (header classes shared with the View), the Paper (`surface="paper"`) is `src/editor/styles/surface.ts` and `src/editor/styles/tokens.ts`, `src/editor/features/code/codeLanguages.ts` (languages as data), `src/editor/features/code/lowlight.ts` (grammars), `src/editor/styles/codeTheme.ts` (the syntax highlighting theme), the server helper is `src/editor/features/code/highlight.ts`; find and replace is `src/editor/features/search/searchTool.ts` (the Tool), `src/editor/features/search/searchPlugin.ts` and `src/editor/features/search/SearchPanel.tsx`; JSON is `src/editor/core/jsonDocument.ts`; Field height is the resize handle `src/editor/ui/FieldResizer.tsx`; emoji (the set's data) is `src/editor/features/emoji/emoji.ts`; the character limit is `src/editor/core/maxLength.ts`; Markdown is `src/editor/core/markdown.ts`.
|
|
1769
|
+
- Tests: `tests/rich-text-editor-text-card.test.tsx` (Text card), `tests/rich-text-editor.test.tsx`, `tests/rich-text-editor-handle.test.tsx` and `tests/rich-text-editor-handle-ssr.test.tsx` (Handle and hook), `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` (lazy code block and table: `basic` does not load them, `full` shows a Skeleton until the chunks load), `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` (a custom Tool; test Tools are in `tests/editor-test-tools.ts`), `tests/rich-text-editor-custom-ui.test.tsx` (Insert items and Block actions of a custom Tool), `tests/rich-text-editor-trigger.test.tsx` (a custom Trigger character: opening, picking, the query, asynchronous items, set errors), `tests/rich-text-editor-custom-button.test.tsx` (buttons of a custom Tool), `tests/rich-text-editor-custom-markdown.test.tsx` (Markdown of a custom Block by default and by its own rules), `tests/rich-text-editor-atom-text.test.tsx` (Atom text, weight in `maxLength`, emptiness), `tests/rich-text-editor-empty-table.test.tsx` (an empty table is content), `tests/rich-text-editor-max-height.test.tsx` (`maxHeight` and `resizable`: what is inside the scroll area, the position of the Block handle during scrolling), `tests/rich-text-editor-events.test.tsx` (`onReady`, `onFocus`, `onBlur`, paste and drop order), `tests/rich-text-editor-loaders.test.tsx` (Engine part Loaders, "Retry"), `tests/rich-text-editor-skeleton.test.tsx` (Skeleton), `tests/rich-text-editor-emoji.test.tsx` (emoji), `tests/rich-text-editor-json.test.tsx` (JSON), `tests/editor-search.test.tsx`, `tests/editor-search-panel.test.tsx` and `tests/editor-search-replace.test.tsx` (find and replace), `tests/rich-text-editor-tool-options.test.tsx` and `tests/editor-tool-options-types.test.ts` (`tool()` options), `tests/rich-text-editor-tool-state.test.tsx`, `tests/rich-text-editor-tool-state-ssr.test.tsx` and `tests/editor-tool-state-types.test.ts` (state), `tests/rich-text-editor-tool-commands.test.tsx`, `tests/editor-search-commands-reveal.test.tsx` and `tests/editor-tool-commands-types.test.ts` (`handle.tool()` commands), `tests/rich-text-editor-define-tool.test.tsx`, `tests/editor-define-tool-types.test.ts` and `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`; in the browser — `tests-visual/interactions.spec.ts` ("RichTextEditor inside Dialog" — Escape in the style list, in the Insert menu, in the link popover and in the code block language menu; "RichTextEditor inside-dialog" and "inside-drawer" — the "…" menu and the color popovers with mouse and keyboard; "RichTextEditor insert menu"; "RichTextEditor block commands" — real `⌥⇧↑/↓`, `⌘]`, `⌘[` and `⌘⇧D`), `tests-visual/rich-text-editor-image.spec.ts` (file drag and width handles), `tests-visual/rich-text-editor-bubble.spec.ts` (the Selection menu in `Dialog`, `Drawer` and a scroll container, at the viewport edge, the inverse surface in three themes), `tests-visual/rich-text-editor-layout.spec.ts` (Layout in a `Dialog`: columns side by side and Tab across them, arrow keys, a stack on a phone), `tests-visual/rich-text-editor-wrapper.spec.ts` (Wrapper: a dashed outline in the Editor and no frame in `RichTextView`, the tone looks the same in both, exit with Enter, `↑` out of a Wrapper), `tests-visual/rich-text-editor-block-handle.spec.ts` (the handle menu in a `Dialog`: `Ctrl/⌘+/`, Escape by levels; the handle's position at the Block's first line), `tests-visual/rich-text-editor-trigger.spec.ts` (a custom Trigger character: the list at the caret, real typing of `@`, picking by mouse and Enter, `a@b` inside a word, axe in three themes), `tests-visual/rich-text-editor-source.spec.ts` (HTML mode: field height, growth with content, focus on entry and exit), `tests-visual/rich-text-editor-height.spec.ts` (Field height), `tests-visual/rich-text-editor-emoji.spec.ts` (emoji), `tests-visual/rich-text-editor-skeleton.spec.ts` (Skeleton).
|
|
1554
1770
|
- The extension API at a glance, "Stability" and recipes — [Editor extension API](../../api/editor-extension.md).
|
|
1555
1771
|
- Viewing without editing — [RichTextView](../content/rich-text-view.md).
|
|
1556
|
-
- Storybook: `Inputs/RichTextEditor
|
|
1772
|
+
- Storybook: `Inputs/RichTextEditor` (the `SearchStatusLine`, `TableOfContentsOptions`, `TableOfContentsExternalNavigation`, `ToolCommands`, `DefinedTool` and `DefinedToolConfigured` stories).
|
|
1557
1773
|
- Code origin: minimal-tiptap (MIT); syntax highlighting theme colors — highlight.js (BSD-3-Clause); licenses are in the package `NOTICE`.
|