@olddevs/ui 4.5.0 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -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/toc/RichTextToc.d.ts.map +1 -1
- package/dist/editor/features/toc/RichTextToc.js +9 -2
- package/dist/editor/features/toc/RichTextToc.js.map +1 -1
- package/dist/editor/features/wrapper/wrapperTool.d.ts +11 -0
- package/dist/editor/features/wrapper/wrapperTool.d.ts.map +1 -0
- package/dist/editor/features/wrapper/wrapperTool.js +32 -0
- package/dist/editor/features/wrapper/wrapperTool.js.map +1 -0
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.d.ts.map +1 -1
- package/dist/editor/index.js.map +1 -1
- package/dist/editor/schema-entry.d.ts +1 -1
- package/dist/editor/styles/prose.d.ts.map +1 -1
- package/dist/editor/styles/prose.js +26 -0
- package/dist/editor/styles/prose.js.map +1 -1
- package/dist/editor/ui/EditorToolbar.d.ts.map +1 -1
- package/dist/editor/ui/EditorToolbar.js +5 -38
- package/dist/editor/ui/EditorToolbar.js.map +1 -1
- package/dist/editor/ui/SelectionBubble.d.ts.map +1 -1
- package/dist/editor/ui/SelectionBubble.js +1 -1
- package/dist/editor/ui/SelectionBubble.js.map +1 -1
- package/dist/editor/ui/ToolButtonView.d.ts.map +1 -1
- package/dist/editor/ui/ToolButtonView.js +1 -1
- package/dist/editor/ui/ToolButtonView.js.map +1 -1
- package/dist/i18n/defaults.d.ts.map +1 -1
- package/dist/i18n/defaults.js +3 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/messages.d.ts +16 -1
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/icons.d.ts +8 -1
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +1 -1
- package/dist/icons.js.map +1 -1
- package/dist/locales/ru.d.ts.map +1 -1
- package/dist/locales/ru.js +3 -0
- package/dist/locales/ru.js.map +1 -1
- package/docs/api/editor-extension.en.md +56 -56
- package/docs/api/editor-extension.md +55 -55
- package/docs/api/messages.en.md +4 -1
- package/docs/api/messages.md +3 -0
- package/docs/api/migration-notes.en.md +3 -3
- package/docs/api/migration-notes.md +2 -2
- package/docs/components/content/rich-text-view.en.md +18 -12
- package/docs/components/content/rich-text-view.md +17 -11
- package/docs/components/inputs/rich-text-editor.en.md +140 -87
- package/docs/components/inputs/rich-text-editor.md +139 -86
- package/docs/conventions.en.md +2 -2
- package/docs/conventions.md +1 -1
- package/docs/getting-started.en.md +9 -9
- package/docs/getting-started.md +8 -8
- package/docs/glossary.en.md +80 -52
- package/docs/glossary.md +79 -51
- package/docs/icons.en.md +4 -1
- package/docs/icons.md +3 -0
- package/package.json +2 -1
- package/src/editor/RichTextEditor.tsx +9 -6
- package/src/editor/core/atomText.ts +2 -2
- package/src/editor/core/blockTypes.ts +19 -83
- package/src/editor/core/containers.ts +4 -2
- package/src/editor/core/customMarkdown.ts +2 -2
- package/src/editor/core/customTool.ts +6 -6
- package/src/editor/core/customToolEngine.ts +9 -9
- package/src/editor/core/extensions.ts +165 -110
- package/src/editor/core/fieldNotice.ts +1 -1
- package/src/editor/core/handle.ts +2 -2
- package/src/editor/core/insertItems.ts +12 -158
- package/src/editor/core/insertMenuTool.ts +32 -0
- package/src/editor/core/jsonDocument.ts +1 -1
- package/src/editor/core/schema.ts +42 -7
- package/src/editor/core/shortcuts.ts +116 -177
- package/src/editor/core/skeleton.ts +4 -3
- package/src/editor/core/toolButtons.ts +3 -3
- package/src/editor/core/toolDefinition.ts +4 -4
- package/src/editor/core/toolDescriptor.ts +65 -52
- package/src/editor/core/toolLoader.ts +3 -3
- package/src/editor/core/toolOptions.ts +21 -2
- package/src/editor/core/tools.ts +10 -1
- package/src/editor/core/viewIsland.ts +1 -1
- package/src/editor/features/builtins.ts +76 -41
- package/src/editor/features/code/codeBlockTool.ts +16 -8
- package/src/editor/features/color/colorTools.ts +68 -0
- package/src/editor/features/details/details.ts +389 -0
- package/src/editor/features/details/detailsMarkdown.ts +158 -0
- package/src/editor/features/details/detailsTool.ts +69 -0
- package/src/editor/features/emoji/emojiTool.tsx +1 -1
- package/src/editor/features/heading/headingTool.ts +96 -0
- package/src/editor/features/history/historyTool.ts +42 -0
- package/src/editor/features/image/imageTool.ts +49 -0
- package/src/editor/features/layout/layoutTool.ts +40 -0
- package/src/editor/features/lists/listTools.ts +112 -0
- package/src/editor/features/marks/markTools.ts +78 -0
- package/src/editor/features/search/searchTool.ts +1 -1
- package/src/editor/features/table/tableTool.ts +3 -3
- package/src/editor/features/toc/RichTextToc.tsx +14 -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
|
@@ -1,20 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import type { ChainedCommands } from "@tiptap/core";
|
|
4
3
|
import type { Node as PmNode } from "@tiptap/pm/model";
|
|
5
4
|
import type { EditorState } from "@tiptap/pm/state";
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
Heading1Icon,
|
|
9
|
-
Heading2Icon,
|
|
10
|
-
Heading3Icon,
|
|
11
|
-
Heading4Icon,
|
|
12
|
-
ListIcon,
|
|
13
|
-
OrderedListIcon,
|
|
14
|
-
TaskListIcon,
|
|
15
|
-
TextIcon,
|
|
16
|
-
type IconProps,
|
|
17
|
-
} from "../../icons.js";
|
|
5
|
+
import { TextIcon } from "../../icons.js";
|
|
18
6
|
import type { PrefixedName } from "./customTool.js";
|
|
19
7
|
import type { InsertItem } from "./insertItems.js";
|
|
20
8
|
import { shortcutKeys } from "./shortcuts.js";
|
|
@@ -30,8 +18,8 @@ import type { Tool } from "./tools.js";
|
|
|
30
18
|
* заново — одной транзакцией, на одну отмену. Своего пути смены типа у меню
|
|
31
19
|
* нет: два пути разошлись бы на первом же списке внутри цитаты.
|
|
32
20
|
*
|
|
33
|
-
*
|
|
34
|
-
* другие слова и иконки: «Текст» с иконкой `T`, а не
|
|
21
|
+
* Пункты «¶» — поле описания `blockTypes`, а не выборка из `insertItems`,
|
|
22
|
+
* потому что у меню «¶» могут быть другие слова и иконки: «Текст» с иконкой `T`, а не
|
|
35
23
|
* «Абзац» с пилкроу, и уровень заголовка на иконке. Блока кода в нём нет — у
|
|
36
24
|
* него своя кнопка Панели.
|
|
37
25
|
*/
|
|
@@ -61,70 +49,24 @@ export function turnIntoTypes(
|
|
|
61
49
|
.map((item) => ({ ...item, section: "turnInto", tool, keys: item.keys ?? [] }));
|
|
62
50
|
}
|
|
63
51
|
|
|
64
|
-
const heading = (level: 1 | 2 | 3 | 4, icon: ComponentType<IconProps>): BlockTypeItem => ({
|
|
65
|
-
id: `heading${level}`,
|
|
66
|
-
section: "turnInto",
|
|
67
|
-
tool: "heading",
|
|
68
|
-
alsoTool: level === 1 ? "heading1" : undefined,
|
|
69
|
-
label: (m) => m.heading({ level }),
|
|
70
|
-
icon,
|
|
71
|
-
keys: shortcutKeys(`h${level}`),
|
|
72
|
-
run: (chain: ChainedCommands) => chain.setHeading({ level }),
|
|
73
|
-
});
|
|
74
|
-
|
|
75
52
|
/**
|
|
76
|
-
*
|
|
77
|
-
* (`shortcuts.ts`)
|
|
78
|
-
* вешают их расширения движка, и подпись обязана совпадать с ними.
|
|
53
|
+
* «Текст» — пункт кита, а не Инструмента: абзац есть в любом наборе. Сочетание —
|
|
54
|
+
* подпись из общей таблицы (`shortcuts.ts`): вешает его расширение движка.
|
|
79
55
|
*/
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
},
|
|
90
|
-
heading(1, Heading1Icon),
|
|
91
|
-
heading(2, Heading2Icon),
|
|
92
|
-
heading(3, Heading3Icon),
|
|
93
|
-
heading(4, Heading4Icon),
|
|
94
|
-
{
|
|
95
|
-
id: "bulletList",
|
|
96
|
-
section: "turnInto",
|
|
97
|
-
tool: "bulletList",
|
|
98
|
-
label: (m) => m.bulletList,
|
|
99
|
-
icon: ListIcon,
|
|
100
|
-
keys: shortcutKeys("bulletList"),
|
|
101
|
-
run: (chain) => chain.toggleBulletList(),
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
id: "orderedList",
|
|
105
|
-
section: "turnInto",
|
|
106
|
-
tool: "orderedList",
|
|
107
|
-
label: (m) => m.orderedList,
|
|
108
|
-
icon: OrderedListIcon,
|
|
109
|
-
keys: shortcutKeys("orderedList"),
|
|
110
|
-
run: (chain) => chain.toggleOrderedList(),
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
id: "taskList",
|
|
114
|
-
section: "turnInto",
|
|
115
|
-
tool: "taskList",
|
|
116
|
-
label: (m) => m.taskList,
|
|
117
|
-
icon: TaskListIcon,
|
|
118
|
-
keys: shortcutKeys("taskList"),
|
|
119
|
-
run: (chain) => chain.toggleTaskList(),
|
|
120
|
-
},
|
|
121
|
-
];
|
|
56
|
+
const TEXT_ITEM: BlockTypeItem = {
|
|
57
|
+
id: "text",
|
|
58
|
+
section: "turnInto",
|
|
59
|
+
tool: null,
|
|
60
|
+
label: (m) => m.textBlock,
|
|
61
|
+
icon: TextIcon,
|
|
62
|
+
keys: shortcutKeys("paragraph"),
|
|
63
|
+
run: (chain) => chain.setParagraph(),
|
|
64
|
+
};
|
|
122
65
|
|
|
123
66
|
/**
|
|
124
67
|
* Место встроенных Инструментов в «¶». «Текст» стоит первым всегда: абзац —
|
|
125
|
-
* не Инструмент.
|
|
126
|
-
*
|
|
127
|
-
* остаётся здесь.
|
|
68
|
+
* не Инструмент. Пункты живут в описаниях, а место на поверхности — привилегия
|
|
69
|
+
* кита.
|
|
128
70
|
*/
|
|
129
71
|
const BLOCK_TYPE_ORDER: readonly Tool[] = [
|
|
130
72
|
"heading",
|
|
@@ -135,18 +77,12 @@ const BLOCK_TYPE_ORDER: readonly Tool[] = [
|
|
|
135
77
|
];
|
|
136
78
|
|
|
137
79
|
/**
|
|
138
|
-
* Пункты «¶»
|
|
139
|
-
* из описания (`blockTypes`), у остальных — из `BLOCK_TYPE_ITEMS`.
|
|
80
|
+
* Пункты «¶» набора: «Текст» и проекция списка описаний (`blockTypes`).
|
|
140
81
|
*/
|
|
141
82
|
function blockTypeItemsOf(tools: ToolList): readonly BlockTypeItem[] {
|
|
142
83
|
return [
|
|
143
|
-
|
|
144
|
-
...projectRecords(
|
|
145
|
-
tools,
|
|
146
|
-
BLOCK_TYPE_ORDER,
|
|
147
|
-
(tool) => BLOCK_TYPE_ITEMS.filter((item) => item.tool === tool),
|
|
148
|
-
(descriptor) => descriptor.blockTypes
|
|
149
|
-
),
|
|
84
|
+
TEXT_ITEM,
|
|
85
|
+
...projectRecords(tools, BLOCK_TYPE_ORDER, (descriptor) => descriptor.blockTypes),
|
|
150
86
|
].filter((item) => item.alsoTool === undefined || tools.has(item.alsoTool));
|
|
151
87
|
}
|
|
152
88
|
|
|
@@ -24,8 +24,9 @@ export { WRAPPER_NODE };
|
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Узлы, внутри которых контейнерного Блока нет ни одного: Обёртка, цитата,
|
|
27
|
-
* пункты
|
|
28
|
-
*
|
|
27
|
+
* пункты списков, ячейки таблицы и содержимое сворачиваемого Блока
|
|
28
|
+
* (`details.ts`). Колонки здесь нет — Обёртку она принимает; Раскладка
|
|
29
|
+
* добавляет её своим аргументом.
|
|
29
30
|
*/
|
|
30
31
|
const CONTAINER_FREE_NODES = [
|
|
31
32
|
WRAPPER_NODE,
|
|
@@ -34,6 +35,7 @@ const CONTAINER_FREE_NODES = [
|
|
|
34
35
|
"taskItem",
|
|
35
36
|
"tableCell",
|
|
36
37
|
"tableHeader",
|
|
38
|
+
"detailsContent",
|
|
37
39
|
];
|
|
38
40
|
|
|
39
41
|
/**
|
|
@@ -12,7 +12,7 @@ import { devWarn } from "../../lib/dev-warn.js";
|
|
|
12
12
|
/*
|
|
13
13
|
Markdown своего Инструмента по умолчанию (ADR-0058, п. 3.1).
|
|
14
14
|
|
|
15
|
-
Свои правила Markdown — в
|
|
15
|
+
Свои правила Markdown — в Extensions: `renderMarkdown` и токенайзер
|
|
16
16
|
Tiptap, отдельного поля в описании нет. Без них `@tiptap/markdown` молча
|
|
17
17
|
выгрузил бы узел пустой строкой — вместе со всем текстом внутри. Поэтому
|
|
18
18
|
Node и Mark без `renderMarkdown` кит дописывает сам: Блок выгружается своим
|
|
@@ -31,7 +31,7 @@ export const MARKDOWN_FALLBACK = "kitMarkdownFallback";
|
|
|
31
31
|
/** Предупреждение о Markdown по умолчанию — одно на Инструмент (`devWarn` помнит текст). */
|
|
32
32
|
export function warnNoMarkdown(tool: string): void {
|
|
33
33
|
devWarn(
|
|
34
|
-
`custom tool "${tool}" has no Markdown rules (renderMarkdown in its
|
|
34
|
+
`custom tool "${tool}" has no Markdown rules (renderMarkdown in its Extensions): ` +
|
|
35
35
|
"getMarkdown() writes its blocks as their content and its atoms as their text"
|
|
36
36
|
);
|
|
37
37
|
}
|
|
@@ -67,7 +67,7 @@ export interface CustomBlock {
|
|
|
67
67
|
/**
|
|
68
68
|
* Остров Блока в `RichTextView` — клиентский компонент из модуля с
|
|
69
69
|
* директивой `"use client"` (ADR-0058, п. 3.3). Без него Блок в Просмотре —
|
|
70
|
-
* очищенный HTML. Редактор поле не читает: его вид
|
|
70
|
+
* очищенный HTML. Редактор поле не читает: его вид задают Extensions.
|
|
71
71
|
*
|
|
72
72
|
* Только компонент из модуля с директивой: серверный компонент и функция-
|
|
73
73
|
* загрузчик через границу RSC не проходят — сервер получает ссылку на
|
|
@@ -103,7 +103,7 @@ export interface CustomMark {
|
|
|
103
103
|
* встроенных (`ToolDescriptor`, ADR-0058, п. 10). Передаётся в `tools`
|
|
104
104
|
* вперемешку с именами Инструментов кита.
|
|
105
105
|
*
|
|
106
|
-
* Обязательное — имя,
|
|
106
|
+
* Обязательное — имя, Extensions и правила Схемы для каждого Блока и
|
|
107
107
|
* Метки, которые она определяет. Остальное — по умолчанию (ADR-0058, п. 1.6):
|
|
108
108
|
* обычный Блок без Признаков, без UI-записей.
|
|
109
109
|
*/
|
|
@@ -111,8 +111,8 @@ export interface CustomTool {
|
|
|
111
111
|
/** Имя с Префиксом потребителя, уникальное в `tools`. */
|
|
112
112
|
name: PrefixedName;
|
|
113
113
|
/**
|
|
114
|
-
*
|
|
115
|
-
* ProseMirror, React-вид узла, `renderText` атома.
|
|
114
|
+
* Extensions — расширения Tiptap: Node, Mark, Extension, плагины
|
|
115
|
+
* ProseMirror, React-вид узла, `renderText` атома. Их контракт следует
|
|
116
116
|
* мажору Tiptap. Node и Mark стоят на верхнем уровне массива: группу,
|
|
117
117
|
* разбор HTML и свои атрибуты им ставит кит по правилам.
|
|
118
118
|
*
|
|
@@ -123,9 +123,9 @@ export interface CustomTool {
|
|
|
123
123
|
* того поле показывает Скелет; промис кэшируется на функции загрузчика.
|
|
124
124
|
*/
|
|
125
125
|
engine: Extensions | (() => Promise<Extensions>);
|
|
126
|
-
/** Правила Схемы для каждого Node
|
|
126
|
+
/** Правила Схемы для каждого Node из Extensions — по имени узла. */
|
|
127
127
|
blocks?: Readonly<Record<PrefixedName, CustomBlock>>;
|
|
128
|
-
/** Правила Схемы для каждой Mark
|
|
128
|
+
/** Правила Схемы для каждой Mark из Extensions — по имени Метки. */
|
|
129
129
|
marks?: Readonly<Record<PrefixedName, CustomMark>>;
|
|
130
130
|
|
|
131
131
|
/* UI-записи (ADR-0058, п. 5 и 6): кнопки, пункты вставки и Действия над Блоком. */
|
|
@@ -43,13 +43,13 @@ import { loaded } from "./toolLoader.js";
|
|
|
43
43
|
Свой Инструмент в движке (ADR-0058, разделы 1 и 2): из
|
|
44
44
|
публичного описания — внутреннее, и проверка набора при сборке.
|
|
45
45
|
|
|
46
|
-
ПРАВИЛА СХЕМЫ — ЕДИНСТВЕННЫЙ ИСТОЧНИК HTML. Node и Mark
|
|
46
|
+
ПРАВИЛА СХЕМЫ — ЕДИНСТВЕННЫЙ ИСТОЧНИК HTML. Node и Mark из Extensions кит
|
|
47
47
|
дописывает сам: группу и строчность — по виду Блока, разбор HTML — только
|
|
48
48
|
`div|span[data-block="<имя>"]` и `span[data-mark="<имя>"]`, свои атрибуты —
|
|
49
49
|
`data-<имя>` со значением по виду из правил, Признаки Блока — в spec узла.
|
|
50
50
|
Своё правило разбора чужого Блока поэтому не займёт тег кита, а значение вне
|
|
51
51
|
правил не войдёт в Документ ни из `value`, ни из вставки: проверяет код кита,
|
|
52
|
-
и ослабить проверку
|
|
52
|
+
и ослабить проверку через Extensions нельзя. Поведение — содержимое,
|
|
53
53
|
`atom`, React-вид, `renderText`, команды — остаётся за потребителем.
|
|
54
54
|
*/
|
|
55
55
|
|
|
@@ -67,7 +67,7 @@ function allowed(rule: CustomAttribute, value: unknown): string | null {
|
|
|
67
67
|
return isAllowedAttributeValue(rule, text) ? text : null;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
/** Node или Mark
|
|
70
|
+
/** Node или Mark из Extensions, дописанный по своим правилам Схемы. */
|
|
71
71
|
function bindRules(extension: AnyExtension, tool: CustomTool): AnyExtension {
|
|
72
72
|
const { name } = extension;
|
|
73
73
|
const block = extension.type === "node" ? blocksOf(tool)[name] : undefined;
|
|
@@ -80,7 +80,7 @@ function bindRules(extension: AnyExtension, tool: CustomTool): AnyExtension {
|
|
|
80
80
|
const config = {
|
|
81
81
|
...markdownFallback(extension, tool.name),
|
|
82
82
|
parseHTML: () => [{ tag: selector }],
|
|
83
|
-
// Свой `renderHTML` остаётся у
|
|
83
|
+
// Свой `renderHTML` остаётся у Extensions (текст внутри атома);
|
|
84
84
|
// соглашение с ним сверяет `customToolsError`.
|
|
85
85
|
...(getExtensionField(extension, "renderHTML") === undefined && {
|
|
86
86
|
renderHTML: ({ node, HTMLAttributes }: { node?: PmNode; HTMLAttributes: object }) =>
|
|
@@ -211,7 +211,7 @@ function uiRecords(tool: CustomTool) {
|
|
|
211
211
|
return { insertItems, blockTypes, blockActions, textCards };
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
/**
|
|
214
|
+
/** Extensions: массив сразу или то, что отдал загрузчик; не массив — пустой массив. */
|
|
215
215
|
function engineOf(tool: CustomTool): Extensions {
|
|
216
216
|
const engine = typeof tool.engine === "function" ? loaded(tool.engine) : tool.engine;
|
|
217
217
|
return Array.isArray(engine) ? engine : [];
|
|
@@ -222,7 +222,7 @@ export function describeCustomTool(tool: CustomTool): ToolDescriptor {
|
|
|
222
222
|
return {
|
|
223
223
|
name: tool.name,
|
|
224
224
|
custom: tool,
|
|
225
|
-
// Загрузчик: правила Схемы ложатся на
|
|
225
|
+
// Загрузчик: правила Схемы ложатся на Extensions, когда они пришли (`engineOf`).
|
|
226
226
|
load: typeof tool.engine === "function" ? tool.engine : undefined,
|
|
227
227
|
engine: () => engineOf(tool).map((extension) => bindRules(extension, tool)),
|
|
228
228
|
...uiRecords(tool),
|
|
@@ -330,8 +330,8 @@ export interface CustomToolsError {
|
|
|
330
330
|
/**
|
|
331
331
|
* Ошибка сборки набора Инструментов или `null` (ADR-0058, п. 1.5, 2.4).
|
|
332
332
|
*
|
|
333
|
-
* Имя без Префикса потребителя, совпадение имён, Node или Mark
|
|
334
|
-
*
|
|
333
|
+
* Имя без Префикса потребителя, совпадение имён, Node или Mark из
|
|
334
|
+
* Extensions без правил Схемы (и правило без них), атрибут, которого нет в
|
|
335
335
|
* правилах, HTML мимо соглашения, Схема, которую движок не собрал, — всё это
|
|
336
336
|
* потеря данных на сервере или в самом Редакторе, и автор должен увидеть её
|
|
337
337
|
* до первого сохранения. `extensions` — полный набор движка, свои уже в нём.
|
|
@@ -442,7 +442,7 @@ export function customToolsError(tools: ToolList, extensions: Extensions): Custo
|
|
|
442
442
|
if (extra) return fail(`has attribute "${extra}" with no rule`);
|
|
443
443
|
/*
|
|
444
444
|
HTML — по соглашению: `toDOM` экземпляра по умолчанию даёт тег и
|
|
445
|
-
метку с именем. Свой `renderHTML`
|
|
445
|
+
метку с именем. Свой `renderHTML` в Extensions мог уйти от них, и
|
|
446
446
|
тогда Блок не прочитался бы обратно из собственного `getHTML()`.
|
|
447
447
|
*/
|
|
448
448
|
const { tag, marker } = customElement(
|
|
@@ -7,35 +7,25 @@
|
|
|
7
7
|
|
|
8
8
|
import { Extension, type Extensions } from "@tiptap/core";
|
|
9
9
|
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
10
|
-
import { TaskItem, TaskList } from "@tiptap/extension-list";
|
|
11
|
-
import { Subscript } from "@tiptap/extension-subscript";
|
|
12
|
-
import { Superscript } from "@tiptap/extension-superscript";
|
|
13
10
|
import { Placeholder } from "@tiptap/extensions";
|
|
14
|
-
import { StarterKit } from "@tiptap/starter-kit";
|
|
11
|
+
import { StarterKit, type StarterKitOptions } from "@tiptap/starter-kit";
|
|
15
12
|
import { AtomText } from "./atomText.js";
|
|
16
13
|
import { BlockCommands, BlockMenuKey } from "./blockCommands.js";
|
|
17
14
|
import { FieldNoticeExtension, type FieldNoticeOptions } from "./fieldNotice.js";
|
|
18
|
-
import {
|
|
15
|
+
import type { RichImageOptions } from "../features/image/image.js";
|
|
19
16
|
import { createDocumentNode } from "./document.js";
|
|
20
|
-
import { LAYOUT_NODE, layoutExtensions } from "../features/layout/layout.js";
|
|
21
17
|
import { createMarkdown, MarkdownPaste } from "./markdown.js";
|
|
22
18
|
import { MaxLength } from "./maxLength.js";
|
|
19
|
+
import { TriggerCommand, type SlashBridge, type TriggerBinding } from "./slashCommand.js";
|
|
23
20
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
SlashCommand,
|
|
30
|
-
TriggerCommand,
|
|
31
|
-
type SlashBridge,
|
|
32
|
-
type TriggerBinding,
|
|
33
|
-
} from "./slashCommand.js";
|
|
34
|
-
import { describedTools, type ToolList } from "./toolDescriptor.js";
|
|
21
|
+
type BlockTraits,
|
|
22
|
+
type ToolDescriptor,
|
|
23
|
+
type ToolEngineContext,
|
|
24
|
+
type ToolList,
|
|
25
|
+
} from "./toolDescriptor.js";
|
|
35
26
|
import { ToolButtons, type ToolPopoverEntry, type ToolPopoverRequest } from "./toolButtons.js";
|
|
36
27
|
import { ToolEntryShortcuts } from "./toolEntryShortcuts.js";
|
|
37
28
|
import type { Tool } from "./tools.js";
|
|
38
|
-
import { WRAPPER_EXTENSIONS, WRAPPER_NODE } from "../features/wrapper/wrapper.js";
|
|
39
29
|
|
|
40
30
|
export interface EditorExtensionOptions {
|
|
41
31
|
/** Включённые инструменты: выключенный не попадает в Схему вовсе. */
|
|
@@ -46,6 +36,11 @@ export interface EditorExtensionOptions {
|
|
|
46
36
|
limit: () => number | undefined;
|
|
47
37
|
/** Имя флажка пункта чек-листа — из каталога, читается при каждой перерисовке пункта. */
|
|
48
38
|
taskCheckbox: (text: string) => string;
|
|
39
|
+
/**
|
|
40
|
+
* Имя кнопки раскрытия сворачиваемого Блока по тексту его заголовка — из
|
|
41
|
+
* каталога, читается при каждой перерисовке кнопки.
|
|
42
|
+
*/
|
|
43
|
+
detailsToggle: (text: string) => string;
|
|
49
44
|
/**
|
|
50
45
|
* Мост Меню вставки по `/`. Нет — меню не открывается: инструмент
|
|
51
46
|
* `insertMenu` выключен или пунктов для него нет.
|
|
@@ -123,6 +118,115 @@ const HandledTabStaysInside = Extension.create({
|
|
|
123
118
|
],
|
|
124
119
|
});
|
|
125
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Признаки Блоков описаний (`blocks`) — в spec узлов Схемы: `blockContainer`,
|
|
123
|
+
* `turnInto` и `plainAtom`, которые читают ручка и Меню выделения. Расширение
|
|
124
|
+
* без разметки: `extendNodeSchema` у Tiptap зовётся для каждого узла набора, и
|
|
125
|
+
* признак находит свой узел по имени. Нет признаков — расширения нет.
|
|
126
|
+
*/
|
|
127
|
+
function blockTraitsExtension(traits: Readonly<Record<string, BlockTraits>>) {
|
|
128
|
+
return Extension.create({
|
|
129
|
+
name: "blockTraits",
|
|
130
|
+
extendNodeSchema: (extension) => {
|
|
131
|
+
const own = Object.hasOwn(traits, extension.name) ? traits[extension.name] : undefined;
|
|
132
|
+
return own
|
|
133
|
+
? {
|
|
134
|
+
blockContainer: own.container === true,
|
|
135
|
+
turnInto: own.turnInto === true,
|
|
136
|
+
plainAtom: own.plainAtom === true,
|
|
137
|
+
}
|
|
138
|
+
: {};
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Ключи StarterKit, которые принадлежат Инструментам. Все выключены, пока их не
|
|
145
|
+
* включит доля описания (`starterKit`): Метка, которой нет в наборе, не
|
|
146
|
+
* попадает в Схему. Цитата, разделитель, блок кода и
|
|
147
|
+
* ссылка — свои расширения кита, а `trailingNode` выключен всегда (см. выше).
|
|
148
|
+
* Документ, абзац, текст, перенос, пункт списка, его клавиши и курсоры
|
|
149
|
+
* StarterKit остаются его умолчаниями: они есть в любом наборе.
|
|
150
|
+
*/
|
|
151
|
+
const STARTER_KIT_OFF: Partial<StarterKitOptions> = {
|
|
152
|
+
heading: false,
|
|
153
|
+
bold: false,
|
|
154
|
+
italic: false,
|
|
155
|
+
underline: false,
|
|
156
|
+
strike: false,
|
|
157
|
+
code: false,
|
|
158
|
+
bulletList: false,
|
|
159
|
+
orderedList: false,
|
|
160
|
+
undoRedo: false,
|
|
161
|
+
blockquote: false,
|
|
162
|
+
codeBlock: false,
|
|
163
|
+
horizontalRule: false,
|
|
164
|
+
link: false,
|
|
165
|
+
trailingNode: false,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/** Место символов-триггеров в `ENGINE_ORDER`: они не Инструмент, но порядок держат. */
|
|
169
|
+
const TRIGGERS = "triggers";
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* ПОРЯДОК РЕГИСТРАЦИИ Extensions встроенных Инструментов — привилегия кита, как
|
|
173
|
+
* порядок поверхностей, и описание его не задаёт. От него зависят:
|
|
174
|
+
*
|
|
175
|
+
* - **ранг Меток** — а с ним вложенность Меток в сохранённом HTML;
|
|
176
|
+
* - **вставка и drop** (ADR-0058, п. 9.5): плагины ProseMirror у Tiptap идут в
|
|
177
|
+
* обратном порядке регистрации при равном `priority`, и зарегистрированное
|
|
178
|
+
* последним опрашивается первым. Свои Инструменты стоят после встроенных
|
|
179
|
+
* (`expandTools`), поэтому `transformPastedHTML`, `transformPasted`,
|
|
180
|
+
* `handlePaste` и `handleDrop` своего Инструмента видят событие раньше и могут
|
|
181
|
+
* забрать его. Давать встроенному плагину `priority` выше 100 нельзя без правки
|
|
182
|
+
* этого правила;
|
|
183
|
+
* - **клавиши**: `Enter`, `Backspace` и стрелки сворачиваемого Блока (`details`)
|
|
184
|
+
* срабатывают только в его заголовке и в пустом конце содержимого, поэтому
|
|
185
|
+
* с клавишами других Блоков не спорят. Он стоит сразу за контейнерами
|
|
186
|
+
* (Раскладка, Обёртка): позже ядра и StarterKit, значит, опрашивается раньше
|
|
187
|
+
* них — `Enter` в заголовке уходит в содержимое, а не режет заголовок надвое;
|
|
188
|
+
* - **клавиши**: `Alt+F10` блока кода (`codeBlockTool.ts`) зарегистрирован после
|
|
189
|
+
* `ToolbarFocus` и опрашивается раньше; Tab в таблице — раньше Раскладки.
|
|
190
|
+
*
|
|
191
|
+
* Список сохраняет порядок, который был до переезда встроенных на описания, —
|
|
192
|
+
* эталонные тесты (`rich-text-editor-golden.test.tsx`) держат сохранённый HTML и клавиши. Метки и
|
|
193
|
+
* Блоки из StarterKit регистрирует он сам, в своём порядке.
|
|
194
|
+
*/
|
|
195
|
+
const ENGINE_ORDER: readonly (Tool | typeof TRIGGERS)[] = [
|
|
196
|
+
"superscript",
|
|
197
|
+
"subscript",
|
|
198
|
+
"color",
|
|
199
|
+
"highlight",
|
|
200
|
+
"align",
|
|
201
|
+
"taskList",
|
|
202
|
+
"image",
|
|
203
|
+
"layout",
|
|
204
|
+
"wrapper",
|
|
205
|
+
"details",
|
|
206
|
+
"insertMenu",
|
|
207
|
+
TRIGGERS,
|
|
208
|
+
"history",
|
|
209
|
+
"heading",
|
|
210
|
+
"heading1",
|
|
211
|
+
"bold",
|
|
212
|
+
"italic",
|
|
213
|
+
"underline",
|
|
214
|
+
"strike",
|
|
215
|
+
"code",
|
|
216
|
+
"clearFormatting",
|
|
217
|
+
"link",
|
|
218
|
+
"bulletList",
|
|
219
|
+
"orderedList",
|
|
220
|
+
"blockquote",
|
|
221
|
+
"codeBlock",
|
|
222
|
+
"horizontalRule",
|
|
223
|
+
"table",
|
|
224
|
+
"emoji",
|
|
225
|
+
"source",
|
|
226
|
+
"search",
|
|
227
|
+
"toc",
|
|
228
|
+
];
|
|
229
|
+
|
|
126
230
|
/**
|
|
127
231
|
* Набор расширений — это и есть Схема в коде: что не зарегистрировано здесь,
|
|
128
232
|
* ProseMirror не разберёт ни из `value`, ни из буфера. Белый список ДАННЫМИ,
|
|
@@ -148,56 +252,29 @@ const HandledTabStaysInside = Extension.create({
|
|
|
148
252
|
* `codeBlock` из StarterKit: язык из `codeLanguages.ts` и подсветка
|
|
149
253
|
* декорациями lowlight (ADR-0051). В Документ по-прежнему уходит только
|
|
150
254
|
* `class="language-*"` на `<code>`. Оно, как и таблица, приходит
|
|
151
|
-
*
|
|
255
|
+
* из описания ленивыми Extensions (`codeBlockTool.ts`, `tableTool.ts`).
|
|
152
256
|
*/
|
|
153
257
|
export function createEditorExtensions(options: EditorExtensionOptions): Extensions {
|
|
154
258
|
/*
|
|
155
|
-
Движок — проекция списка описаний (`toolDescriptor.ts`):
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
между Инструментами (уровни заголовка, Обёртка в колонке) спрашивают сам
|
|
159
|
-
набор.
|
|
259
|
+
Движок — проекция списка описаний (`toolDescriptor.ts`): Extensions
|
|
260
|
+
Инструмента берутся из его описания. Связи между Инструментами (уровни
|
|
261
|
+
заголовка, Обёртка в колонке) спрашивают `has` контекста.
|
|
160
262
|
*/
|
|
161
|
-
const described =
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// настройки по умолчанию.
|
|
166
|
-
const toggle = (tool: Tool) => (on(tool) ? undefined : false);
|
|
263
|
+
const described = options.tools.descriptors;
|
|
264
|
+
// Без самого набора: связи между Инструментами — только через `has`.
|
|
265
|
+
const { tools, ...bridges } = options;
|
|
266
|
+
const context: ToolEngineContext = { ...bridges, has: (tool) => tools.has(tool) };
|
|
167
267
|
// Контейнеры Блоков верхнего уровня: их принимает только свой Документ.
|
|
168
|
-
const containers = [
|
|
169
|
-
...(on("layout") ? [LAYOUT_NODE] : []),
|
|
170
|
-
...(on("wrapper") ? [WRAPPER_NODE] : []),
|
|
171
|
-
...described.flatMap((descriptor) => descriptor.documentContent ?? []),
|
|
172
|
-
];
|
|
268
|
+
const containers = described.flatMap((descriptor) => descriptor.documentContent ?? []);
|
|
173
269
|
|
|
174
270
|
const extensions: Extensions = [
|
|
175
271
|
StarterKit.configure({
|
|
272
|
+
...STARTER_KIT_OFF,
|
|
273
|
+
// Доли описаний — одним StarterKit: порядок его Меток не меняется.
|
|
274
|
+
...Object.assign({}, ...described.map((descriptor) => descriptor.starterKit?.(context))),
|
|
176
275
|
// С Раскладкой или Обёрткой Документ — свой (`document.ts`): они не в
|
|
177
276
|
// группе `block`, и принять их может только тот, кто назвал их поимённо.
|
|
178
277
|
document: containers.length > 0 ? false : undefined,
|
|
179
|
-
// H1 — только с `heading1`; без него `#` и `<h1>` остаются
|
|
180
|
-
// прежними: правило ввода и разбор HTML строятся по этим уровням.
|
|
181
|
-
heading: on("heading")
|
|
182
|
-
? { levels: options.tools.has("heading1") ? [1, 2, 3, 4] : [2, 3, 4] }
|
|
183
|
-
: false,
|
|
184
|
-
bold: toggle("bold"),
|
|
185
|
-
italic: toggle("italic"),
|
|
186
|
-
underline: toggle("underline"),
|
|
187
|
-
strike: toggle("strike"),
|
|
188
|
-
code: toggle("code"),
|
|
189
|
-
bulletList: toggle("bulletList"),
|
|
190
|
-
orderedList: toggle("orderedList"),
|
|
191
|
-
// Цитата и разделитель переехали на описание (`features/builtins.ts`): их Часть для движка — оттуда.
|
|
192
|
-
blockquote: false,
|
|
193
|
-
// Блок кода — `RichCodeBlock` из описания (`codeBlockTool.ts`): язык и подсветка (ADR-0051).
|
|
194
|
-
codeBlock: false,
|
|
195
|
-
horizontalRule: false,
|
|
196
|
-
undoRedo: toggle("history"),
|
|
197
|
-
// Ссылка — `RichLink` из описания Инструмента (`linkTool.tsx`): StarterKit
|
|
198
|
-
// отдаёт только опции, а разбор `<a>` нужно сузить до Схемы.
|
|
199
|
-
link: false,
|
|
200
|
-
trailingNode: false,
|
|
201
278
|
}),
|
|
202
279
|
...(containers.length > 0 ? [createDocumentNode(containers)] : []),
|
|
203
280
|
Placeholder.configure({ placeholder: () => options.placeholder() }),
|
|
@@ -212,64 +289,36 @@ export function createEditorExtensions(options: EditorExtensionOptions): Extensi
|
|
|
212
289
|
HandledTabStaysInside,
|
|
213
290
|
// Команды над Блоками — своей разметки нет, включены всегда (`blockCommands.ts`).
|
|
214
291
|
// После ядра Tiptap: его `Mod+A` (весь Документ) опрашивается позже нашего.
|
|
215
|
-
BlockCommands.configure({ clearFormatting:
|
|
292
|
+
BlockCommands.configure({ clearFormatting: options.tools.has("clearFormatting") }),
|
|
216
293
|
// `⌘/` — меню ручки Блока по месту клавиши, раньше карт Меток (`blockCommands.ts`).
|
|
217
294
|
BlockMenuKey.configure({ openBlockMenu: options.openBlockMenu }),
|
|
218
295
|
// `showNotice` — в каждом Редакторе: сообщить может любой Инструмент (`fieldNotice.ts`).
|
|
219
296
|
FieldNoticeExtension.configure({ onNotice: options.onNotice }),
|
|
220
297
|
];
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
// Картинка — `figure` с подписью (`image.ts`). Без инструмента `<img>` из
|
|
241
|
-
// вставки и `value` отбрасывается, а файлы из буфера не перехватываются.
|
|
242
|
-
if (on("image") && options.image) extensions.push(RichImage.configure(options.image));
|
|
243
|
-
// Раскладка — узлы колонок и их клавиатура (`layout.ts`). Без инструмента
|
|
244
|
-
// `div[data-block]` из вставки и `value` не разбирается, и Блоки колонок
|
|
245
|
-
// встают в Документ подряд.
|
|
246
|
-
if (on("layout")) extensions.push(...layoutExtensions({ wrapper: options.tools.has("wrapper") }));
|
|
247
|
-
// Обёртка — узел и выход из неё по Enter (`wrapper.ts`). Без инструмента
|
|
248
|
-
// `div[data-block="wrapper"]` из вставки и `value` не разбирается, и её
|
|
249
|
-
// Блоки встают на её место подряд.
|
|
250
|
-
if (on("wrapper")) extensions.push(...WRAPPER_EXTENSIONS);
|
|
251
|
-
if (on("insertMenu") && options.slash) {
|
|
252
|
-
extensions.push(SlashCommand.configure({ bridge: options.slash }));
|
|
298
|
+
// Признаки Блоков описаний (`blocks`) — одним расширением на весь набор.
|
|
299
|
+
const traits: Record<string, BlockTraits> = Object.assign(
|
|
300
|
+
{},
|
|
301
|
+
...described.map((descriptor) => descriptor.blocks)
|
|
302
|
+
);
|
|
303
|
+
if (Object.keys(traits).length > 0) extensions.push(blockTraitsExtension(traits));
|
|
304
|
+
// Extensions встроенных Инструментов — в порядке кита, свои — следом в порядке списка.
|
|
305
|
+
const engineOf = (descriptor: ToolDescriptor) =>
|
|
306
|
+
descriptor.engine?.(context, descriptor.options) ?? [];
|
|
307
|
+
for (const slot of ENGINE_ORDER) {
|
|
308
|
+
if (slot === TRIGGERS) {
|
|
309
|
+
if (options.triggers?.length) {
|
|
310
|
+
extensions.push(TriggerCommand.configure({ triggers: options.triggers }));
|
|
311
|
+
}
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const descriptor = described.find((candidate) => candidate.name === slot);
|
|
315
|
+
if (descriptor) extensions.push(...engineOf(descriptor));
|
|
253
316
|
}
|
|
254
|
-
|
|
255
|
-
|
|
317
|
+
for (const descriptor of described) {
|
|
318
|
+
if (!(ENGINE_ORDER as readonly string[]).includes(descriptor.name)) {
|
|
319
|
+
extensions.push(...engineOf(descriptor));
|
|
320
|
+
}
|
|
256
321
|
}
|
|
257
|
-
/*
|
|
258
|
-
Части для движка переехавших Инструментов — в порядке списка. ЗДЕСЬ ЖЕ
|
|
259
|
-
ЗАДАЁТСЯ ПОРЯДОК ВСТАВКИ И DROP (ADR-0058, п. 9.5): плагины ProseMirror у
|
|
260
|
-
Tiptap идут в обратном порядке регистрации при равном `priority`, и
|
|
261
|
-
зарегистрированное последним опрашивается первым. Свои Инструменты стоят в
|
|
262
|
-
конце списка (`expandTools`: за встроенными), а встроенные вставка Markdown,
|
|
263
|
-
загрузка картинок и Раскладка зарегистрированы выше — поэтому
|
|
264
|
-
`transformPastedHTML`, `transformPasted`, `handlePaste` и `handleDrop` своего
|
|
265
|
-
Инструмента видят событие раньше и могут забрать его. Менять порядок
|
|
266
|
-
регистрации или давать встроенному плагину `priority` выше 100 нельзя без
|
|
267
|
-
правки этого правила. На том же порядке держится `Alt+F10` блока кода
|
|
268
|
-
(`codeBlockTool.ts`): его расширение здесь, после `ToolbarFocus`, и
|
|
269
|
-
опрашивается раньше.
|
|
270
|
-
*/
|
|
271
|
-
for (const descriptor of described)
|
|
272
|
-
extensions.push(...(descriptor.engine?.(options, descriptor.options) ?? []));
|
|
273
322
|
// Сочетания кнопок описаний вешает кит: поле записи — их единственный источник.
|
|
274
323
|
const buttons = described.flatMap((descriptor) => descriptor.buttons ?? []);
|
|
275
324
|
// Поповер пункта вставки и Действия открывает та же команда, что у кнопки.
|
|
@@ -281,8 +330,14 @@ export function createEditorExtensions(options: EditorExtensionOptions): Extensi
|
|
|
281
330
|
.flatMap(({ id, label, popover }): ToolPopoverEntry[] =>
|
|
282
331
|
popover ? [{ id, label: ({ messages, locale }) => label(messages, locale), popover }] : []
|
|
283
332
|
);
|
|
284
|
-
|
|
285
|
-
|
|
333
|
+
// Кнопке без сочетания и поповера (отмена, картинка) расширение не нужно: её
|
|
334
|
+
// `run` зовёт Панель. Так набор расширений не меняется от того, что кнопка
|
|
335
|
+
// переехала в описание.
|
|
336
|
+
const keyed = buttons.filter((button) => button.shortcut || button.popover);
|
|
337
|
+
if (keyed.length + entries.length > 0) {
|
|
338
|
+
extensions.push(
|
|
339
|
+
ToolButtons.configure({ buttons: keyed, entries, openPopover: options.openPopover })
|
|
340
|
+
);
|
|
286
341
|
}
|
|
287
342
|
// То же — у пунктов вставки и Действий над Блоком.
|
|
288
343
|
const keyedItems = described.flatMap((d) => d.insertItems ?? []).filter((i) => i.shortcut);
|