@olddevs/ui 4.5.1 → 4.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/editor/RichTextEditor.d.ts +2 -2
  3. package/dist/editor/RichTextEditor.d.ts.map +1 -1
  4. package/dist/editor/RichTextEditor.js +3 -0
  5. package/dist/editor/RichTextEditor.js.map +1 -1
  6. package/dist/editor/core/blockTypes.d.ts +2 -8
  7. package/dist/editor/core/blockTypes.d.ts.map +1 -1
  8. package/dist/editor/core/blockTypes.js +11 -54
  9. package/dist/editor/core/blockTypes.js.map +1 -1
  10. package/dist/editor/core/containers.d.ts.map +1 -1
  11. package/dist/editor/core/containers.js +1 -0
  12. package/dist/editor/core/containers.js.map +1 -1
  13. package/dist/editor/core/customMarkdown.js +1 -1
  14. package/dist/editor/core/customMarkdown.js.map +1 -1
  15. package/dist/editor/core/customTool.d.ts +6 -6
  16. package/dist/editor/core/customTool.d.ts.map +1 -1
  17. package/dist/editor/core/customToolEngine.d.ts +2 -2
  18. package/dist/editor/core/extensions.d.ts +7 -2
  19. package/dist/editor/core/extensions.d.ts.map +1 -1
  20. package/dist/editor/core/extensions.js +96 -64
  21. package/dist/editor/core/extensions.js.map +1 -1
  22. package/dist/editor/core/fieldNotice.d.ts +1 -1
  23. package/dist/editor/core/insertItems.d.ts +5 -28
  24. package/dist/editor/core/insertItems.d.ts.map +1 -1
  25. package/dist/editor/core/insertItems.js +3 -94
  26. package/dist/editor/core/insertItems.js.map +1 -1
  27. package/dist/editor/core/insertMenuTool.d.ts +13 -0
  28. package/dist/editor/core/insertMenuTool.d.ts.map +1 -0
  29. package/dist/editor/core/insertMenuTool.js +20 -0
  30. package/dist/editor/core/insertMenuTool.js.map +1 -0
  31. package/dist/editor/core/schema.d.ts +20 -5
  32. package/dist/editor/core/schema.d.ts.map +1 -1
  33. package/dist/editor/core/schema.js +18 -3
  34. package/dist/editor/core/schema.js.map +1 -1
  35. package/dist/editor/core/shortcuts.d.ts +56 -5
  36. package/dist/editor/core/shortcuts.d.ts.map +1 -1
  37. package/dist/editor/core/shortcuts.js +91 -156
  38. package/dist/editor/core/shortcuts.js.map +1 -1
  39. package/dist/editor/core/skeleton.d.ts +1 -1
  40. package/dist/editor/core/skeleton.d.ts.map +1 -1
  41. package/dist/editor/core/skeleton.js +1 -0
  42. package/dist/editor/core/skeleton.js.map +1 -1
  43. package/dist/editor/core/toolButtons.d.ts +3 -3
  44. package/dist/editor/core/toolDefinition.d.ts +3 -3
  45. package/dist/editor/core/toolDescriptor.d.ts +49 -30
  46. package/dist/editor/core/toolDescriptor.d.ts.map +1 -1
  47. package/dist/editor/core/toolDescriptor.js +2 -5
  48. package/dist/editor/core/toolDescriptor.js.map +1 -1
  49. package/dist/editor/core/toolLoader.d.ts +1 -1
  50. package/dist/editor/core/toolLoader.d.ts.map +1 -1
  51. package/dist/editor/core/toolOptions.d.ts +19 -2
  52. package/dist/editor/core/toolOptions.d.ts.map +1 -1
  53. package/dist/editor/core/toolOptions.js.map +1 -1
  54. package/dist/editor/core/tools.d.ts +10 -2
  55. package/dist/editor/core/tools.d.ts.map +1 -1
  56. package/dist/editor/core/tools.js +1 -0
  57. package/dist/editor/core/tools.js.map +1 -1
  58. package/dist/editor/features/builtins.d.ts +6 -8
  59. package/dist/editor/features/builtins.d.ts.map +1 -1
  60. package/dist/editor/features/builtins.js +48 -21
  61. package/dist/editor/features/builtins.js.map +1 -1
  62. package/dist/editor/features/code/codeBlockTool.d.ts +5 -6
  63. package/dist/editor/features/code/codeBlockTool.d.ts.map +1 -1
  64. package/dist/editor/features/code/codeBlockTool.js +10 -1
  65. package/dist/editor/features/code/codeBlockTool.js.map +1 -1
  66. package/dist/editor/features/color/colorTools.d.ts +14 -0
  67. package/dist/editor/features/color/colorTools.d.ts.map +1 -0
  68. package/dist/editor/features/color/colorTools.js +39 -0
  69. package/dist/editor/features/color/colorTools.js.map +1 -0
  70. package/dist/editor/features/details/details.d.ts +68 -0
  71. package/dist/editor/features/details/details.d.ts.map +1 -0
  72. package/dist/editor/features/details/details.js +237 -0
  73. package/dist/editor/features/details/details.js.map +1 -0
  74. package/dist/editor/features/details/detailsMarkdown.d.ts +17 -0
  75. package/dist/editor/features/details/detailsMarkdown.d.ts.map +1 -0
  76. package/dist/editor/features/details/detailsMarkdown.js +84 -0
  77. package/dist/editor/features/details/detailsMarkdown.js.map +1 -0
  78. package/dist/editor/features/details/detailsTool.d.ts +16 -0
  79. package/dist/editor/features/details/detailsTool.d.ts.map +1 -0
  80. package/dist/editor/features/details/detailsTool.js +39 -0
  81. package/dist/editor/features/details/detailsTool.js.map +1 -0
  82. package/dist/editor/features/emoji/emojiTool.d.ts +1 -1
  83. package/dist/editor/features/heading/headingTool.d.ts +18 -0
  84. package/dist/editor/features/heading/headingTool.d.ts.map +1 -0
  85. package/dist/editor/features/heading/headingTool.js +59 -0
  86. package/dist/editor/features/heading/headingTool.js.map +1 -0
  87. package/dist/editor/features/history/historyTool.d.ts +15 -0
  88. package/dist/editor/features/history/historyTool.d.ts.map +1 -0
  89. package/dist/editor/features/history/historyTool.js +28 -0
  90. package/dist/editor/features/history/historyTool.js.map +1 -0
  91. package/dist/editor/features/image/imageTool.d.ts +19 -0
  92. package/dist/editor/features/image/imageTool.d.ts.map +1 -0
  93. package/dist/editor/features/image/imageTool.js +28 -0
  94. package/dist/editor/features/image/imageTool.js.map +1 -0
  95. package/dist/editor/features/layout/layoutTool.d.ts +11 -0
  96. package/dist/editor/features/layout/layoutTool.d.ts.map +1 -0
  97. package/dist/editor/features/layout/layoutTool.js +20 -0
  98. package/dist/editor/features/layout/layoutTool.js.map +1 -0
  99. package/dist/editor/features/lists/listTools.d.ts +15 -0
  100. package/dist/editor/features/lists/listTools.d.ts.map +1 -0
  101. package/dist/editor/features/lists/listTools.js +78 -0
  102. package/dist/editor/features/lists/listTools.js.map +1 -0
  103. package/dist/editor/features/marks/markTools.d.ts +16 -0
  104. package/dist/editor/features/marks/markTools.d.ts.map +1 -0
  105. package/dist/editor/features/marks/markTools.js +43 -0
  106. package/dist/editor/features/marks/markTools.js.map +1 -0
  107. package/dist/editor/features/search/searchTool.d.ts +1 -1
  108. package/dist/editor/features/table/tableTool.d.ts +3 -3
  109. package/dist/editor/features/wrapper/wrapperTool.d.ts +11 -0
  110. package/dist/editor/features/wrapper/wrapperTool.d.ts.map +1 -0
  111. package/dist/editor/features/wrapper/wrapperTool.js +32 -0
  112. package/dist/editor/features/wrapper/wrapperTool.js.map +1 -0
  113. package/dist/editor/index.d.ts +1 -1
  114. package/dist/editor/index.d.ts.map +1 -1
  115. package/dist/editor/index.js.map +1 -1
  116. package/dist/editor/schema-entry.d.ts +1 -1
  117. package/dist/editor/styles/prose.d.ts.map +1 -1
  118. package/dist/editor/styles/prose.js +26 -0
  119. package/dist/editor/styles/prose.js.map +1 -1
  120. package/dist/editor/ui/EditorToolbar.d.ts.map +1 -1
  121. package/dist/editor/ui/EditorToolbar.js +5 -38
  122. package/dist/editor/ui/EditorToolbar.js.map +1 -1
  123. package/dist/editor/ui/SelectionBubble.d.ts.map +1 -1
  124. package/dist/editor/ui/SelectionBubble.js +1 -1
  125. package/dist/editor/ui/SelectionBubble.js.map +1 -1
  126. package/dist/editor/ui/ToolButtonView.d.ts.map +1 -1
  127. package/dist/editor/ui/ToolButtonView.js +1 -1
  128. package/dist/editor/ui/ToolButtonView.js.map +1 -1
  129. package/dist/i18n/defaults.d.ts.map +1 -1
  130. package/dist/i18n/defaults.js +3 -0
  131. package/dist/i18n/defaults.js.map +1 -1
  132. package/dist/i18n/messages.d.ts +16 -1
  133. package/dist/i18n/messages.d.ts.map +1 -1
  134. package/dist/icons.d.ts +8 -1
  135. package/dist/icons.d.ts.map +1 -1
  136. package/dist/icons.js +1 -1
  137. package/dist/icons.js.map +1 -1
  138. package/dist/locales/ru.d.ts.map +1 -1
  139. package/dist/locales/ru.js +3 -0
  140. package/dist/locales/ru.js.map +1 -1
  141. package/docs/api/editor-extension.en.md +68 -68
  142. package/docs/api/editor-extension.md +55 -55
  143. package/docs/api/messages.en.md +4 -1
  144. package/docs/api/messages.md +3 -0
  145. package/docs/api/migration-notes.en.md +11 -11
  146. package/docs/api/migration-notes.md +2 -2
  147. package/docs/api/utilities.en.md +6 -6
  148. package/docs/components/actions/split-button.en.md +9 -9
  149. package/docs/components/content/data-display.en.md +1 -1
  150. package/docs/components/content/layout-primitives.en.md +3 -3
  151. package/docs/components/content/rich-text-toc.en.md +1 -1
  152. package/docs/components/content/rich-text-view.en.md +23 -17
  153. package/docs/components/content/rich-text-view.md +17 -11
  154. package/docs/components/content/virtual-grid.en.md +2 -2
  155. package/docs/components/date-time/date-picker.en.md +1 -1
  156. package/docs/components/inputs/rich-text-editor.en.md +283 -230
  157. package/docs/components/inputs/rich-text-editor.md +139 -86
  158. package/docs/components/navigation/mega-menu.en.md +10 -10
  159. package/docs/components/overlays/menu.en.md +2 -2
  160. package/docs/components/overlays/shortcuts-help.en.md +1 -1
  161. package/docs/components/table/data-table.en.md +3 -3
  162. package/docs/components/table/filter-bar.en.md +1 -1
  163. package/docs/components/table/toolbar.en.md +1 -1
  164. package/docs/conventions.en.md +2 -2
  165. package/docs/conventions.md +1 -1
  166. package/docs/getting-started.en.md +9 -9
  167. package/docs/getting-started.md +8 -8
  168. package/docs/glossary.en.md +193 -133
  169. package/docs/glossary.md +191 -132
  170. package/docs/icons.en.md +12 -9
  171. package/docs/icons.md +3 -0
  172. package/docs/palette.en.md +3 -3
  173. package/docs/theming.en.md +6 -6
  174. package/package.json +2 -1
  175. package/src/editor/RichTextEditor.tsx +9 -6
  176. package/src/editor/core/atomText.ts +2 -2
  177. package/src/editor/core/blockTypes.ts +19 -83
  178. package/src/editor/core/containers.ts +4 -2
  179. package/src/editor/core/customMarkdown.ts +2 -2
  180. package/src/editor/core/customTool.ts +6 -6
  181. package/src/editor/core/customToolEngine.ts +9 -9
  182. package/src/editor/core/extensions.ts +165 -110
  183. package/src/editor/core/fieldNotice.ts +1 -1
  184. package/src/editor/core/handle.ts +2 -2
  185. package/src/editor/core/insertItems.ts +12 -158
  186. package/src/editor/core/insertMenuTool.ts +32 -0
  187. package/src/editor/core/jsonDocument.ts +1 -1
  188. package/src/editor/core/schema.ts +42 -7
  189. package/src/editor/core/shortcuts.ts +116 -177
  190. package/src/editor/core/skeleton.ts +4 -3
  191. package/src/editor/core/toolButtons.ts +3 -3
  192. package/src/editor/core/toolDefinition.ts +4 -4
  193. package/src/editor/core/toolDescriptor.ts +65 -52
  194. package/src/editor/core/toolLoader.ts +3 -3
  195. package/src/editor/core/toolOptions.ts +21 -2
  196. package/src/editor/core/tools.ts +10 -1
  197. package/src/editor/core/viewIsland.ts +1 -1
  198. package/src/editor/features/builtins.ts +76 -41
  199. package/src/editor/features/code/codeBlockTool.ts +16 -8
  200. package/src/editor/features/color/colorTools.ts +68 -0
  201. package/src/editor/features/details/details.ts +389 -0
  202. package/src/editor/features/details/detailsMarkdown.ts +158 -0
  203. package/src/editor/features/details/detailsTool.ts +69 -0
  204. package/src/editor/features/emoji/emojiTool.tsx +1 -1
  205. package/src/editor/features/heading/headingTool.ts +96 -0
  206. package/src/editor/features/history/historyTool.ts +42 -0
  207. package/src/editor/features/image/imageTool.ts +49 -0
  208. package/src/editor/features/layout/layoutTool.ts +40 -0
  209. package/src/editor/features/lists/listTools.ts +112 -0
  210. package/src/editor/features/marks/markTools.ts +78 -0
  211. package/src/editor/features/search/searchTool.ts +1 -1
  212. package/src/editor/features/table/tableTool.ts +3 -3
  213. package/src/editor/features/toc/tocTool.ts +1 -1
  214. package/src/editor/features/wrapper/wrapperTool.ts +52 -0
  215. package/src/editor/index.ts +1 -0
  216. package/src/editor/schema-entry.ts +1 -1
  217. package/src/editor/styles/prose.ts +49 -0
  218. package/src/editor/ui/EditorToolbar.tsx +7 -50
  219. package/src/editor/ui/SelectionBubble.tsx +2 -5
  220. package/src/editor/ui/ToolButtonView.tsx +2 -1
  221. package/src/i18n/defaults.ts +3 -0
  222. package/src/i18n/messages.ts +14 -1
  223. package/src/icons.ts +7 -0
  224. package/src/locales/ru.ts +3 -0
@@ -63,6 +63,13 @@ export const RICH_TEXT_SCHEMA = {
63
63
  * Стоит на верхнем уровне и прямо в колонке Раскладки; внутри — Блоки, кроме
64
64
  * другой Обёртки и Раскладки.
65
65
  *
66
+ * `details` — сворачиваемый Блок: `details > summary` и следом
67
+ * `div[data-type="detailsContent"]` с Блоками; `open` — только у `details` и
68
+ * только пустым (`open=""`), так его пишет Редактор с Опцией `persist`.
69
+ * `summary` вне `details` и обёртка содержимого вне него снимаются —
70
+ * Блоки остаются подряд. Теги и атрибуты Блока открыты только в этой форме:
71
+ * `iframe` и ему подобное так не открывается.
72
+ *
66
73
  * Таблица — `table > tbody > tr > th|td`, без `thead`, `colgroup` и
67
74
  * `caption`: строка-заголовок — это обычная первая строка из `th`. На ячейке
68
75
  * бывают только слияние (`colspan`, `rowspan`) и выравнивание столбца
@@ -94,6 +101,8 @@ export const RICH_TEXT_SCHEMA = {
94
101
  "figure",
95
102
  "img",
96
103
  "figcaption",
104
+ "details",
105
+ "summary",
97
106
  ],
98
107
  /**
99
108
  * Метки и строчные узлы. `br` — мягкий перенос по Shift+Enter. `mark` —
@@ -112,9 +121,13 @@ export const RICH_TEXT_SCHEMA = {
112
121
  * Раскладка и её колонка: `data-block="layout"` и `data-block="column"`;
113
122
  * `data-layout` — вариант из `layouts`, только у Раскладки. Обёртка —
114
123
  * `data-block="wrapper"` и, если у неё есть тон, `data-tone` — имя из
115
- * `wrapperTones`, только у Обёртки. У `div` пункта чек-листа атрибутов нет.
124
+ * `wrapperTones`, только у Обёртки. Обёртка содержимого сворачиваемого
125
+ * Блока — `data-type="detailsContent"`, только прямо в `details`. У `div`
126
+ * пункта чек-листа атрибутов нет.
116
127
  */
117
- div: ["data-block", "data-layout", "data-tone"],
128
+ div: ["data-block", "data-layout", "data-tone", "data-type"],
129
+ /** Открытый сворачиваемый Блок: пустой `open`, только с Опцией `persist`. */
130
+ details: ["open"],
118
131
  /** Пункт чек-листа: `data-type="taskItem"` и `data-checked="true|false"`. */
119
132
  li: ["data-type", "data-checked"],
120
133
  /** Флажок пункта чек-листа: всегда `type="checkbox"`, `checked` — у отмеченного. */
@@ -369,8 +382,8 @@ function customAttributes(
369
382
  * набор отвергает ошибкой поля, а у сервера ошибок нет — и описание с именем
370
383
  * кита (`wrapper`, атрибут `tone`) не должно открыть обход правил кита.
371
384
  *
372
- * Читаются только данные описания (`blocks`, `marks`), Часть для движка не
373
- * трогается: модуль с описанием своего Инструмента импортируется на сервере,
385
+ * Читаются только данные описания (`blocks`, `marks`), Extensions не
386
+ * трогаются: модуль с описанием своего Инструмента импортируется на сервере,
374
387
  * и Tiptap здесь не нужен.
375
388
  */
376
389
  export function richTextSchema(tools: readonly ToolEntry[] = []): RichTextSchema {
@@ -584,6 +597,10 @@ export interface RichTextPurifier {
584
597
  * колонке Раскладки, не в другой Обёртке, списке, цитате и таблице; иначе
585
598
  * снимается так же, вместе с тоном; `data-tone` — имя из `wrapperTones` и
586
599
  * только на Обёртке;
600
+ * - сворачиваемый Блок — `details` с `summary` прямо в нём и обёрткой содержимого
601
+ * `div[data-type="detailsContent"]` тоже прямо в нём; `open` — только пустой и
602
+ * только на `details`. `summary` и обёртка вне `details` теряют тег, их Блоки
603
+ * остаются подряд;
587
604
  * - `colspan`, `rowspan` — на `th`/`td`, целое от 1 до `tableSpans`;
588
605
  * - ссылка — `href` по `protocols` (выражение конфига), `rel` всегда
589
606
  * `"noopener noreferrer"`, ссылка без адреса становится текстом;
@@ -648,7 +665,14 @@ const ATTRIBUTE_RULES: Record<string, (node: Element, value: string) => boolean>
648
665
  start: (node) => node.nodeName === "OL",
649
666
  "data-type": (node, value) =>
650
667
  (node.nodeName === "UL" && value === "taskList") ||
651
- (node.nodeName === "LI" && value === "taskItem"),
668
+ (node.nodeName === "LI" && value === "taskItem") ||
669
+ // Обёртка содержимого сворачиваемого Блока: только `div` прямо в `details`.
670
+ (node.nodeName === "DIV" &&
671
+ value === DETAILS_CONTENT &&
672
+ node.parentElement?.nodeName === "DETAILS"),
673
+ // Открытый Блок: `open=""`, как его пишет Редактор; значение `open="open"` из
674
+ // чужого HTML атрибут теряет, Блок остаётся закрытым.
675
+ open: (node, value) => node.nodeName === "DETAILS" && value === "",
652
676
  "data-checked": (node, value) =>
653
677
  node.nodeName === "LI" &&
654
678
  node.getAttribute("data-type") === "taskItem" &&
@@ -787,6 +811,9 @@ const PARENT_OF: Record<string, string> = {
787
811
  SPAN: `${TASK_ITEM} > label`,
788
812
  };
789
813
 
814
+ /** Значение `div[data-type]` обёртки содержимого сворачиваемого Блока (`details.ts`). */
815
+ const DETAILS_CONTENT = "detailsContent";
816
+
790
817
  /** Значения `div[data-block]`: Раскладка, её колонка и Обёртка. */
791
818
  const LAYOUT_BLOCK = "layout";
792
819
  const COLUMN_BLOCK = "column";
@@ -794,10 +821,10 @@ const WRAPPER_BLOCK = "wrapper";
794
821
 
795
822
  /**
796
823
  * Где Раскладки быть не может: в другой Раскладке (её колонке), в Обёртке, в
797
- * списке, цитате и ячейке. Редактор такую не отдаёт — у колонки и прочих контейнеров
824
+ * списке, цитате, ячейке и сворачиваемом Блоке. Редактор такую не отдаёт — у колонки и прочих контейнеров
798
825
  * в Схеме движка её нет, — и при загрузке разворачивает в Блоки подряд.
799
826
  */
800
- const NOT_A_LAYOUT_PLACE = "div[data-block], li, blockquote, th, td";
827
+ const NOT_A_LAYOUT_PLACE = "div[data-block], li, blockquote, th, td, details";
801
828
 
802
829
  /**
803
830
  * Раскладка, колонка и Обёртка: `true` — элемент обработан (оставлен или
@@ -868,6 +895,14 @@ function checkStructure(node: Element, schema: RichTextSchema): void {
868
895
  return;
869
896
  }
870
897
  if (node.nodeName === "SPAN" && coloured) return;
898
+ // Сворачиваемый Блок: `summary` — только прямо в `details`, обёртка
899
+ // содержимого с `data-type` — тоже (атрибут уже снят хуком, если она не там).
900
+ // Вне Блока они теряют обёртку, содержимое остаётся.
901
+ if (node.nodeName === "SUMMARY") {
902
+ if (node.parentElement?.nodeName !== "DETAILS") node.replaceWith(...node.childNodes);
903
+ return;
904
+ }
905
+ if (node.nodeName === "DIV" && node.getAttribute("data-type") === DETAILS_CONTENT) return;
871
906
  // Картинка — `img` с адресом прямо внутри `figure`, и только так: `figure`
872
907
  // без годной картинки разворачивается в содержимое, `img` вне `figure` или без
873
908
  // адреса удаляется, `figcaption` вне `figure` теряет обёртку.
@@ -1,5 +1,5 @@
1
1
  import type { UiMessages } from "../../i18n/messages.js";
2
- import { describedTools, toolLabel, type ToolList } from "./toolDescriptor.js";
2
+ import { toolLabel, type ToolList } from "./toolDescriptor.js";
3
3
  import type { PrefixedName } from "./customTool.js";
4
4
  import type { Tool } from "./tools.js";
5
5
 
@@ -65,7 +65,94 @@ export interface ShortcutCommand {
65
65
  const LIST_TOOLS: readonly Tool[] = ["bulletList", "orderedList", "taskList"];
66
66
 
67
67
  /**
68
- * Таблица — сочетания кита и Инструментов, ещё не переехавших на описание.
68
+ * Сочетания встроенных команд по id — данные, а не привязки. Строка справочника
69
+ * берёт клавиши отсюда, где бы она ни жила: в `SHORTCUTS` или в описании
70
+ * Инструмента (`shortcuts`, `keys` кнопки). Поэтому подписи Меню вставки и «¶»,
71
+ * которые читаются при загрузке модуля (`shortcutKeys`), не зависят от того,
72
+ * где лежит строка.
73
+ */
74
+ export const SHORTCUT_KEYS = {
75
+ selectBlock: ["mod", "A"],
76
+ selectDocument: ["mod", "A"],
77
+ undo: ["mod", "Z"],
78
+ redo: ["mod", "shift", "Z"],
79
+ duplicateBlock: ["mod", "shift", "D"],
80
+ moveBlockUp: ["alt", "shift", "↑"],
81
+ moveBlockDown: ["alt", "shift", "↓"],
82
+ blockActions: ["mod", "/"],
83
+ clearFormatting: ["mod", "\\"],
84
+ bold: ["mod", "B"],
85
+ italic: ["mod", "I"],
86
+ strike: ["mod", "shift", "S"],
87
+ underline: ["mod", "U"],
88
+ code: ["mod", "E"],
89
+ superscript: ["mod", "."],
90
+ subscript: ["mod", ","],
91
+ paragraph: ["mod", "alt", "0"],
92
+ h1: ["mod", "alt", "1"],
93
+ h2: ["mod", "alt", "2"],
94
+ h3: ["mod", "alt", "3"],
95
+ h4: ["mod", "alt", "4"],
96
+ blockquote: ["mod", "shift", "B"],
97
+ codeBlock: ["mod", "alt", "C"],
98
+ bulletList: ["mod", "shift", "8"],
99
+ orderedList: ["mod", "shift", "7"],
100
+ taskList: ["mod", "shift", "9"],
101
+ indentListItem: ["mod", "]"],
102
+ outdentListItem: ["mod", "["],
103
+ alignLeft: ["mod", "shift", "L"],
104
+ alignCenter: ["mod", "shift", "E"],
105
+ alignRight: ["mod", "shift", "R"],
106
+ focusToolbar: ["alt", "F10"],
107
+ } as const satisfies Record<string, readonly string[]>;
108
+
109
+ /** Id строки справочника из таблицы кита. */
110
+ export type ShortcutId = keyof typeof SHORTCUT_KEYS;
111
+
112
+ /**
113
+ * Порядок строк справочника внутри групп — привилегия кита, как `INSERT_ORDER`
114
+ * у Меню вставки, и не зависит от того, где живёт строка: в `SHORTCUTS` или в
115
+ * описании Инструмента. Строки вне списка (кнопки описаний, свои Инструменты)
116
+ * встают в конец своей группы в порядке набора.
117
+ */
118
+ export const SHORTCUT_ORDER: readonly ShortcutId[] = [
119
+ "selectBlock",
120
+ "selectDocument",
121
+ "undo",
122
+ "redo",
123
+ "duplicateBlock",
124
+ "moveBlockUp",
125
+ "moveBlockDown",
126
+ "blockActions",
127
+ "clearFormatting",
128
+ "bold",
129
+ "italic",
130
+ "strike",
131
+ "underline",
132
+ "code",
133
+ "superscript",
134
+ "subscript",
135
+ "paragraph",
136
+ "h1",
137
+ "h2",
138
+ "h3",
139
+ "h4",
140
+ "blockquote",
141
+ "codeBlock",
142
+ "bulletList",
143
+ "orderedList",
144
+ "taskList",
145
+ "indentListItem",
146
+ "outdentListItem",
147
+ "alignLeft",
148
+ "alignCenter",
149
+ "alignRight",
150
+ "focusToolbar",
151
+ ];
152
+
153
+ /**
154
+ * Таблица — сочетания кита: команды, которые есть при любом наборе (выделение,
155
+ * Блоки, сдвиг пункта списка, `Alt+F10`). Строки Инструментов — в их описаниях.
69
156
  * Сочетание кнопки описания — поле самой кнопки (`toolDescriptor.ts`): её
70
157
  * строку справочник берёт оттуда, клавишу вешает `toolButtons.ts`.
71
158
  */
@@ -75,45 +162,36 @@ export const SHORTCUTS: readonly ShortcutCommand[] = [
75
162
  id: "selectBlock",
76
163
  group: "selection",
77
164
  tool: null,
78
- keys: ["mod", "A"],
165
+ keys: SHORTCUT_KEYS.selectBlock,
79
166
  label: (m) => m.selectBlock,
80
167
  },
81
168
  {
82
169
  id: "selectDocument",
83
170
  group: "selection",
84
171
  tool: null,
85
- keys: ["mod", "A"],
172
+ keys: SHORTCUT_KEYS.selectDocument,
86
173
  label: (m) => m.selectDocument,
87
174
  },
88
175
 
89
- { id: "undo", group: "history", tool: "history", keys: ["mod", "Z"], label: (m) => m.undo },
90
- {
91
- id: "redo",
92
- group: "history",
93
- tool: "history",
94
- keys: ["mod", "shift", "Z"],
95
- label: (m) => m.redo,
96
- },
97
-
98
176
  {
99
177
  id: "duplicateBlock",
100
178
  group: "blocks",
101
179
  tool: null,
102
- keys: ["mod", "shift", "D"],
180
+ keys: SHORTCUT_KEYS.duplicateBlock,
103
181
  label: (m) => m.duplicateBlock,
104
182
  },
105
183
  {
106
184
  id: "moveBlockUp",
107
185
  group: "blocks",
108
186
  tool: null,
109
- keys: ["alt", "shift", "↑"],
187
+ keys: SHORTCUT_KEYS.moveBlockUp,
110
188
  label: (m) => m.moveBlockUp,
111
189
  },
112
190
  {
113
191
  id: "moveBlockDown",
114
192
  group: "blocks",
115
193
  tool: null,
116
- keys: ["alt", "shift", "↓"],
194
+ keys: SHORTCUT_KEYS.moveBlockDown,
117
195
  label: (m) => m.moveBlockDown,
118
196
  },
119
197
  // Меню ручки Блока: ручка есть в любой правке, при любой Панели.
@@ -121,172 +199,41 @@ export const SHORTCUTS: readonly ShortcutCommand[] = [
121
199
  id: "blockActions",
122
200
  group: "blocks",
123
201
  tool: null,
124
- keys: ["mod", "/"],
202
+ keys: SHORTCUT_KEYS.blockActions,
125
203
  label: (m) => m.blockActions,
126
204
  },
127
205
 
128
- {
129
- id: "clearFormatting",
130
- group: "clear",
131
- tool: "clearFormatting",
132
- keys: ["mod", "\\"],
133
- label: (m) => m.clearFormatting,
134
- },
135
-
136
- { id: "bold", group: "marks", tool: "bold", keys: ["mod", "B"], label: (m) => m.bold },
137
- { id: "italic", group: "marks", tool: "italic", keys: ["mod", "I"], label: (m) => m.italic },
138
- {
139
- id: "strike",
140
- group: "marks",
141
- tool: "strike",
142
- keys: ["mod", "shift", "S"],
143
- label: (m) => m.strike,
144
- },
145
- {
146
- id: "underline",
147
- group: "marks",
148
- tool: "underline",
149
- keys: ["mod", "U"],
150
- label: (m) => m.underline,
151
- },
152
- { id: "code", group: "marks", tool: "code", keys: ["mod", "E"], label: (m) => m.code },
153
- {
154
- id: "superscript",
155
- group: "marks",
156
- tool: "superscript",
157
- keys: ["mod", "."],
158
- label: (m) => m.superscript,
159
- },
160
- {
161
- id: "subscript",
162
- group: "marks",
163
- tool: "subscript",
164
- keys: ["mod", ","],
165
- label: (m) => m.subscript,
166
- },
167
-
168
- // Подпись — как у пункта «¶» («Текст»), а не «Абзац» Меню вставки: справочник
169
- // называет команду тем словом, которым её выбирают на Панели.
170
- {
171
- id: "paragraph",
172
- group: "blockType",
173
- tool: "heading",
174
- keys: ["mod", "alt", "0"],
175
- label: (m) => m.textBlock,
176
- },
177
- {
178
- id: "h1",
179
- group: "blockType",
180
- tool: "heading1",
181
- requires: "heading",
182
- keys: ["mod", "alt", "1"],
183
- label: (m) => m.heading({ level: 1 }),
184
- },
185
- {
186
- id: "h2",
187
- group: "blockType",
188
- tool: "heading",
189
- keys: ["mod", "alt", "2"],
190
- label: (m) => m.heading({ level: 2 }),
191
- },
192
- {
193
- id: "h3",
194
- group: "blockType",
195
- tool: "heading",
196
- keys: ["mod", "alt", "3"],
197
- label: (m) => m.heading({ level: 3 }),
198
- },
199
- {
200
- id: "h4",
201
- group: "blockType",
202
- tool: "heading",
203
- keys: ["mod", "alt", "4"],
204
- label: (m) => m.heading({ level: 4 }),
205
- },
206
- {
207
- id: "codeBlock",
208
- group: "blockType",
209
- tool: "codeBlock",
210
- keys: ["mod", "alt", "C"],
211
- label: (m) => m.codeBlock,
212
- },
213
-
214
- {
215
- id: "bulletList",
216
- group: "lists",
217
- tool: "bulletList",
218
- keys: ["mod", "shift", "8"],
219
- label: (m) => m.bulletList,
220
- },
221
- {
222
- id: "orderedList",
223
- group: "lists",
224
- tool: "orderedList",
225
- keys: ["mod", "shift", "7"],
226
- label: (m) => m.orderedList,
227
- },
228
- {
229
- id: "taskList",
230
- group: "lists",
231
- tool: "taskList",
232
- keys: ["mod", "shift", "9"],
233
- label: (m) => m.taskList,
234
- },
235
206
  {
236
207
  id: "indentListItem",
237
208
  group: "lists",
238
209
  tool: LIST_TOOLS,
239
- keys: ["mod", "]"],
210
+ keys: SHORTCUT_KEYS.indentListItem,
240
211
  label: (m) => m.indentListItem,
241
212
  },
242
213
  {
243
214
  id: "outdentListItem",
244
215
  group: "lists",
245
216
  tool: LIST_TOOLS,
246
- keys: ["mod", "["],
217
+ keys: SHORTCUT_KEYS.outdentListItem,
247
218
  label: (m) => m.outdentListItem,
248
219
  },
249
220
 
250
- {
251
- id: "alignLeft",
252
- group: "align",
253
- tool: "align",
254
- keys: ["mod", "shift", "L"],
255
- label: (m) => m.alignLeft,
256
- },
257
- {
258
- id: "alignCenter",
259
- group: "align",
260
- tool: "align",
261
- keys: ["mod", "shift", "E"],
262
- label: (m) => m.alignCenter,
263
- },
264
- {
265
- id: "alignRight",
266
- group: "align",
267
- tool: "align",
268
- keys: ["mod", "shift", "R"],
269
- label: (m) => m.alignRight,
270
- },
271
-
272
221
  {
273
222
  id: "focusToolbar",
274
223
  group: "toolbar",
275
224
  tool: null,
276
- keys: ["alt", "F10"],
225
+ keys: SHORTCUT_KEYS.focusToolbar,
277
226
  label: (m) => m.focusToolbar,
278
227
  },
279
228
  ];
280
229
 
281
- const BY_ID = new Map(SHORTCUTS.map((command) => [command.id, command]));
282
-
283
230
  /**
284
231
  * Сочетание команды по id или `undefined`, если у команды его нет. Панель
285
232
  * спрашивает так про каждую свою кнопку: у горизонтальной линии, картинки и
286
233
  * меню цвета сочетаний нет.
287
234
  */
288
235
  export function findShortcutKeys(id: string): readonly string[] | undefined {
289
- return BY_ID.get(id)?.keys;
236
+ return Object.hasOwn(SHORTCUT_KEYS, id) ? SHORTCUT_KEYS[id as ShortcutId] : undefined;
290
237
  }
291
238
 
292
239
  /**
@@ -318,21 +265,13 @@ export interface ShortcutSection {
318
265
  commands: readonly ShortcutRow[];
319
266
  }
320
267
 
321
- /**
322
- * Место строки переехавшего Инструмента в своей группе: id строки → id строки
323
- * из `SHORTCUTS`, перед которой она встаёт. Остальные переехавшие строки идут
324
- * в конец группы. Порядок справочника — не поле описания: место на поверхности
325
- * держит кит, как `INSERT_ORDER` у Меню вставки.
326
- */
327
- const MOVED_BEFORE: Readonly<Record<string, string>> = { blockquote: "codeBlock" };
328
-
329
268
  /**
330
269
  * Команды включённых инструментов по группам; пустые группы не возвращаются.
331
270
  *
332
- * Проекция списка описаний: строки не переехавших Инструментов — из
333
- * `SHORTCUTS`, переехавших — из их описаний (`toolDescriptor.ts`): клавиши
334
- * Части для движка — поле `shortcuts`, сочетания кнопок — поле кнопки. Строка
335
- * встаёт на место из `MOVED_BEFORE` или следом за строками своей группы кита;
271
+ * Проекция списка описаний: строки кита — из `SHORTCUTS`, строки Инструментов —
272
+ * из их описаний (`toolDescriptor.ts`): клавиши
273
+ * Extensions — поле `shortcuts`, сочетания кнопок — поле кнопки. Место
274
+ * строки в группе — `SHORTCUT_ORDER`, а строки вне него — следом в порядке набора;
336
275
  * кнопка без группы кита — в группу своего Инструмента после групп кита, в
337
276
  * порядке списка (ADR-0058, п. 6.8).
338
277
  */
@@ -341,25 +280,18 @@ export function shortcutGroupsFor(
341
280
  /** Локаль `UiProvider` — для подписи кнопки, заданной функцией от контекста. */
342
281
  locale = "en-US"
343
282
  ): readonly ShortcutSection[] {
344
- const described = describedTools(tools);
283
+ const described = tools.descriptors;
345
284
  const moved = described.flatMap((descriptor) => descriptor.shortcuts ?? []);
346
- const anchored = (row: ShortcutCommand) =>
347
- SHORTCUTS.some((command) => command.id === MOVED_BEFORE[row.id]);
348
- const commands = [
349
- ...SHORTCUTS.flatMap((command) => [
350
- ...moved.filter((row) => MOVED_BEFORE[row.id] === command.id),
351
- command,
352
- ]),
353
- ...moved.filter((row) => !anchored(row)),
354
- ].filter((command) => enabled(command, tools));
285
+ const commands = [...SHORTCUTS, ...moved].filter((command) => enabled(command, tools));
355
286
  const ownGroups: ShortcutSection[] = [];
356
287
  for (const descriptor of described) {
357
288
  const own: ShortcutRow[] = [];
358
289
  for (const button of descriptor.buttons ?? []) {
359
- if (!button.shortcut) continue;
290
+ const keys = button.shortcut ?? button.keys;
291
+ if (!keys) continue;
360
292
  const row: ShortcutRow = {
361
293
  id: button.id,
362
- keys: button.shortcut,
294
+ keys,
363
295
  label: (messages) => toolLabel(button.label, { locale, messages }),
364
296
  };
365
297
  // Набор уже отфильтрован: кнопка есть только у включённого Инструмента.
@@ -380,10 +312,17 @@ export function shortcutGroupsFor(
380
312
  }
381
313
  if (own.length > 0) ownGroups.push({ group: descriptor.name, commands: own });
382
314
  }
315
+ // Сортировка устойчивая: строки вне `SHORTCUT_ORDER` сохраняют порядок набора.
316
+ const rank = (id: string) => {
317
+ const index = (SHORTCUT_ORDER as readonly string[]).indexOf(id);
318
+ return index === -1 ? SHORTCUT_ORDER.length : index;
319
+ };
383
320
  return [
384
321
  ...SHORTCUT_GROUPS.map((group) => ({
385
322
  group,
386
- commands: commands.filter((command) => command.group === group),
323
+ commands: commands
324
+ .filter((command) => command.group === group)
325
+ .sort((left, right) => rank(left.id) - rank(right.id)),
387
326
  })),
388
327
  ...ownGroups,
389
328
  ].filter((entry) => entry.commands.length > 0);
@@ -19,9 +19,9 @@ import type { ToolLoader } from "./toolLoader.js";
19
19
  набора и выгружается обратно в HTML — в документ, тоже инертный. Что Схема не
20
20
  знает (теги, атрибуты, обработчики, `javascript:`), не доходит до страницы.
21
21
 
22
- Схема — та, что получит движок, но без Частей для движка, чьи загрузчики ещё
22
+ Схема — та, что получит движок, но без Extensions, чьи загрузчики ещё
23
23
  не отработали: их Блоки видны текстом, обёртку Схема снимает. Ленивый
24
- встроенный (`codeBlock`, `table`) вместо Части для движка даёт
24
+ встроенный (`codeBlock`, `table`) вместо Extensions даёт
25
25
  форму Блоков (`skeleton` описания) — его Блоки видны своей разметкой, и поле
26
26
  не прыгает с приходом чанка. Шапка блока кода — та же, что у `RichTextView`
27
27
  (`withCodeHeaders`): в Редакторе она есть, и без неё Скелет был бы ниже. Набор
@@ -37,6 +37,7 @@ const SCHEMA_ONLY: Omit<EditorExtensionOptions, "tools"> = {
37
37
  placeholder: () => "",
38
38
  limit: () => undefined,
39
39
  taskCheckbox: (text) => text,
40
+ detailsToggle: (text) => text,
40
41
  openPopover: quiet,
41
42
  focusToolbar: no,
42
43
  openBlockMenu: no,
@@ -83,7 +84,7 @@ function cleaned(html: string, tools: ToolList): string | null {
83
84
  }
84
85
 
85
86
  /**
86
- * Документ Скелета: `html`, очищенный Схемой набора без Частей ещё не
87
+ * Документ Скелета: `html`, очищенный Схемой набора без Extensions ещё не
87
88
  * отработавших загрузчиков (`pending`). Только в браузере.
88
89
  *
89
90
  * Набор с ошибкой своих Инструментов Схемы не даёт (ошибка поля, ADR-0058,
@@ -70,9 +70,9 @@ export function keymapName(keys: readonly string[]): string {
70
70
  }
71
71
 
72
72
  /**
73
- * Часть для движка у кнопок описаний (ADR-0058, п. 5.7): сочетание — поле
74
- * записи и единственный его источник, поэтому клавишу вешает кит, а не Часть
75
- * для движка Инструмента. `run` выполняется, поповер открывается у выделения.
73
+ * Сочетания у кнопок описаний (ADR-0058, п. 5.7): сочетание — поле
74
+ * записи и единственный его источник, поэтому клавишу вешает кит, а не
75
+ * Extensions Инструмента. `run` выполняется, поповер открывается у выделения.
76
76
  * Недоступная кнопка клавишу не берёт: она идёт дальше, в Документ.
77
77
  */
78
78
  export const ToolButtons = Extension.create<ToolButtonsOptions>({
@@ -10,7 +10,7 @@ import { isConfiguredTool, mergeOptions } from "./toolOptions.js";
10
10
  же формой `CustomTool` (ADR-0058): отдельного пути сборки у определения нет,
11
11
  кит собирает из него обычное описание Инструмента (`describeDefinition`), и
12
12
  все правила своего Инструмента — Префикс потребителя, уникальность имени,
13
- проверки набора, загрузчик Части для движка — действуют как были.
13
+ проверки набора, загрузчик Extensions — действуют как были.
14
14
 
15
15
  ИМЯ ФАЙЛА — НЕ `defineTool.ts`. Под этим именем сборка потребителя на Next.js
16
16
  15.5 (`check:consumer`) теряла `RichTextEditor` из клиентского манифеста RSC:
@@ -107,14 +107,14 @@ export interface ToolDefinition<
107
107
  * - `defaults` — полный набор Опций; тип Опций выводится из него, второй раз его
108
108
  * объявлять не нужно.
109
109
  * - `create(options)` отдаёт тело описания — ту же форму, что `CustomTool`, без
110
- * имени: Часть для движка, правила Схемы, UI-записи. Зовётся один раз на запись
110
+ * имени: Extensions, правила Схемы, UI-записи. Зовётся один раз на запись
111
111
  * `tools` (определение само или объект, что вернул `tool(definition, options)`):
112
112
  * кит запоминает результат, и Остров, замкнутый над Опциями, один и тот же на
113
113
  * каждом рендере. Запись, созданная заново на каждом рендере экрана
114
114
  * (`tools={[tool(mention, …)]}` в теле компонента), для кита новая — выносите
115
- * `tools` из рендера. Загрузчик Части для движка стоит держать константой
115
+ * `tools` из рендера. Загрузчик Extensions стоит держать константой
116
116
  * модуля: кэш загрузчика живёт на самой функции, а новая функция на каждый
117
- * вызов `create` загружала бы Часть заново для каждой записи.
117
+ * вызов `create` загружала бы Extensions заново для каждой записи.
118
118
  * - `state({ editor, options })` — Состояние для `useRichTextToolState(ref, name)`.
119
119
  * Зовётся при каждой транзакции и при каждом чтении хука, поэтому должна быть
120
120
  * быстрой и без побочных эффектов; вернуть — данные без DOM-узлов. Простые данные